[
  {
    "path": ".github/workflows/clean_workflow.yml",
    "content": "name: Clean Workflow Logs\non:\n  workflow_dispatch:\n    inputs:\n      keep_minimum_runs:\n        description: \"Numero di workflow recenti da mantenere\"\n        default: \"5\"\n        required: false\njobs:\n  clean-logs:\n    runs-on: ubuntu-latest\n    permissions:\n      actions: write\n    steps:\n      - name: Delete workflow runs\n        uses: Mattraks/delete-workflow-runs@v2\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n          repository: ${{ github.repository }}\n          retain_days: 0  # Imposta a 0 per ignorare la data e considerare solo keep_minimum_runs\n          keep_minimum_runs: ${{ github.event.inputs.keep_minimum_runs }}\n"
  },
  {
    "path": ".github/workflows/cli-build-esp32-dev.yml",
    "content": "# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json\n\nname: (ESP32) Build dev with Arduino CLI\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - dev\n      \n  pull_request:\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  arduino-esp32:\n    name: ESP32 (arduino-cli) (shard=${{ matrix.shard }})\n    if: github.event_name == 'workflow_dispatch' || vars.ENABLE_BUILDS == 'true'\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        shard: [1, 2, 3, 4]\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n\n      - name: Install arduino-cli\n        run: curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=/usr/local/bin sh\n\n      - name: Cache Arduino CLI data\n        uses: actions/cache@v4\n        with:\n          key: ${{ runner.os }}-arduino-esp32-${{ hashFiles('.github/workflows/cli-build-esp32-dev.yml') }}-${{ github.run_id }}-${{ matrix.shard }}\n          restore-keys: |\n            ${{ runner.os }}-arduino-esp32-${{ hashFiles('.github/workflows/cli-build-esp32-dev.yml') }}-\n            ${{ runner.os }}-arduino-esp32-\n          path: |\n            ~/.arduino15\n            ~/Arduino\n            ~/.cache/arduino-cli\n\n      - name: Update core index\n        run: arduino-cli core update-index --additional-urls https://espressif.github.io/arduino-esp32/package_esp32_index.json\n\n      - name: Install core\n        run: arduino-cli core install --additional-urls https://espressif.github.io/arduino-esp32/package_esp32_index.json esp32:esp32\n\n      - name: Install ArduinoJson\n        run: arduino-cli lib install ArduinoJson\n\n      - name: Install Arduino-MySQL\n        run: ARDUINO_LIBRARY_ENABLE_UNSAFE_INSTALL=true arduino-cli lib install --git-url https://github.com/cotestatnt/Arduino-MySQL\n\n      - name: Install Arduino_MFRC522v2\n        run: ARDUINO_LIBRARY_ENABLE_UNSAFE_INSTALL=true arduino-cli lib install --git-url https://github.com/OSSLibraries/Arduino_MFRC522v2\n\n      - name: Install PubSubClient\n        run: ARDUINO_LIBRARY_ENABLE_UNSAFE_INSTALL=true arduino-cli lib install --git-url https://github.com/knolleary/pubsubclient        \n\n      - name: Build Examples\n        run: |\n          set -uo pipefail\n\n          total_shards=4\n          shard=\"${{ matrix.shard }}\"\n\n          report_file=\"build-report-esp32-arduino-cli-shard${{ matrix.shard }}.txt\"\n          : > \"$report_file\"\n\n          failures=0\n          total=0\n          index=0\n\n          for dir in examples/*; do\n            if [[ ! -d \"$dir\" ]]; then\n              continue\n            fi\n\n            example=\"$(basename \"$dir\")\"\n\n            # Split examples across shards for faster CI.\n            index=$((index + 1))\n            example_shard=$(( (index - 1) % total_shards + 1 ))\n            if [[ \"$example_shard\" != \"$shard\" ]]; then\n              continue\n            fi\n\n            sketch=\"examples/${example}/${example}.ino\"\n            total=$((total + 1))\n\n            echo \"=============================================================\"\n            echo \"Building ${sketch}...\"\n            echo \"=============================================================\"\n\n            if [[ ! -f \"$sketch\" ]]; then\n              failures=$((failures + 1))\n              printf '%-25s : FAIL (missing %s)\\n' \"${example}\" \"${sketch}\" >> \"$report_file\"\n              continue\n            fi\n\n            echo \"::group::arduino-cli compile - ${example}\"\n            set +e\n            arduino-cli compile \\\n              --library . \\\n              --warnings none \\\n              --build-cache-path \"$HOME/.cache/arduino-cli\" \\\n              -b esp32:esp32:esp32 \\\n              \"$sketch\"\n            rc=$?\n            set -e\n            echo \"::endgroup::\"\n\n            if [[ $rc -eq 0 ]]; then\n              printf '%-25s : OK\\n' \"${example}\" >> \"$report_file\"\n            else\n              failures=$((failures + 1))\n              printf '%-25s : FAIL (exit=%s)\\n' \"${example}\" \"$rc\" >> \"$report_file\"\n            fi\n          done\n\n          echo \"\"\n          echo \"==================== Build report (arduino-cli) ====================\"\n          cat \"$report_file\"\n          echo \"===================================================================\"\n          echo \"Total attempted: ${total} | Failures: ${failures}\"\n\n          # Do not fail the job here: the final report job will decide.\n\n      - name: Upload build report\n        if: always()\n        uses: actions/upload-artifact@v4\n        with:\n          name: arduino-cli-esp32-report-shard${{ matrix.shard }}\n          path: build-report-esp32-arduino-cli-shard${{ matrix.shard }}.txt\n\n  report:\n    name: ESP32 (arduino-cli) - Final report\n    runs-on: ubuntu-latest\n    needs: arduino-esp32\n    if: always() && needs.arduino-esp32.result != 'skipped'\n    steps:\n      - name: Download all reports\n        uses: actions/download-artifact@v4\n        with:\n          pattern: arduino-cli-esp32-report-*\n          merge-multiple: true\n          path: reports\n\n      - name: Print final report and set status\n        run: |\n          set -uo pipefail\n          echo \"==================== Final build report (ESP32/arduino-cli) ====================\"\n          failures=0\n          files=0\n          shopt -s nullglob\n          for f in reports/*.txt; do\n            files=$((files + 1))\n            echo \"--- ${f} ---\"\n            cat \"$f\"\n            if grep -q \" : FAIL\" \"$f\"; then\n              failures=$((failures + 1))\n            fi\n          done\n          if [[ $files -eq 0 ]]; then\n            echo \"No report files found (artifact download failed?)\"\n            exit 1\n          fi\n          echo \"===========================================================================\"\n          if [[ $failures -gt 0 ]]; then\n            echo \"One or more shards reported FAIL.\"\n            exit 1\n          fi\n\n"
  },
  {
    "path": ".github/workflows/cli-build-esp8266.yml",
    "content": "# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json\n\nname: (ESP8266) Build with Arduino CLI\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - dev\n  pull_request:\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  arduino-esp8266:\n    name: ESP8266 (arduino-cli) (shard=${{ matrix.shard }})\n    if: github.event_name == 'workflow_dispatch' || vars.ENABLE_BUILDS == 'true'\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        shard: [1, 2, 3, 4]\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n\n      - name: Install arduino-cli\n        run: curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=/usr/local/bin sh\n\n      - name: Cache Arduino CLI data\n        uses: actions/cache@v4\n        with:\n          key: ${{ runner.os }}-arduino-esp8266-${{ hashFiles('.github/workflows/cli-build-esp8266.yml') }}-${{ github.run_id }}-${{ matrix.shard }}\n          restore-keys: |\n            ${{ runner.os }}-arduino-esp8266-${{ hashFiles('.github/workflows/cli-build-esp8266.yml') }}-\n            ${{ runner.os }}-arduino-esp8266-\n          path: |\n            ~/.arduino15\n            ~/Arduino\n            ~/.cache/arduino-cli\n\n      - name: Update core index\n        run: arduino-cli core update-index --additional-urls https://arduino.esp8266.com/stable/package_esp8266com_index.json\n\n      - name: Install core\n        run: arduino-cli core install --additional-urls https://arduino.esp8266.com/stable/package_esp8266com_index.json esp8266:esp8266\n\n      - name: Install ArduinoJson\n        run: arduino-cli lib install ArduinoJson\n\n      - name: Build Examples\n        run: |\n          set -uo pipefail\n\n          total_shards=4\n          shard=\"${{ matrix.shard }}\"\n\n          # Examples not intended for ESP8266 / this job\n          EXCLUDE_EXAMPLES=(\n            esp32-cam\n            binaryWebSocket\n            csvLoggerSD\n            localRFID\n            mysqlRFID\n            mqtt_webserver\n          )\n\n          report_file=\"build-report-esp8266-arduino-cli-shard${{ matrix.shard }}.txt\"\n          : > \"$report_file\"\n\n          failures=0\n          total=0\n          index=0\n\n          for dir in examples/*; do\n            if [[ ! -d \"$dir\" ]]; then\n              continue\n            fi\n\n            example=\"$(basename \"$dir\")\"\n\n            if [[ \" ${EXCLUDE_EXAMPLES[*]} \" == *\" ${example} \"* ]]; then\n              echo \"Skipping: ${example}\"\n              printf '%-25s : SKIP\\n' \"${example}\" >> \"$report_file\"\n              continue\n            fi\n\n            # Split examples across shards for faster CI.\n            index=$((index + 1))\n            example_shard=$(( (index - 1) % total_shards + 1 ))\n            if [[ \"$example_shard\" != \"$shard\" ]]; then\n              continue\n            fi\n\n            sketch=\"examples/${example}/${example}.ino\"\n            total=$((total + 1))\n\n            echo \"=============================================================\"\n            echo \"Building ${sketch}...\"\n            echo \"=============================================================\"\n\n            if [[ ! -f \"$sketch\" ]]; then\n              failures=$((failures + 1))\n              printf '%-25s : FAIL (missing %s)\\n' \"${example}\" \"${sketch}\" >> \"$report_file\"\n              continue\n            fi\n\n            echo \"::group::arduino-cli compile - ${example}\"\n            set +e\n            arduino-cli compile \\\n              --library . \\\n              --warnings none \\\n              --build-cache-path \"$HOME/.cache/arduino-cli\" \\\n              -b esp8266:esp8266:huzzah \\\n              \"$sketch\"\n            rc=$?\n            set -e\n            echo \"::endgroup::\"\n\n            if [[ $rc -eq 0 ]]; then\n              printf '%-25s : OK\\n' \"${example}\" >> \"$report_file\"\n            else\n              failures=$((failures + 1))\n              printf '%-25s : FAIL (exit=%s)\\n' \"${example}\" \"$rc\" >> \"$report_file\"\n            fi\n          done\n\n          echo \"\"\n          echo \"==================== Build report (esp8266/arduino-cli) ====================\"\n          cat \"$report_file\"\n          echo \"=============================================================================\"\n          echo \"Total attempted: ${total} | Failures: ${failures}\"\n\n          # Do not fail the job here: the final report job will decide.\n\n      - name: Upload build report\n        if: always()\n        uses: actions/upload-artifact@v4\n        with:\n          name: arduino-cli-esp8266-report-shard${{ matrix.shard }}\n          path: build-report-esp8266-arduino-cli-shard${{ matrix.shard }}.txt\n\n  report:\n    name: ESP8266 (arduino-cli) - Final report\n    runs-on: ubuntu-latest\n    needs: arduino-esp8266\n    if: always() && needs.arduino-esp8266.result != 'skipped'\n    steps:\n      - name: Download all reports\n        uses: actions/download-artifact@v4\n        with:\n          pattern: arduino-cli-esp8266-report-*\n          merge-multiple: true\n          path: reports\n\n      - name: Print final report and set status\n        run: |\n          set -uo pipefail\n          echo \"==================== Final build report (ESP8266/arduino-cli) ====================\"\n          failures=0\n          files=0\n          shopt -s nullglob\n          for f in reports/*.txt; do\n            files=$((files + 1))\n            echo \"--- ${f} ---\"\n            cat \"$f\"\n            if grep -q \" : FAIL\" \"$f\"; then\n              failures=$((failures + 1))\n            fi\n          done\n          if [[ $files -eq 0 ]]; then\n            echo \"No report files found (artifact download failed?)\"\n            exit 1\n          fi\n          echo \"===========================================================================\"\n          if [[ $failures -gt 0 ]]; then\n            echo \"One or more shards reported FAIL.\"\n            exit 1\n          fi\n"
  },
  {
    "path": ".github/workflows/pio-build-esp32-dev.yml",
    "content": "# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json\n\nname: (ESP32) Build dev with PlatformIO\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - dev\n      \n  pull_request:\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  # Build with PlatformIO - ESP32 Arduino Latest\n\n  platformio-esp32-arduino-latest:\n    name: ESP32 (pio) - Arduino Latest (board=${{ matrix.board }}, shard=${{ matrix.shard }})\n    if: github.event_name == 'workflow_dispatch' || vars.ENABLE_BUILDS == 'true'\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        board:\n          - esp32dev\n          - esp32-s3-devkitc-1\n        shard: [1, 2, 3, 4]\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n\n      - name: Cache PlatformIO\n        uses: actions/cache@v4\n        with:\n          key: ${{ runner.os }}-pio\n          path: |\n            ~/.cache/pip\n            ~/.platformio\n\n      - name: Python\n        uses: actions/setup-python@v5\n        with:\n          python-version: \"3.13\"\n\n      - name: Install PIO\n        run: |\n          python -m pip install --upgrade pip\n          pip install --upgrade platformio\n\n      - name: Build Examples\n        run: |\n          set -uo pipefail\n\n          total_shards=4\n          shard=\"${{ matrix.shard }}\"\n\n          report_file=\"build-report-${{ matrix.board }}-shard${{ matrix.shard }}.txt\"\n          : > \"$report_file\"\n\n          failures=0\n          total=0\n          index=0\n\n          for dir in examples/*; do\n            if [[ ! -d \"$dir\" ]]; then\n              continue\n            fi\n\n            example=\"$(basename \"$dir\")\"\n            board_to_use=\"${{ matrix.board }}\"\n\n            # Split examples across shards for faster CI.\n            index=$((index + 1))\n            example_shard=$(( (index - 1) % total_shards + 1 ))\n            if [[ \"$example_shard\" != \"$shard\" ]]; then\n              continue\n            fi\n\n            # Avoid duplicating esp32-cam across the board matrix.\n            if [[ \"$example\" == \"esp32-cam\" && \"${{ matrix.board }}\" != \"esp32dev\" ]]; then\n              printf '%-25s : SKIP (board-specific)\\n' \"${example}\" >> \"$report_file\"\n              continue\n            fi\n\n            # Some examples are board-specific\n            if [[ \"$example\" == \"esp32-cam\" ]]; then\n              board_to_use=\"esp32cam\"\n            fi\n\n            total=$((total + 1))\n\n            echo \"=============================================================\"\n            echo \"Building examples/${example} (board=${board_to_use})...\"\n            echo \"=============================================================\"\n\n            echo \"::group::pio run - examples/${example}\"\n            set +e\n            # Use a per-example build directory to avoid cross-example artefacts.\n            PLATFORMIO_BUILD_DIR=\".pio/build-${board_to_use}-${example}\" \\\n            PLATFORMIO_SRC_DIR=\"examples/${example}\" PIO_BOARD=\"${board_to_use}\" \\\n              pio run -e ci-arduino-3-latest\n            rc=$?\n            set -e\n            echo \"::endgroup::\"\n\n            if [[ $rc -eq 0 ]]; then\n              printf '%-25s : OK\\n' \"${example}\" >> \"$report_file\"\n            else\n              failures=$((failures + 1))\n              printf '%-25s : FAIL (exit=%s)\\n' \"${example}\" \"$rc\" >> \"$report_file\"\n            fi\n          done\n\n          echo \"\"\n          echo \"==================== Build report (board=${{ matrix.board }}) ====================\"\n          cat \"$report_file\"\n          echo \"=============================================================================\"\n          echo \"Total attempted: ${total} | Failures: ${failures}\"\n\n          # Do not fail the job here: the final report job will decide.\n\n      - name: Upload build report\n        if: always()\n        uses: actions/upload-artifact@v4\n        with:\n          name: pio-esp32-report-${{ matrix.board }}-shard${{ matrix.shard }}\n          path: build-report-${{ matrix.board }}-shard${{ matrix.shard }}.txt\n\n  report:\n    name: ESP32 (pio) - Final report\n    runs-on: ubuntu-latest\n    needs: platformio-esp32-arduino-latest\n    if: always() && needs.platformio-esp32-arduino-latest.result != 'skipped'\n    steps:\n      - name: Download all reports\n        uses: actions/download-artifact@v4\n        with:\n          pattern: pio-esp32-report-*\n          merge-multiple: true\n          path: reports\n\n      - name: Print final report and set status\n        run: |\n          set -uo pipefail\n          echo \"==================== Final build report (ESP32/pio) ====================\"\n          failures=0\n          files=0\n          shopt -s nullglob\n          for f in reports/*.txt; do\n            files=$((files + 1))\n            echo \"--- ${f} ---\"\n            cat \"$f\"\n            if grep -q \" : FAIL\" \"$f\"; then\n              failures=$((failures + 1))\n            fi\n          done\n          if [[ $files -eq 0 ]]; then\n            echo \"No report files found (artifact download failed?)\"\n            exit 1\n          fi\n          echo \"=======================================================================\"\n          if [[ $failures -gt 0 ]]; then\n            echo \"One or more shards reported FAIL.\"\n            exit 1\n          fi\n\n"
  },
  {
    "path": ".github/workflows/pio-build-esp8266.yml",
    "content": "# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json\n\nname: (ESP8266) Build with PlatformIO\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - dev\n  pull_request:\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  platformio-esp8266:\n    name: ESP8266 (pio) (board=${{ matrix.board }}, shard=${{ matrix.shard }})\n    if: github.event_name == 'workflow_dispatch' || vars.ENABLE_BUILDS == 'true'\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        board:\n          - d1_mini\n        shard: [1, 2, 3, 4]\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n\n      - name: Cache PlatformIO\n        uses: actions/cache@v4\n        with:\n          key: ${{ runner.os }}-pio\n          path: |\n            ~/.cache/pip\n            ~/.platformio\n\n      - name: Python\n        uses: actions/setup-python@v5\n        with:\n          python-version: \"3.x\"\n\n      - name: Install PIO\n        run: |\n          python -m pip install --upgrade pip\n          pip install --upgrade platformio\n\n      - name: Build Examples\n        run: |\n          set -uo pipefail\n\n          total_shards=4\n          shard=\"${{ matrix.shard }}\"\n\n          # Examples not intended for ESP8266 / this job\n          EXCLUDE_EXAMPLES=(\n            esp32-cam\n            binaryWebSocket\n            csvLoggerSD\n            localRFID\n            mysqlRFID\n            mqtt_webserver\n          )\n\n          report_file=\"build-report-esp8266-pio-${{ matrix.board }}-shard${{ matrix.shard }}.txt\"\n          : > \"$report_file\"\n\n          failures=0\n          total=0\n          index=0\n\n          for dir in examples/*; do\n            if [[ ! -d \"$dir\" ]]; then\n              continue\n            fi\n\n            example=\"$(basename \"$dir\")\"\n\n            if [[ \" ${EXCLUDE_EXAMPLES[*]} \" == *\" ${example} \"* ]]; then\n              echo \"Skipping: ${example}\"\n              printf '%-25s : SKIP\\n' \"${example}\" >> \"$report_file\"\n              continue\n            fi\n\n            # Split examples across shards for faster CI.\n            index=$((index + 1))\n            example_shard=$(( (index - 1) % total_shards + 1 ))\n            if [[ \"$example_shard\" != \"$shard\" ]]; then\n              continue\n            fi\n\n            total=$((total + 1))\n\n            echo \"=============================================================\"\n            echo \"Building examples/${example} (board=${{ matrix.board }}, shard=${shard})...\"\n            echo \"=============================================================\"\n\n            echo \"::group::pio run - examples/${example}\"\n            set +e\n            # Use a per-example build directory to avoid cross-example artefacts.\n            PLATFORMIO_BUILD_DIR=\".pio/build-${{ matrix.board }}-${example}\" \\\n            PLATFORMIO_SRC_DIR=\"examples/${example}\" PIO_BOARD=\"${{ matrix.board }}\" \\\n              pio run -e ci-esp8266\n            rc=$?\n            set -e\n            echo \"::endgroup::\"\n\n            if [[ $rc -eq 0 ]]; then\n              printf '%-25s : OK\\n' \"${example}\" >> \"$report_file\"\n            else\n              failures=$((failures + 1))\n              printf '%-25s : FAIL (exit=%s)\\n' \"${example}\" \"$rc\" >> \"$report_file\"\n            fi\n          done\n\n          echo \"\"\n          echo \"==================== Build report (esp8266/pio, board=${{ matrix.board }}, shard=${{ matrix.shard }}) ====================\"\n          cat \"$report_file\"\n          echo \"===============================================================================================\"\n          echo \"Total attempted: ${total} | Failures: ${failures}\"\n\n          # Do not fail the job here: the final report job will decide.\n\n      - name: Upload build report\n        if: always()\n        uses: actions/upload-artifact@v4\n        with:\n          name: pio-esp8266-report-${{ matrix.board }}-shard${{ matrix.shard }}\n          path: build-report-esp8266-pio-${{ matrix.board }}-shard${{ matrix.shard }}.txt\n\n  report:\n    name: ESP8266 (pio) - Final report\n    runs-on: ubuntu-latest\n    needs: platformio-esp8266\n    if: always() && needs.platformio-esp8266.result != 'skipped'\n    steps:\n      - name: Download all reports\n        uses: actions/download-artifact@v4\n        with:\n          pattern: pio-esp8266-report-*\n          merge-multiple: true\n          path: reports\n\n      - name: Print final report and set status\n        run: |\n          set -uo pipefail\n          echo \"==================== Final build report (ESP8266/pio) ====================\"\n          failures=0\n          files=0\n          shopt -s nullglob\n          for f in reports/*.txt; do\n            files=$((files + 1))\n            echo \"--- ${f} ---\"\n            cat \"$f\"\n            if grep -q \" : FAIL\" \"$f\"; then\n              failures=$((failures + 1))\n            fi\n          done\n          if [[ $files -eq 0 ]]; then\n            echo \"No report files found (artifact download failed?)\"\n            exit 1\n          fi\n          echo \"===========================================================================\"\n          if [[ $failures -gt 0 ]]; then\n            echo \"One or more shards reported FAIL.\"\n            exit 1\n          fi\n\n"
  },
  {
    "path": ".gitignore",
    "content": "# 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 libraries\n*.so\n*.dylib\n*.dll\n\n# Fortran module files\n*.mod\n*.smod\n\n# Compiled Static libraries\n*.lai\n*.la\n*.a\n*.lib\n\n# Executables\n*.exe\n*.out\n*.app\n\n# PlatformIO in VScode\n.pio/\n.vscode/\n*.lnk\n*.bak\n/build\n/built-in-webpages/setup/build_setup/node_modules\n/examples/customHTML/build\n/pio_examples/temp\n/pio_examples/simpleServer/setup/build_setup/node_modules\n/built-in-webpages/edit/build_edit/node_modules\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# ESP-FS-WebServer\n\nA 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.\n\n![WiFi Manager](docs/wifi_manager.png)\n\n## Features\n\n-   **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).\n-   **Powerful & Customizable UI**:\n    -   Easily add your own configuration parameters (text boxes, checkboxes, sliders, dropdown lists) to the setup page.\n    -   Inject custom **HTML, CSS, and JavaScript** snippets into the setup page to create rich, dynamic user interfaces for your specific project needs.\n-   **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.\n-   **WebSocket Support**: Built-in support for real-time, two-way communication between the web client and the ESP.\n-   **Advanced File Management**: An embedded file manager (`/edit`) allows you to browse, view, upload, and delete files and folders.\n\nBuilt-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.\n\n## Documentation\n\nFor more detailed information, please refer to the documentation in the `docs` folder:\n\n-   **[API Reference](docs/API.md)** – Detailed overview of the public methods.\n-   **[Setup and WiFi](docs/SetupAndWiFi.md)** – Guide to `startWiFi()`, captive portal, and the `/setup` page.\n-   **[Filesystem and Editor](docs/FileEditorAndFS.md)** – How to serve static files and use the `/edit` page.\n-   **[WebSocket](docs/WebSocket.md)** – Information on using the WebSocket server.\n-   **[Password Encryption](docs/pwd_encrypt.md)** – Information on how passwords are managed.\n\n## Dependencies\n\n-   ESP8266/ESP32 Core for Arduino\n\n## Basic Usage\n\n```cpp\n#include <FS.h>\n#include <LittleFS.h>\n#if defined(ESP8266)\n#include <ESP8266WiFi.h>\n#else\n#include <WiFi.h>\n#endif\n#include \"FSWebServer.h\"\n\n// Use LittleFS\nfs::FS& filesystem = LittleFS;\n\nFSWebServer server(80, filesystem, \"esphost\");\n\nvoid setup() {\n  Serial.begin(115200);\n\n  // Initialize Filesystem\n  if (!filesystem.begin()) {\n    Serial.println(\"Error mounting file system.\");\n    return;\n  }\n\n  // Start WiFi (or captive portal if no credentials)\n  server.startWiFi(10000);\n\n  // Add a handler for the root page\n  server.on(\"/\", []() {\n    server.send(200, \"text/html\", \"<h1>Hello from FSWebServer!</h1>\");\n  });\n\n  // Start the server\n  server.begin();\n  Serial.println(\"HTTP server started.\");\n}\n\nvoid loop() {\n  server.run();\n}\n\n```\n\n## WiFi Management Enhancements\n\n### Dedicated WiFiService class\n- `WiFiService` centralizes scanning, connections, captive portal setup, MDNS, and watchdog handling so the sketch can stay focused on application logic.\n- 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.\n- `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.\n\n### Setup page reconnection experience\n- 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.\n- 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.\n- 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.\n\n\n### Custom application \"options manager\"\n![options](docs/options_manager.png)\n\n### OTA update and `data` folder upload\n![OTA](docs/ota_data.png)\n\n### Custom 'code snippets'\n![snippet](docs/custom_html.png)\n\n### ACE file editor\n![File Manager](docs/file_manager.png)\n"
  },
  {
    "path": "built-in-webpages/readme.md",
    "content": "The canonical built-in page sources are no longer maintained in this folder.\n\nUse the shared repository at `C:\\Cloud\\fs-webserver-shared-pages` as the single source of truth for both **AsyncFsWebServer** and **FSWebServer** built-in pages.\n\nCurrent workflow:\n* edit `/setup` sources in `C:\\Cloud\\fs-webserver-shared-pages\\setup`\n* edit `/edit` sources in `C:\\Cloud\\fs-webserver-shared-pages\\edit`\n* open a terminal in `C:\\Cloud\\fs-webserver-shared-pages`\n* run `npm install` once\n* run `npm run build` to regenerate `setup_htm.h`, `logo_svg.h`, and `edit_htm.h` in both library `src/assets` folders\n\nTarget resolution order in the shared repo:\n* `--target <path>` CLI arguments\n* `targets.json`\n* autodiscovery of the Arduino sketchbook `libraries` folder\n\nThis folder is kept only as historical reference and should not be used as the primary build entrypoint anymore."
  },
  {
    "path": "docs/API.md",
    "content": "# FSWebServer – API (overview)\n\nThis page summarizes the main methods exposed by `FSWebServer` and when to use them.\n\n> Note: some APIs are available only when the related features are enabled via macros (e.g. `ESP_FS_WS_SETUP`, `ESP_FS_WS_EDIT`).\n\n## Constructor\n\n```cpp\nFSWebServer(uint16_t port, fs::FS &fs, const char* hostname = \"\");\n```\n\n- `port`: HTTP port (typically `80`)\n- `fs`: filesystem (`LittleFS`, `SPIFFS`, `FFat`, …)\n- `hostname`: optional (also used for mDNS)\n\n## Server start\n\n```cpp\nvoid begin(WebSocketsServer::WebSocketServerEvent wsEventHandler = nullptr);\n```\n\n- Registers built-in handlers (setup/edit if enabled), static file serving, and notFound.\n- If `wsEventHandler != nullptr`, creates and starts a websocket server.\n- Starts the webserver.\n\n## Runtime info\n\n```cpp\nIPAddress getServerIP();\nbool isAccessPointMode() const;\n```\n\n## Authentication (/setup page)\n\n```cpp\nvoid setAuthentication(const char* user, const char* pswd);\n```\n\nWhen set, the `/setup` page requires basic-auth.\n\n## Filesystem listing\n\n```cpp\nvoid printFileList(fs::FS &fs, const char * dirname, uint8_t levels);\nvoid printFileList(fs::FS &fs, const char * dirname, uint8_t levels, Print& out);\n```\n\n- The `Print& out` overload lets you send output to streams other than `Serial`.\n\nExample:\n\n```cpp\nserver.printFileList(FILESYSTEM, \"/\", 1);           // default -> Serial\nserver.printFileList(FILESYSTEM, \"/\", 1, Serial);  // explicit\n```\n\n## WiFi + captive portal\n\n```cpp\nbool startWiFi(uint32_t timeout, CallbackF fn = nullptr);\nbool startCaptivePortal(const char* ssid, const char* pass, const char* redirectTargetURL);\n```\n\n- `startWiFi()` tries to connect using already-saved credentials.\n- If it fails, you typically start `startCaptivePortal()` and use `/setup` to configure.\n\n## WebSocket (runtime)\n\n```cpp\nWebSocketsServer* getWebSocketServer();\nbool broadcastWebSocket(const String &payload);\nbool broadcastWebSocket(const uint8_t *payload, size_t length);\nbool sendWebSocket(uint8_t num, const String &payload);\n```\n\n## Setup page (only if `ESP_FS_WS_SETUP`)\n\nConfig file and callback:\n\n```cpp\nFile getConfigFile(const char* mode);\nconst char* getConfiFileName();\nbool clearConfigFile();\nvoid setConfigSavedCallback(ConfigSavedCallbackF callback);\n```\n\nOptions and setup UI:\n\n```cpp\nvoid setSetupPageTitle(const char* title);\nvoid addOptionBox(const char* title);\n\n// attach a comment string to an existing option element\n// For most controls the comment is rendered in a separate line under the input.\n// Boolean (checkbox) fields use a <span> so the text stays on the same line.\nvoid addComment(const char *lbl, const char *comment);\n\n// boolean option: grouped by default, pass fourth argument false to keep declaration order\n// the third parameter specifies `hidden` exactly like the generic template\nvoid addOption(const char *lbl, bool val, bool hidden = false, bool grouped = true);\n\n// boolean overload that accepts a comment\nvoid addOption(const char *lbl, bool val, const char *comment,\n               bool hidden = false, bool grouped = true);\n\n// generic templated version (bool excluded via SFINAE below)\ntemplate <typename T>\nvoid addOption(const char *lbl, T val, bool hidden=false, double min=MIN_F, double max=MAX_F, double st=1.0);\n\n// convenience comment overload for non-bool types\n// use the bool-specific variant to add a comment to a checkbox\ntemplate <typename T, typename std::enable_if<!std::is_same<T,bool>::value, int>::type = 0>\nvoid addOption(const char *lbl, T val, const char *comment);\n\ntemplate <typename T>\nvoid addOption(const char *lbl, T val, bool hidden=false, double min=MIN_F, double max=MAX_F, double st=1.0);\n\ntemplate <typename T>\nbool getOptionValue(const char *lbl, T &var);\n\ntemplate <typename T>\nbool saveOptionValue(const char *lbl, T val);\n```\n\nBoolean 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.\n\nDropdown/Slider:\n\n```cpp\nusing DropdownList = FSWebServer::DropdownList;\nusing Slider = FSWebServer::Slider;\n\nvoid addDropdownList(DropdownList &def);\nvoid addSlider(Slider &def);\nbool getDropdownSelection(DropdownList &def);\nbool getSliderValue(Slider &def);\n```\n\n## Web file editor (only if `ESP_FS_WS_EDIT`)\n\n```cpp\nvoid enableFsCodeEditor(FsInfoCallbackF fsCallback = nullptr);\nvoid setFsInfoCallback(FsInfoCallbackF fsCallback);\n```\n\nSee also: `SetupAndWiFi.md`, `FileEditorAndFS.md`, `WebSocket.md`.\n"
  },
  {
    "path": "docs/FileEditorAndFS.md",
    "content": "# Filesystem + Web File Editor (/edit)\n\nThe library serves static files from the filesystem and, optionally, includes a web editor (ACE) to manage files directly from the browser.\n\n## Static file serving\n\nBy default, files are served from the filesystem with:\n\n- root URL `/` -> filesystem path `/`\n- default file: `index.htm`\n\nIn code (internally):\n\n- `serveStatic(\"/\", fs, \"/\").setDefaultFile(\"index.htm\")`\n\n## Stampa contenuto filesystem (debug)\n## Print filesystem contents (debug)\n\n```cpp\nserver.printFileList(FILESYSTEM, \"/\", 1);           // default -> Serial\nserver.printFileList(FILESYSTEM, \"/\", 1, Serial);  // or to a chosen stream\n```\n\nThe `Print&` overload is useful to log to streams other than `Serial`.\n\n## Enable /edit\n\n```cpp\nserver.enableFsCodeEditor();\n```\n\nMain endpoints:\n- `GET /edit` editor page\n- `GET /list?dir=/` directory listing\n- `POST /edit` upload\n- `PUT /edit` create/rename\n- `DELETE /edit` delete\n\n## Provide filesystem info (recommended on ESP32)\n\nOn ESP32, to show correct “total/used bytes” in the UI:\n\n```cpp\nserver.setFsInfoCallback([](fsInfo_t* fsInfo) {\n  fsInfo->fsName = \"LittleFS\";\n  fsInfo->totalBytes = LittleFS.totalBytes();\n  fsInfo->usedBytes = LittleFS.usedBytes();\n});\n```\n"
  },
  {
    "path": "docs/SetupAndWiFi.md",
    "content": "# Setup + WiFi (startWiFi / captive portal / config)\n\nThis library can:\n- try to connect to a previously saved WiFi network (STA)\n- if it fails, start an Access Point + captive portal and serve `/setup`\n- persist **application options** into `config.json` on the filesystem  \n  (WiFi credentials are now handled separately by `CredentialManager` and **never** stored in `config.json`)\n\n## Typical flow\n\n```cpp\nif (!server.startWiFi(10000)) {\n  server.startCaptivePortal(\"ESP_AP\", \"123456789\", \"/setup\");  \n}\nserver.init(onWsEvent);\n```\n\n1. `startWiFi(timeout)` attempts to connect using saved credentials.\n2. If it fails, `startCaptivePortal(ssid, pass, \"/setup\")` switches the ESP to AP mode and redirects requests to `/setup` (or a custom endpoint)\n3. On `/setup` the user selects the WiFi network (managed by `CredentialManager`) and any extra application options;  \n  the library saves **only the application options** to `config.json` and stores WiFi credentials in encrypted form via `CredentialManager`.\n\nIt's possible also change the setting of IP address and mask for the captive portal passing a `WiFiConnectParams` structs to `startCaptivePortal()` method.\n```cpp\nif (!server.startWiFi(10000)) {\n  Serial.println(\"\\nWiFi not connected! Starting AP mode...\");\n  WiFiConnectParams params (\"ESP_AP\", \"123456789\");            \n  params.config.local_ip = IPAddress(192, 168, 1, 1);\n  params.config.gateway = IPAddress(192, 168, 1, 1);\n  params.config.subnet = IPAddress(255, 255, 255, 0);    \n  server.startCaptivePortal(params, \"/setup\");\n}\n```\n## Application options on /setup\n\nExample (see also `withWebSocket.ino`):\n\n```cpp\nserver.addOptionBox(\"My Options\");\nserver.addOption(\"LED Pin\", ledPin);\nserver.addOption(\"Option 1\", option1.c_str());\nserver.addOption(\"Option 2\", option2);\n// you can also add a comment for a control that will appear beneath it\nserver.addComment(\"Option 2\", \"Explanation or hint text goes here\");\n```\n\nRead your application options at boot (from `config.json`, WiFi excluded):\n\n```cpp\nuint32_t option2;\nserver.getOptionValue(\"Option 2\", option2);\n```\n\n## Config file: read/write\n\n- Full path: `server.getConfiFileName()`\n- File access: `server.getConfigFile(\"r\")` / `server.getConfigFile(\"w\")`\n- Reset config: `server.clearConfigFile()`\n\n“Config saved” callback (useful when saving via `/edit` or upload):\n\n```cpp\nserver.setConfigSavedCallback([](const char* filename){\n  Serial.printf(\"Config saved: %s\\n\", filename);\n});\n```\n\n## WiFi credentials storage (CredentialManager)\n\n- WiFi SSID, password, DHCP/static IP and related data are **not** stored in `config.json`.\n- They are managed and stored (encrypted) by the internal `CredentialManager`:\n  - ESP32: NVS\n  - ESP8266: filesystem (e.g. LittleFS)\n- The `/setup` page talks directly with the WiFi APIs (`/wifi/credentials`, `/connect`, etc.),  \n  so your sketch usually does **not** need to read or write WiFi data manually.\n\nSee also: `pwd_encrypt.md` for a deeper overview of `CredentialManager` and encrypted WiFi storage.\n\n## Protect /setup with basic-auth\n\n```cpp\nserver.setAuthentication(\"admin\", \"admin\");\n```\n\nWhen set, the `/setup` page requires authentication.\n"
  },
  {
    "path": "docs/WebSocket.md",
    "content": "# WebSocket Support\n\nThe library includes and uses [WebSockets](https://github.com/Links2004/arduino-WebSockets) by Markus Sattler for bidirectional communication with web clients.\n\nSupport is enabled by default via the `ESP_FS_WS_WEBSOCKET` macro.\n\n## 1. Create the Event Handler\n\nFirst, you need to define a function that will handle WebSocket events. This function must match the `WebSocketServerEvent` signature.\n\n```cpp\n// WebSocket event handler\nvoid webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {\n  switch (type) {\n    case WStype_DISCONNECTED:\n      Serial.printf(\"[%u] Disconnected!\\n\", num);\n      break;\n    case WStype_CONNECTED:\n      {\n        IPAddress ip = server.getWebSocketServer()->remoteIP(num);\n        Serial.printf(\"[%u] Connected from %d.%d.%d.%d url: %s\\n\", num, ip[0], ip[1], ip[2], ip[3], payload);\n\n        // Send a welcome message to the newly connected client\n        server.getWebSocketServer()->sendTXT(num, \"Hello from FSWebServer!\");\n      }\n      break;\n    case WStype_TEXT:\n      Serial.printf(\"[%u] Received text: %s\\n\", num, payload);\n\n      // Echo the message back to the client\n      server.getWebSocketServer()->sendTXT(num, payload);\n      break;\n    case WStype_BIN:\n      Serial.printf(\"[%u] Received binary data of length %u\\n\", num, length);\n      // hexdump(payload, length); // Example for printing binary data\n      break;\n    default:\n      Serial.printf(\"Unhandled event type: %d\\n\", type);\n      break;\n  }\n}\n```\n- `num`: The client ID (an integer from 0 up to the maximum number of clients).\n- `type`: The type of event (e.g., `WStype_CONNECTED`, `WStype_DISCONNECTED`, `WStype_TEXT`).\n- `payload`: A pointer to the received data.\n- `length`: The length of the received data.\n\n## 2. Start the Server with the Handler\n\nPass your event handler function to the `server.begin()` method. This will automatically start the WebSocket server.\n\n```cpp\nvoid setup() {\n  // ... other setup code ...\n\n  // Start the web server and enable WebSockets\n  server.begin(webSocketEvent);\n}\n```\n\n## 3. Sending Messages from the ESP\n\nYou have two ways to send messages to clients.\n\n### Simple Broadcast (Recommended)\n\nUse the helper methods built into `FSWebServer` to easily broadcast messages to all connected clients.\n\n```cpp\n// Broadcast a text message\nserver.broadcastWebSocket(\"This is a message for everyone.\");\n\n// Broadcast binary data\nuint8_t binaryPayload[] = {0xDE, 0xAD, 0xBE, 0xEF};\nserver.broadcastWebSocket(binaryPayload, sizeof(binaryPayload));\n```\n\n### Advanced Control\n\nFor more advanced scenarios, like sending a message to a specific client, you can get a pointer to the underlying `WebSocketsServer` object.\n\n```cpp\n// Get the WebSocket server instance\nWebSocketsServer* ws = server.getWebSocketServer();\n\nif (ws) {\n  // Send a text message to client number 2\n  ws->sendTXT(2, \"This is a private message for you.\");\n\n  // Disconnect client number 3\n  ws->disconnect(3);\n}\n```\nThis gives you full access to the underlying WebSocket library's API.\n"
  },
  {
    "path": "docs/pwd_encrypt.md",
    "content": "# FSWebServer CredentialManager Integration \n\n✅ **Encrypted Password Storage** - AES-256-CBC encryption\n✅ **Automatic Persistence** - NVS (ESP32) or Filesystem (ESP8266)  \n✅ **Multi-SSID Support** - Store up to 5 WiFi networks (configurable)\n✅ **FIFO Management** - Automatically remove oldest when full\n✅ **RSSI-Based Selection** - Connect to strongest signal\n✅ **Zero Configuration** - Works transparently with FSWebServer\n✅ **Cross-Platform** - ESP32 and ESP8266 compatible\n\n\n## Quick Start\n\n### Set Encryption Key\n```cpp\n// In CredentialManager.h, change this:\n#define CREDENTIAL_MANAGER_ENCRYPTION_KEY \"YOUR_SECRET_KEY_16_CHARS!\"\n// To something like:\n#define CREDENTIAL_MANAGER_ENCRYPTION_KEY \"MyCompany2024Key!\"\n```\n\nThen:\n1. Open `/setup` to add WiFi credentials\n2. Reboot device\n3. Check serial logs for RSSI-based connection\n\n---\n\n## Architecture Overview\n\n```\n┌─────────────────────────────────────────────────────┐\n│              User's Arduino Sketch                  │\n│                                                     │\n│  - Calls: server.begin()                            │\n│  - Calls: server.run()                              │\n│  - No credential management code needed             │\n└─────────────────────────┬───────────────────────────┘\n                          │\n                          ▼\n┌─────────────────────────────────────────────────────┐\n│         Modified FSWebServer Library                │\n│                                                     │\n│  • Constructor: Load credentials from storage       │\n│  • startWiFi(): RSSI-based WiFi selection           │\n│  • doWifiConnection(): FIFO credential mgmt         │  \n└─────────────────────────┬───────────────────────────┘\n                          │\n                          ▼\n┌─────────────────────────────────────────────────────┐\n│       CredentialManager (New Class)                 │\n│                                                     │\n│  • AES-256-CBC encryption/decryption                │\n│  • NVS persistence (ESP32)                          │\n│  • Filesystem persistence (ESP8266)                 │\n│  • Credential list management                       │\n│  • FIFO removal when full                           │\n└─────────────────────────┬───────────────────────────┘\n                          │\n            ┌─────────────┴─────────────┐\n            ▼                           ▼\n      ┌──────────────┐         ┌──────────────────┐\n      │  NVS Flash   │         │  Filesystem      │\n      │  (ESP32)     │         │  (ESP8266/ESP32) │\n      │  Encrypted   │         │  Encrypted       │\n      │  Passwords   │         │  Passwords       │\n      └──────────────┘         └──────────────────┘\n```\n\n---\n\n## Key Features Explained\n\n### 1. Transparent Credential Management\n- User adds credentials via `/setup` web page\n- Credentials automatically saved to encrypted storage\n- No code changes needed in user sketch\n- Credentials persist across power cycles\n\n### 2. FIFO (First In, First Out) Management\n```\nWhen list is full (5 credentials):\nAdd new credential → Automatically remove oldest → Keep 5 total\n```\n\n### 3. RSSI-Based WiFi Selection\n```\nOn startup:\n1. Scan available networks\n2. Find stored credentials that match\n3. Select the one with best (highest) signal strength\n4. Connect to that network\n```\n\n**Example**:\n- HomeWiFi: -65 dBm (good)\n- OfficeWiFi: -42 dBm (excellent)  \n→ Connect to OfficeWiFi (higher value = better signal)\n\n### 4. Encryption\n```\nUser input: \"MyPassword123\"\n     ↓ (AES-256-CBC encrypt)\nStored: \"aX7k3jQ9pL2nM8vX...\"\n     ↓ (AES-256-CBC decrypt)\nRetrieved: \"MyPassword123\"\n```\n\n### 5. Cross-Platform Support\n```\nESP32: Uses NVS (secure, encrypted by hardware)\nESP8266: Uses LittleFS/SPIFFS (plaintext storage file)\nBoth: Passwords encrypted by CredentialManager\n```\n\n## Security Considerations\n\n### Password Encryption\n- **Algorithm**: AES-256-CBC (256-bit encryption)\n- **Padding**: PKCS7 (standard padding)\n- **Mode**: Cipher Block Chaining (secure against pattern attacks)\n\n### Encryption Key Storage\n- **ESP32**: Ideally in eFuse BLOCK_KEY0 (hardware protected)\n  ```bash\n  # Generate and set key:\n  espefuse.py key_set BLOCK_KEY0 <32-byte-hex-key>\n  ```\n\n- **ESP8266**: Compile-time constant (less secure)\n  ```cpp\n  #define CREDENTIAL_MANAGER_ENCRYPTION_KEY \"MySecretKey12345\"\n  ```\n\n### Additional Security Measures\n- Use **Secure Boot** to prevent firmware tampering\n- Use **eFuse** to lock down critical settings\n- Keep encryption key confidential and consistent\n- Change key only if you can re-encrypt stored credentials\n\n### Threat Model\n- **Protected Against**: Flash dump reading (passwords encrypted)\n- **Protected Against**: Casual inspection of storage\n- **Not Protected Against**: Brute-force attacks on weak keys\n- **Not Protected Against**: Side-channel attacks (e.g., timing)\n\n---\n\n## Customization Options\n\n### Change Maximum Credentials\nIn FSWebServer.h:\n```cpp\nstatic constexpr uint8_t MAX_CREDENTIALS = 5;  // Change to 10 for more\n```\n\n### Change Encryption Key\nIn CredentialManager.h:\n```cpp\n#define CREDENTIAL_MANAGER_ENCRYPTION_KEY \"YourCustomKey12!\"\n```\n\n---\n\n## Performance\n\n### Startup Time Impact\n- WiFi scan: 500-2000ms (depends on RF environment)\n- Credential matching: 10-50ms\n- Connection: 2-5 seconds\n- **Total**: ~3-7 seconds (typical)\n\n### Memory Usage\n- CredentialManager class: ~500 bytes\n- Per credential: ~100 bytes\n- Vector overhead: ~20 bytes\n- **Total for 5 credentials**: ~1.2 KB\n\n### Storage Usage\n- Per credential in NVS/FS: ~60 bytes encrypted\n- For 5 credentials: ~300 bytes\n- **Impact**: Negligible on ESP32 (512KB NVS)\n\n---\n\n"
  },
  {
    "path": "docs/readme.md",
    "content": "# Documentation\n\n- [API](API.md) – available methods and what they do\n- [Setup + WiFi](SetupAndWiFi.md) – `startWiFi()`, captive portal, `/setup`, config\n- [Filesystem + Editor](FileEditorAndFS.md) – static file serving, `/edit`, FS info\n- [WebSocket](WebSocket.md) – enablement, handler, broadcast\n"
  },
  {
    "path": "examples/csvLogger/.gitignore",
    "content": ".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",
    "content": "#include <Arduino.h>\n#include <FS.h>\n#include <FSWebServer.h>\n#include <LittleFS.h>\n\nFSWebServer server(LittleFS, 80, \"esphost\");\n\n// Timezone definition to get properly time from NTP server\n#define MYTZ \"CET-1CEST,M3.5.0,M10.5.0/3\"\n#include <time.h>\n\nstruct tm ntpTime;\nconst char* basePath = \"/csv\";\n\n////////////////////////////////  Filesystem\n////////////////////////////////////////////\nbool startFilesystem() {\n  if (LittleFS.begin()) {\n    server.printFileList(LittleFS, \"/\", 2, Serial);\n    return true;\n  } else {\n    Serial.println(\"ERROR on mounting filesystem. It will be formmatted!\");\n    LittleFS.format();\n    ESP.restart();\n  }\n  return false;\n}\n\n//////////////////////////// Append a row to csv file\n//////////////////////////////////////\nbool appenRow() {\n  getLocalTime(&ntpTime, 10);\n\n  char filename[32];\n  snprintf(filename, sizeof(filename), \"%s/%04d_%02d_%02d.csv\", basePath,\n           ntpTime.tm_year + 1900, ntpTime.tm_mon + 1, ntpTime.tm_mday);\n\n  File file;\n  if (LittleFS.exists(filename)) {\n    file = LittleFS.open(filename, \"a\");  // Append to existing file\n  } else {\n    file = LittleFS.open(filename, \"w\");  // Create a new file\n    file.println(\"timestamp, free heap, largest free block, connected, wifi strength\");\n  }\n\n  if (file) {\n    char timestamp[25];\n    strftime(timestamp, sizeof(timestamp), \"%c\", &ntpTime);\n\n    char row[64];\n#ifdef ESP32\n    snprintf(row, sizeof(row), \"%s, %d, %d, %s, %d\", timestamp,\n             heap_caps_get_free_size(0), heap_caps_get_largest_free_block(0),\n             (WiFi.status() == WL_CONNECTED) ? \"true\" : \"false\", WiFi.RSSI());\n#elif defined(ESP8266)\n    uint32_t free;\n    uint32_t max;\n    ESP.getHeapStats(&free, &max, nullptr);\n    snprintf(row, sizeof(row), \"%s, %d, %d, %s, %d\", timestamp, free, max,\n             (WiFi.status() == WL_CONNECTED) ? \"true\" : \"false\", WiFi.RSSI());\n#endif\n    Serial.println(row);\n    file.println(row);\n    file.close();\n    return true;\n  }\n\n  return false;\n}\n\nvoid setup() {\n  Serial.begin(115200);\n  delay(1000);\n  startFilesystem();\n\n  // Try to connect to WiFi (will start AP if not connected after timeout)\n  if (!server.startWiFi(10000)) {\n    Serial.println(\"\\nWiFi not connected! Starting AP mode...\");\n    server.startCaptivePortal(\"ESP32_LOGGER\", \"123456789\", \"/setup\");\n  }\n\n  // Enable ACE FS file web editor and add FS info callback fucntion\n  server.enableFsCodeEditor();\n\n  // Start server\n  server.begin();\n  Serial.print(F(\"Async ESP Web Server started on IP Address: \"));\n  Serial.println(server.getServerIP());\n  Serial.println(\n      F(\"This is \\\"scvLogger.ino\\\" example.\\n\"\n        \"Open /setup page to configure optional parameters.\\n\"\n        \"Open /edit page to view, edit or upload example or your custom \"\n        \"webserver source files.\"));\n\n// Set NTP servers\n#ifdef ESP8266\n  configTime(MYTZ, \"time.google.com\", \"time.windows.com\", \"pool.ntp.org\");\n#elif defined(ESP32)\n  configTzTime(MYTZ, \"time.google.com\", \"time.windows.com\", \"pool.ntp.org\");\n#endif\n  // Wait for NTP sync (with timeout)\n  getLocalTime(&ntpTime, 5000);\n\n  // Create csv logs folder if not exists\n  if (!LittleFS.exists(basePath)) {\n    LittleFS.mkdir(basePath);\n  }\n  Serial.println(\"Setup completed.\");\n}\n\nvoid loop() {\n  server.handleClient();\n  if (server.isAccessPointMode()) server.updateDNS();\n\n  static uint32_t updateTime;\n  if (millis() - updateTime > 30000) {\n    updateTime = millis();\n    appenRow();\n  }\n}\n"
  },
  {
    "path": "examples/csvLogger/data/assets/css/index.css",
    "content": "/* Misc */\nhtml, body {\n    font-family: \"Helvetica\",\"Arial\",sans-serif;\n    font-size: 14px;\n    font-weight: 400;\n    line-height: 20px;\n}\nbody {\n    margin: 0;\n    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\";\n    font-size: 1rem;\n    font-weight: 400;\n    line-height: 1.5;\n    color: #212529;\n    text-align: left;\n    background-color: #fff;\n}\n\nh1 {\n  margin: 5px;\n  text-align: center;\n}\n\np {\n  font-size: 0.9rem;\n  margin: 0.5rem 0 1.5rem 0;\n}\n\na,\na:visited {\n  color: #08C;\n  text-decoration: none;\n}\n\na:hover,\na:focus {\n  color: #69c773;\n  cursor: pointer;\n}\n\na.delete-file,\na.delete-file:visited {\n  color: #CC0000;\n  margin-left: 0.5rem;\n  vertical-align: middle;\n}\n\nbutton {\n  display: inline-block;\n  border-radius: 3px;\n  border: none;\n  font-size: 0.9rem;\n  padding: 0.5rem 1em;\n  background: #86b32d;\n  border-bottom: 1px solid #5d7d1f;\n  color: white;\n  margin: 5px 0;\n  text-align: center;\n}\n\nbutton:hover {\n  opacity: 0.75;\n  cursor: pointer;\n}\n\n#page-wrapper {\n  width: 95%;\n  background: #FFF;\n  padding: 1.25rem;\n  margin: 1rem auto;\n  min-height: 800px;\n  border-top: 5px solid #69c773;\n  box-shadow: 0 2px 10px rgba(0,0,0,0.8);\n}\n\n#content {\n  width: 85%;\n  overflow: auto;\n  height: 86vh;\n}\n\n#files {\n  width: 15%;\n}\n\n#files ul {\n  margin: 20px 0;\n  padding: 0.5rem 1rem;\n  overflow-y: auto;\n  list-style: square;\n  background: #F7F7F7;\n  border: 1px solid #D9D9D9;\n  border-radius: 5px;\n  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.1);\n  max-height: 75vh;\n}\n\n#files li {\n  margin-left: 8px;\n  font-size: 14px;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n}\n\n.container {\n  display: flex;\n  flex-direction: row;\n  align-content: flex-start;\n  justify-content: space-between;\n  align-items: flex-start;\n  column-gap: 10px;\n  height: 86vh;\n}\n\n.delete {\n  font-size: 24px;\n  transition: 0.3s;\n  margin-top:5px;\n}\n\n.delete-all{\n  color: #f44336;\n  font-size: 12px;\n  background-color: transparent;\n  background-repeat: no-repeat;\n  border: none;\n  cursor: pointer;\n  overflow: hidden;\n}\n\n/* Tables */\n.table-holder {\n    margin-top: 20px;\n    border: 1px solid lightgray;\n    border-radius: 5px;\n    border-bottom: 0px;\n    border-bottom-left-radius: 0px;\n    border-bottom-right-radius: 0px;\n}\n\n.tables {\n    margin-bottom: 50px;\n}\n\ntable {\n    width: 100%;\n    border-bottom: 0px;\n}\n\ntable, th, td {\n    border: 1px solid lightgrey;\n    border-collapse: collapse;\n    padding-left: 5px;\n}\n\ntable tr:nth-child(even) {\n    background-color: white;\n}\n\ntable tr:nth-child(odd) {\n    background-color: #f2f2f2;\n}\n\ntable th {\n    background-color: #e1e1e1;\n    color: black;\n}\n\n/* Sections */\n\n.section {\n    box-shadow: 10px 10px 10px 10px;\n    background-color: #e5e5e5;\n    padding: 10px;\n    padding-top: 20px;\n    font-size: 18px;\n}\n\n.section-lightgrey {\n    background-color: #f9f9f9;\n}\n\n/* 100% Image Width on Smaller Screens */\n@media only screen and (max-width: 700px){\n  .modal-content {\n    width: 100%;\n  }\n}\n"
  },
  {
    "path": "examples/csvLogger/data/assets/css/style.css",
    "content": "/* Misc */\nhtml, body {\n    font-family: \"Helvetica\",\"Arial\",sans-serif;\n    font-size: 14px;\n    font-weight: 400;\n    line-height: 20px;\n}\nbody {\n    margin: 0;\n    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\";\n    font-size: 1rem;\n    font-weight: 400;\n    line-height: 1.5;\n    color: #212529;\n    text-align: left;\n    background-color: #fff;\n}\n\nh1 {\n  margin: 5px;\n  text-align: center;\n}\n\np {\n  font-size: 0.9rem;\n  margin: 0.5rem 0 1.5rem 0;\n}\n\na,\na:visited {\n  color: #08C;\n  text-decoration: none;\n}\n\na:hover,\na:focus {\n  color: #69c773;\n  cursor: pointer;\n}\n\na.delete-file,\na.delete-file:visited {\n  color: #CC0000;\n  margin-left: 0.5rem;\n  vertical-align: middle;\n}\n\nbutton {\n  display: inline-block;\n  border-radius: 3px;\n  border: none;\n  font-size: 0.9rem;\n  padding: 0.5rem 1em;\n  background: #86b32d;\n  border-bottom: 1px solid #5d7d1f;\n  color: white;\n  margin: 5px 0;\n  text-align: center;\n}\n\nbutton:hover {\n  opacity: 0.75;\n  cursor: pointer;\n}\n\n#page-wrapper {\n  width: 95%;\n  background: #FFF;\n  padding: 1.25rem;\n  margin: 1rem auto;\n  min-height: 800px;\n  border-top: 5px solid #69c773;\n  box-shadow: 0 2px 10px rgba(0,0,0,0.8);\n}\n\n#content {\n  width: 85%;\n  overflow: auto;\n  height: 86vh;\n}\n\n#files {\n  width: 15%;\n  line-height: 1;\n}\n\n#files ul {\n  margin: 20px 0;\n  padding: 0.5rem 1rem;\n  overflow-y: auto;\n  list-style: square;\n  background: #F7F7F7;\n  border: 1px solid #D9D9D9;\n  border-radius: 5px;\n  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.1);\n  max-height: 75vh;\n}\n\n#files li {\n  font-size: 14px;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n}\n\n.container {\n  display: flex;\n  flex-direction: row;\n  align-content: flex-start;\n  justify-content: space-between;\n  align-items: flex-start;\n  column-gap: 10px;\n  height: 86vh;\n}\n\n.delete {\n  font-size: 24px;\n  transition: 0.3s;\n  margin-top:5px;\n}\n\n.delete-all{\n  color: #f44336;\n  font-size: 12px;\n  background-color: transparent;\n  background-repeat: no-repeat;\n  border: none;\n  cursor: pointer;\n  overflow: hidden;\n}\n\n/* Tables */\n.table-holder {\n    margin-top: 20px;\n    border: 1px solid lightgray;\n    border-radius: 5px;\n    border-bottom: 0px;\n    border-bottom-left-radius: 0px;\n    border-bottom-right-radius: 0px;\n}\n\n.tables {\n    margin-bottom: 50px;\n}\n\ntable {\n    width: 100%;\n    border-bottom: 0px;\n}\n\ntable, th, td {\n    border: 1px solid lightgrey;\n    border-collapse: collapse;\n    padding-left: 5px;\n}\n\ntable tr:nth-child(even) {\n    background-color: white;\n}\n\ntable tr:nth-child(odd) {\n    background-color: #f2f2f2;\n}\n\ntable th {\n    background-color: #e1e1e1;\n    color: black;\n}\n\n/* Sections */\n\n.section {\n    box-shadow: 10px 10px 10px 10px;\n    background-color: #e5e5e5;\n    padding: 10px;\n    padding-top: 20px;\n    font-size: 18px;\n}\n\n.section-lightgrey {\n    background-color: #f9f9f9;\n}\n\n/* 100% Image Width on Smaller Screens */\n@media only screen and (max-width: 700px){\n  .modal-content {\n    width: 100%;\n  }\n}\n"
  },
  {
    "path": "examples/csvLogger/data/assets/js/csv.js",
    "content": "\n// Default file to be loaded with no parameter in url\nvar filename = '';\n\n// JQuery-like selector\nvar $ = function(el) {\n\treturn document.getElementById(el);\n};\n\n\n/**\n * @returns {getUserInput.userInput} an object\n * containing all the user input at the time \n * of the method call.\n */\nfunction getUserInput() {\n  var userInput = {};\n  userInput.fileName = filename;\n  userInput.maxRows = \"0\";\n  userInput.encoding = 'UTF-8';\n  userInput.columnSeparator = ',';\n  userInput.useQuotes = true;\n  userInput.firstRowHeaders = true;\n  userInput.firstRowInlcude = false;\n  return userInput;\n}\n\n/* Tables */\n\nvar tableCount = 1;\n\n/**\n * Creates a table holder with the \n * given table in it.\n * \n * @param {type} title\n * @param {type} tableHtml\n * @returns {String|getTableUnit.tableHolder}\n */\n\nfunction getTableUnit(title, tableHtml){\n  var id = \"table-\" + tableCount;\n  var tableHolder = \"<div class='table-holder' id='\" + id + \"'><b contenteditable>{@name}</b>{@table}</div>\";\n  tableHolder = tableHolder.replace(\"{@name}\", title);\n  tableHolder = tableHolder.replace(\"{@table}\", tableHtml);\n  tableCount++;\n  return tableHolder;\n}\n\n/**\n * Clears all tables from the page.\n */\nfunction clearTables(){\n  tableCount = 1;\n  $('csv-table').innerHTML = '';\n}\n\n/**\n * Adds (appends) a table holder to the page.\n * \n * @param {type} unit\n * @returns {undefined}\n */\nfunction addTableUnit(unit){\n  $('csv-table').innerHTML = unit ;\n}\n\n\nfunction saveTable(filename, text) {\n  var myblob = new Blob([text]);\n  var formData = new FormData();\n  formData.append(\"data\", myblob, filename);\n\n  // POST data using the Fetch API\n  fetch('/edit', {\n    method: 'POST',\n    headers: {\n      'Access-Control-Allow-Origin': '*',\n      'Access-Control-Max-Age': '600',\n      'Access-Control-Allow-Methods': 'PUT,POST,GET,OPTIONS',\n      'Access-Control-Allow-Headers': '*',\n      'filename': filename\n   },\n    body: formData\n  })\n  // Handle the server response\n  .then(response => response.text())\n  .then(text => {\n    console.log(text);\n  });\n}\n\n\n/* Table downloading */\n\n/**\n * Download the specified table to \n * the users computer.\n * \n * @param {type} table table number\n * @param {save} save file to host memory\n * @returns {undefined}\n */\n\nfunction downloadTable(table, save = false) {\n  var tableId = \"table-\" + table;\n   \n\tvar csvArray = [];\n\tvar rows = document.querySelectorAll(\"#\" + tableId + \" > table tr\");\n\t\t\t\n\tfor (var i = 0; i < rows.length; i++) {\n\t\tvar row = [], cols = rows[i].querySelectorAll(\"td, th\");\n\t\tfor (var j = 0; j < cols.length; j++) {\n\t\t  \n\t\t  var value = cols[j].innerText;\n\t\t  \n\t\t  if(customParseFloat(value) ) {\n        row.push(value);\n\t\t  }\n\t\t  else {\n\t\t     row.push('\"' + value +'\"');\n\t\t  }\n    }\n\t\tcsvArray.push(row.join(\",\")); \t\t\n\t\tcsvArray.push(\"\\n\");\n\t}\n\n  var csvString = csvArray.join(\"\");\n  if (save === false)\n    download(filename, csvString);\n  else {\n    saveTable(filename, csvString);\n  }\n}\n\n\n/**\n * Creates and downloads a file to the users computer.\n * \n * @param {type} filename\n * @param {type} text\n * @returns {undefined}\n */\nfunction download(filename, text) {\n  var element = document.createElement('a');\n  element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));\n  element.setAttribute('download', filename);\n\n  element.style.display = 'none';\n  document.body.appendChild(element);\n  element.click();\n  document.body.removeChild(element);\n}\n\n/**\n * Gets the users input and creates\n * a set of tables from it.\n */\nfunction populate(csv){\n  var ui = getUserInput();\n  addTableUnit(\n    getTableUnit(\n      ui.fileName,\n      getTable(\n        csvTo2DArray(\n          csv,\n          ui.columnSeparator,\n          ui.useQuotes,\n          ui.maxRows\n        ),\n        ui.firstRowHeaders, \n        ui.firstRowInlcude\n      )\n    )\n  );\n\n  // Prevents to ad a new line inside cell\n  var cells =  document.querySelectorAll('td');\n  \n  cells.forEach(item => {\n    item.addEventListener('keypress', event => {\n      if ( event.keyCode === 13 ){\n        if (window.event) {\n          window.event.returnValue = false;\n        }\n      }\n    });\n  });\n}\n\n/**\n * Load the csv from webserver location and fit table with data\n*/\nfunction loadCsv(path) {\n  clearTables();\n  filename = path;\n  \n  fetch(path)\n  .then(response => response.text()) \n  .then(textString => {\n      populate(textString);\n  });\n}\n\n\n/**\n * Standard parseFloat don't handle properly \"0\", \"0.0\", \"0.00\" etc strings\n * @param {strNumber} the string representing a number to be parsed\n * @returns {float number} or NaN\n*/\nfunction customParseFloat(strNumber){\n  if (isNaN(parseFloat(strNumber)) === false){\n    let toFixedLength = 0;\n\n    let arr = strNumber.split('.');\n    if (arr.length === 2 ){\n      toFixedLength = arr[1].length;\n    }\n    return parseFloat(strNumber).toFixed(toFixedLength);\n  }\n  return NaN; // Not a number\n}\n\n/**\n * Creates an unstyled, bare-bones html table\n * from the provided 2D(Multidimentional Array).\n * \n * @param {type} tableArray the 2D array.\n * @param {type} useHeaders will make the first row\n * in the table bold if true.\n * @param {type} dupeHeaders will duplicate the first row\n * in the table if true and useHeaders is true.\n * @param {type} tableId HTML ID for the table.\n * @returns {String} the constructed html table as text.\n */\nfunction getTable(tableArray, useHeaders, dupeHeaders, tableId){\n    var tableOpen = \"<table contenteditable id=\\\"\" + tableId + \"\\\">\";\n    var tableClose = \"</table>\";\n    \n    var headerCell = \"<th>{@val}</th>\";\n    var cell = \"<td>{@val}</td>\";\n    \n    var rowOpen = \"<tr>\";\n    var rowClose = \"</tr>\";\n    \n    var table = tableOpen;\n    \n    for(i = 0; i < tableArray.length; i++){\n        //Row\n        if(i === 1 && useHeaders && dupeHeaders){\n            i = 0;\n            useHeaders = false;\n            dupeHeaders = false;\n        }\n        \n        table += rowOpen;\n        for(j = 0; j < tableArray[i].length; j++){\n            //Cell\n            if(i === 0 && useHeaders){\n                table += headerCell.replace(\"{@val}\", tableArray[i][j]);\n            } else {\n                table += cell.replace(\"{@val}\", tableArray[i][j]);\n            }\n        }\n        \n        table += rowClose;\n    }\n    \n    return table + tableClose;\n}\n\n/**\n * Creates a 2D (Multidimentional) array from\n * CSV data in string form.\n * \n * @param {type} csv the CSV data.\n * @param {type} separator the character used\n * to separate the columns/cells.\n * @param {type} quotes ignores the separator\n * in quoted text.\n * @param {type} maxRows the maximum rows\n * to scan.\n * @returns {Array|csvTo2DArray.table} the CSV data\n * as a 2D (Multidimentional) array.\n */\nfunction csvTo2DArray(csv, separator, quotes, maxRows){\n    var table = [];\n    var rows = 0;\n    \n    csv.split(\"\\n\").map(function(row){\n        if(maxRows !== \"0\")\n            if(rows >= maxRows)\n                return;\n        \n        var tableRow = getRow(row, separator,  quotes);\n        \n        if(tableRow === null)\n            return table;\n        \n        table.push(tableRow);\n        rows++;\n    });\n    \n    return table;\n}\n\n/**\n * Creates an array from a CSV row (line)\n * \n * @param {type} row the CSV row.\n * @param {type} separator character used to separate\n * cells/columns\n * @param {type} quotes ignores the separator\n * in quoted text.\n * @returns {Array|getRow.trow} the CSV row as an array.\n */\nfunction getRow(row, separator, quotes){\n    if(row.length === 0)\n        return null;\n        \n    isQuoted = false;\n    var trow = [];\n    var cell = \"\";\n        \n    for(var i = 0; i < row.length; i++){\n        var char = row.charAt(i);\n            \n        if(quotes){\n            if(char === '\\\"' || char === '\\''){\n                isQuoted = !isQuoted;\n                continue;\n            }\n        }\n            \n        if(char === separator && !isQuoted){\n            trow.push(cell);\n            cell = \"\";\n            continue;\n        }\n            \n        cell += char;\n    }\n    \n    trow.push(cell);\n    return trow;\n}"
  },
  {
    "path": "examples/csvLogger/data/assets/js/index.js",
    "content": "var dataFolder = document.getElementById(\"csv-path\").value;\nvar fileList = document.getElementById('file-list');\nvar currentFile = \"\";\n\n// Fetch the list of files and fill the filelist\nfunction listFiles() {\n  var url = '/list?dir=' + dataFolder;\n  if (url.charAt(url.length - 1) === '/')\n    url = url.slice(0, -1);             // Remove the last character\n  fetch(url)                            // Do the request\n  .then(response => response.json())    // Parse the response\n  .then(obj => {                        // DO something with response\n    fileList.innerHTML = '';\n    obj.forEach(function(entry, i) {\n      addEntry(entry.name);\n    });\n    // Load last file\n    loadCsv(dataFolder + obj[obj.length -1].name);    \n  });\n}\n\n// Load selected image inside the preview content\nfunction loadFile(filename) {\n  loadCsv(filename);\n}\n\n// Delete selected file in SD\nasync function deleteFile(filename) {\n  var isExecuted = confirm(\"Are you sure to delete \"+ filename + \"?\");\n  if(isExecuted){\n    const data = new URLSearchParams();\n    data.append('path', filename);\n    fetch('/edit', {\n        method: 'DELETE',\n        body: data\n    });\n    // Update the file browser.\n    listFiles();\n  }\n}\n\nasync function deleteAll() {\n  var isExecuted = confirm(\"Are you sure to delete all files in \"+ dataFolder + \" folder?\");\n  if(isExecuted){\n    var ul = document.getElementById(\"file-list\");\n    var items = ul.getElementsByClassName(\"edit-file\");\n    for (var i=0; i<items.length; i++) {\n      console.log(\"Delete \" + items[i].innerHTML);\n      await deleteFile(imgFolder + items[i].innerHTML);\n    }\n  }\n}\n\n// Add a single entry to the filelist\nfunction addEntry(entryName) {\n  var li = document.createElement('li');\n  var link = document.createElement('a');\n  link.innerHTML = entryName;\n  link.className = 'edit-file';\n  li.appendChild(link);\n\n  var delLink = document.createElement('a');\n  delLink.innerHTML = '<span class=\"delete\">&times;</span>';\n  delLink.className = 'delete-file';\n  li.appendChild(delLink);\n  fileList.insertBefore(li, fileList.firstChild);\n\n  // Setup an event listener that will load the file when the link is clicked.\n  link.addEventListener('click', function(e) {\n    e.preventDefault();\n    loadFile(dataFolder +  entryName);\n    currentFile = dataFolder +  entryName;\n  });\n\n  // Setup an event listener that will delete the file when the delete link is clicked.\n  delLink.addEventListener('click', function(e) {\n    e.preventDefault();\n    deleteFile(dataFolder + entryName);\n  });\n\n}\n\n// Add the event listeners\ndocument.getElementById('download-csv').addEventListener('click', function(e) {\n  downloadTable(1);\n});\ndocument.getElementById('save-csv').addEventListener('click', function(e) {\n  downloadTable(1, true);\n});\n\ndocument.getElementById('load-list').addEventListener('click', function(e) {\n  listFiles();\n});\n\n\n// Start the web page\nlistFiles();"
  },
  {
    "path": "examples/csvLogger/data/csv/2024_01_10.csv",
    "content": "timestamp, free heap, largest free block, connected, wifi strength\nWed Jan 10 13:19:43 2024, 270472, 262132, true, -43\nWed Jan 10 13:19:53 2024, 270472, 262132, true, -43\nWed Jan 10 13:20:03 2024, 270472, 262132, true, -40\nWed Jan 10 13:20:13 2024, 268924, 258036, true, -46\nWed Jan 10 13:20:23 2024, 266644, 253940, true, -41\nWed Jan 10 13:20:33 2024, 266644, 253940, true, -42\nWed Jan 10 13:20:43 2024, 266644, 253940, true, -42\nWed Jan 10 13:20:53 2024, 266644, 253940, true, -43\nWed Jan 10 13:21:03 2024, 270504, 262132, true, -45\nWed Jan 10 13:21:13 2024, 270500, 262132, true, -44\nWed Jan 10 13:21:23 2024, 270500, 262132, true, -42\nWed Jan 10 13:21:33 2024, 270500, 262132, true, -44\nWed Jan 10 13:21:43 2024, 270500, 262132, true, -45\nWed Jan 10 13:21:53 2024, 270500, 262132, true, -43\nWed Jan 10 13:22:33 2024, 263376, 253940, true, -42\n"
  },
  {
    "path": "examples/csvLogger/data/index.htm",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\">\n  <title>ESP32 CSV List</title>\n  <!-- Cyustom page styling -->\n  <link rel=\"stylesheet\" href=\"assets/css/style.css\" />\n</head>\n  <body>\n    <main>\n      <div id=\"page-wrapper\" class=\"clearfix\">\n        <h1>CSV list web interface</h1>\n        <div class=container>\n          <div id=\"files\">\n            <ul id=\"file-list\"></ul>\n            <div class=\"field\">\n              <input type=\"text\" id=\"csv-path\" name=\"path\" value='/csv/'>\n              <button id=\"load-list\">Load list</button><br><br>\n              <button id=\"download-csv\">Download</button>\n              <button id=\"save-csv\">Save CSV</button>\n            </div>\n            <hr>\n            <input type=\"button\" onclick=\"deleteAll()\" value=\"Delete all files\" class=\"delete-all\">  \n            <hr>\n          </div>\n          <div id=\"content\">\n            <div class=\"tables text-center\" id=\"csv-table\"></div>\n          </div>\n        </div>\n      </div>\n    </main>\n    <script src=\"assets/js/csv.js\"></script>\n    <script src=\"assets/js/index.js\"></script>\n  </body>\n</html>"
  },
  {
    "path": "examples/csvLogger/partitions.csv",
    "content": "# Name,   Type, SubType, Offset,  Size, Flags\nnvs,      data, nvs,     0x9000,  0x5000,\notadata,  data, ota,     0xe000,  0x2000,\napp0,     app,  ota_0,   0x10000, 0x140000,\napp1,     app,  ota_1,   0x150000,0x140000,\nspiffs,   data, spiffs,  0x290000,0x160000,\ncoredump, data, coredump,0x3F0000,0x10000,\n"
  },
  {
    "path": "examples/csvLogger/platformio.ini",
    "content": "; PlatformIO Project Configuration File\n;\n;   Build options: build flags, source filter\n;   Upload options: custom upload port, speed and extra flags\n;   Library options: dependencies, extra library storages\n;   Advanced options: extra scripting\n;\n; Please visit documentation for the other options and examples\n; https://docs.platformio.org/page/projectconf.html\n\n[platformio]\nsrc_dir = .\n\n[env:esp32-s3-devkitc1-n4r2]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32-s3-devkitc1-n4r2\nframework = arduino\nupload_speed = 921600\nmonitor_speed = 115200\nboard_build.partitions = partitions.csv\nlib_extra_dirs = ../../\nlib_ignore = pio_examples\n\n[env:esp32dev]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32dev\nframework = arduino\nupload_speed = 921600\nmonitor_speed = 115200\nboard_build.partitions = partitions.csv\nlib_extra_dirs = ../../\nlib_ignore = pio_examples\n\n[env:esp8266-nodemcuv2]\nplatform = espressif8266\nboard = nodemcuv2\nframework = arduino\nupload_speed = 921600\nmonitor_speed = 115200\nboard_build.partitions = partitions.csv\nlib_extra_dirs = ../../\nlib_ignore = pio_examples\n"
  },
  {
    "path": "examples/csvLogger/readme.md",
    "content": "An example for logging to a CSV file and viewing the content with the browser.\nIt is also possible to modify or download the file.\n\n![image](https://github.com/cotestatnt/esp-fs-webserver/assets/27758688/a776a217-f634-480c-873c-8914e82f87e3)"
  },
  {
    "path": "examples/csvLoggerSD/csvLoggerSD.ino",
    "content": "#include <SD.h>\n#include <FSWebServer.h>\n\n// Timezone definition to get properly time from NTP server\n#define MYTZ \"CET-1CEST,M3.5.0,M10.5.0/3\"\n#include <time.h>\n\n#define PIN_CS 14\n#define PIN_SCK 13\n#define PIN_MOSI 12\n#define PIN_MISO 11\n\n\nFSWebServer server(SD, 80, \"myServer\");\nbool captiveRun = false;\nstruct tm ntpTime;\nconst char* basePath = \"/csv\";\n\n////////////////////////////////  NTP Time  /////////////////////////////////////////\nvoid getUpdatedtime(const uint32_t timeout) {\n    uint32_t start = millis();\n    do {\n        time_t now = time(nullptr);\n        ntpTime = *localtime(&now);\n        delay(1);\n    } while (millis() - start < timeout && ntpTime.tm_year <= (1970 - 1900));\n}\n\n\n////////////////////////////////  Filesystem  /////////////////////////////////////////\nbool startFilesystem(){\n  if (SD.begin(PIN_CS)){\n    server.printFileList(SD, \"/\", 2);\n    return true;\n  }\n  else {\n    Serial.println(\"ERROR on mounting filesystem. It will be formmatted!\");\n    ESP.restart();\n  }\n  return false;\n}\n\n//////////////////////////// Append a row to csv file ///////////////////////////////////\nbool appenRow() {\n  getUpdatedtime(10);\n\n  char filename[24];\n  snprintf(filename, sizeof(filename),\n    \"%s/%04d_%02d_%02d.csv\",\n    basePath,\n    ntpTime.tm_year + 1900,\n    ntpTime.tm_mon + 1,\n    ntpTime.tm_mday\n  );\n\n  File file;\n  if (SD.exists(filename)) {\n    file = SD.open(filename, \"a\");   // Append to existing file\n  }\n  else {\n    file = SD.open(filename, \"w\");   // Create a new file\n    file.println(\"timestamp, free heap, largest free block, connected, wifi strength\");\n  }\n\n  if (file) {\n    char timestamp[25];\n    strftime(timestamp, sizeof(timestamp), \"%c\", &ntpTime);\n\n    char row[64];\n  #ifdef ESP32\n      snprintf(row, sizeof(row), \"%s, %d, %d, %s, %d\",\n        timestamp,\n        heap_caps_get_free_size(0),\n        heap_caps_get_largest_free_block(0),\n        (WiFi.status() == WL_CONNECTED) ? \"true\" : \"false\",\n        WiFi.RSSI()\n      );\n  #elif defined(ESP8266)\n      uint32_t free;\n      uint16_t max;\n      ESP.getHeapStats(&free, &max, nullptr);\n      snprintf(row, sizeof(row),\n        \"%s, %d, %d, %s, %d\",\n        timestamp, free, max,\n        (WiFi.status() == WL_CONNECTED) ? \"true\" : \"false\",\n        WiFi.RSSI()\n      );\n  #endif\n    Serial.println(row);\n    file.println(row);\n    file.close();\n    return true;\n  }\n\n  return false;\n}\n\n\nvoid setup() {\n  SPI.begin(PIN_SCK, PIN_MISO, PIN_MOSI, PIN_CS);\n  Serial.begin(115200);\n  \n  delay(1000);\n  startFilesystem();\n\n  // Create csv logs folder if not exists\n  if (!SD.exists(basePath)) {\n    SD.mkdir(basePath);\n  }\n\n  // Try to connect to WiFi (will start AP if not connected after timeout)\n  if (!server.startWiFi(10000)) {\n    Serial.println(\"\\nWiFi not connected! Starting AP mode...\");\n    server.startCaptivePortal(\"ESP_AP\", \"123456789\", \"/setup\");\n    captiveRun = true;\n  }\n\n  // Enable ACE FS file web editor and add FS info callback fucntion\n  server.enableFsCodeEditor();\n  #ifdef ESP32\n  server.setFsInfoCallback([](fsInfo_t* fsInfo) {\n      fsInfo->totalBytes = SD.totalBytes();\n      fsInfo->usedBytes = SD.usedBytes();\n      fsInfo->fsName = \"SD\";\n  });\n  #endif\n\n  // Start server\n  server.begin();\n  Serial.print(F(\"\\nAsync ESP Web Server started on IP Address: \"));\n  Serial.println(server.getServerIP());\n  Serial.println(F(\n      \"This is \\\"scvLoggerSdFat.ino\\\" example.\\n\"\n      \"Open /setup page to configure optional parameters.\\n\"\n      \"Open /edit page to view, edit or upload example or your custom webserver source files.\"\n  ));\n\n  // Set NTP servers\n  #ifdef ESP8266\n  configTime(MYTZ, \"time.google.com\", \"time.windows.com\", \"pool.ntp.org\");\n  #elif defined(ESP32)\n  configTzTime(MYTZ, \"time.google.com\", \"time.windows.com\", \"pool.ntp.org\");\n  #endif\n\n  // Wait for NTP sync (with timeout)\n  getUpdatedtime(5000);\n\n  appenRow();\n}\n\nvoid loop() {\n  server.handleClient();\n  if (captiveRun)\n    server.updateDNS();\n\n  static uint32_t updateTime;\n  if (millis()- updateTime > 30000) {\n    updateTime = millis();\n    appenRow();\n  }\n}\n"
  },
  {
    "path": "examples/csvLoggerSD/data/assets/css/index.css",
    "content": "/* Misc */\nhtml, body {\n    font-family: \"Helvetica\",\"Arial\",sans-serif;\n    font-size: 14px;\n    font-weight: 400;\n    line-height: 20px;\n}\nbody {\n    margin: 0;\n    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\";\n    font-size: 1rem;\n    font-weight: 400;\n    line-height: 1.5;\n    color: #212529;\n    text-align: left;\n    background-color: #fff;\n}\n\nh1 {\n  margin: 5px;\n  text-align: center;\n}\n\np {\n  font-size: 0.9rem;\n  margin: 0.5rem 0 1.5rem 0;\n}\n\na,\na:visited {\n  color: #08C;\n  text-decoration: none;\n}\n\na:hover,\na:focus {\n  color: #69c773;\n  cursor: pointer;\n}\n\na.delete-file,\na.delete-file:visited {\n  color: #CC0000;\n  margin-left: 0.5rem;\n  vertical-align: middle;\n}\n\nbutton {\n  display: inline-block;\n  border-radius: 3px;\n  border: none;\n  font-size: 0.9rem;\n  padding: 0.5rem 1em;\n  background: #86b32d;\n  border-bottom: 1px solid #5d7d1f;\n  color: white;\n  margin: 5px 0;\n  text-align: center;\n}\n\nbutton:hover {\n  opacity: 0.75;\n  cursor: pointer;\n}\n\n#page-wrapper {\n  width: 95%;\n  background: #FFF;\n  padding: 1.25rem;\n  margin: 1rem auto;\n  min-height: 800px;\n  border-top: 5px solid #69c773;\n  box-shadow: 0 2px 10px rgba(0,0,0,0.8);\n}\n\n#content {\n  width: 85%;\n  overflow: auto;\n  height: 86vh;\n}\n\n#files {\n  width: 15%;\n}\n\n#files ul {\n  margin: 20px 0;\n  padding: 0.5rem 1rem;\n  overflow-y: auto;\n  list-style: square;\n  background: #F7F7F7;\n  border: 1px solid #D9D9D9;\n  border-radius: 5px;\n  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.1);\n  max-height: 75vh;\n}\n\n#files li {\n  margin-left: 8px;\n  font-size: 14px;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n}\n\n.container {\n  display: flex;\n  flex-direction: row;\n  align-content: flex-start;\n  justify-content: space-between;\n  align-items: flex-start;\n  column-gap: 10px;\n  height: 86vh;\n}\n\n.delete {\n  font-size: 24px;\n  transition: 0.3s;\n  margin-top:5px;\n}\n\n.delete-all{\n  color: #f44336;\n  font-size: 12px;\n  background-color: transparent;\n  background-repeat: no-repeat;\n  border: none;\n  cursor: pointer;\n  overflow: hidden;\n}\n\n/* Tables */\n.table-holder {\n    margin-top: 20px;\n    border: 1px solid lightgray;\n    border-radius: 5px;\n    border-bottom: 0px;\n    border-bottom-left-radius: 0px;\n    border-bottom-right-radius: 0px;\n}\n\n.tables {\n    margin-bottom: 50px;\n}\n\ntable {\n    width: 100%;\n    border-bottom: 0px;\n}\n\ntable, th, td {\n    border: 1px solid lightgrey;\n    border-collapse: collapse;\n    padding-left: 5px;\n}\n\ntable tr:nth-child(even) {\n    background-color: white;\n}\n\ntable tr:nth-child(odd) {\n    background-color: #f2f2f2;\n}\n\ntable th {\n    background-color: #e1e1e1;\n    color: black;\n}\n\n/* Sections */\n\n.section {\n    box-shadow: 10px 10px 10px 10px;\n    background-color: #e5e5e5;\n    padding: 10px;\n    padding-top: 20px;\n    font-size: 18px;\n}\n\n.section-lightgrey {\n    background-color: #f9f9f9;\n}\n\n/* 100% Image Width on Smaller Screens */\n@media only screen and (max-width: 700px){\n  .modal-content {\n    width: 100%;\n  }\n}\n"
  },
  {
    "path": "examples/csvLoggerSD/data/assets/css/style.css",
    "content": "/* Misc */\nhtml, body {\n    font-family: \"Helvetica\",\"Arial\",sans-serif;\n    font-size: 14px;\n    font-weight: 400;\n    line-height: 20px;\n}\nbody {\n    margin: 0;\n    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\";\n    font-size: 1rem;\n    font-weight: 400;\n    line-height: 1.5;\n    color: #212529;\n    text-align: left;\n    background-color: #fff;\n}\n\nh1 {\n  margin: 5px;\n  text-align: center;\n}\n\np {\n  font-size: 0.9rem;\n  margin: 0.5rem 0 1.5rem 0;\n}\n\na,\na:visited {\n  color: #08C;\n  text-decoration: none;\n}\n\na:hover,\na:focus {\n  color: #69c773;\n  cursor: pointer;\n}\n\na.delete-file,\na.delete-file:visited {\n  color: #CC0000;\n  margin-left: 0.5rem;\n  vertical-align: middle;\n}\n\nbutton {\n  display: inline-block;\n  border-radius: 3px;\n  border: none;\n  font-size: 0.9rem;\n  padding: 0.5rem 1em;\n  background: #86b32d;\n  border-bottom: 1px solid #5d7d1f;\n  color: white;\n  margin: 5px 0;\n  text-align: center;\n}\n\nbutton:hover {\n  opacity: 0.75;\n  cursor: pointer;\n}\n\n#page-wrapper {\n  width: 95%;\n  background: #FFF;\n  padding: 1.25rem;\n  margin: 1rem auto;\n  min-height: 800px;\n  border-top: 5px solid #69c773;\n  box-shadow: 0 2px 10px rgba(0,0,0,0.8);\n}\n\n#content {\n  width: 85%;\n  overflow: auto;\n  height: 86vh;\n}\n\n#files {\n  width: 15%;\n  line-height: 1;\n}\n\n#files ul {\n  margin: 20px 0;\n  padding: 0.5rem 1rem;\n  overflow-y: auto;\n  list-style: square;\n  background: #F7F7F7;\n  border: 1px solid #D9D9D9;\n  border-radius: 5px;\n  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.1);\n  max-height: 75vh;\n}\n\n#files li {\n  font-size: 14px;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n}\n\n.container {\n  display: flex;\n  flex-direction: row;\n  align-content: flex-start;\n  justify-content: space-between;\n  align-items: flex-start;\n  column-gap: 10px;\n  height: 86vh;\n}\n\n.delete {\n  font-size: 24px;\n  transition: 0.3s;\n  margin-top:5px;\n}\n\n.delete-all{\n  color: #f44336;\n  font-size: 12px;\n  background-color: transparent;\n  background-repeat: no-repeat;\n  border: none;\n  cursor: pointer;\n  overflow: hidden;\n}\n\n/* Tables */\n.table-holder {\n    margin-top: 20px;\n    border: 1px solid lightgray;\n    border-radius: 5px;\n    border-bottom: 0px;\n    border-bottom-left-radius: 0px;\n    border-bottom-right-radius: 0px;\n}\n\n.tables {\n    margin-bottom: 50px;\n}\n\ntable {\n    width: 100%;\n    border-bottom: 0px;\n}\n\ntable, th, td {\n    border: 1px solid lightgrey;\n    border-collapse: collapse;\n    padding-left: 5px;\n}\n\ntable tr:nth-child(even) {\n    background-color: white;\n}\n\ntable tr:nth-child(odd) {\n    background-color: #f2f2f2;\n}\n\ntable th {\n    background-color: #e1e1e1;\n    color: black;\n}\n\n/* Sections */\n\n.section {\n    box-shadow: 10px 10px 10px 10px;\n    background-color: #e5e5e5;\n    padding: 10px;\n    padding-top: 20px;\n    font-size: 18px;\n}\n\n.section-lightgrey {\n    background-color: #f9f9f9;\n}\n\n/* 100% Image Width on Smaller Screens */\n@media only screen and (max-width: 700px){\n  .modal-content {\n    width: 100%;\n  }\n}\n"
  },
  {
    "path": "examples/csvLoggerSD/data/assets/js/csv.js",
    "content": "\n// Default file to be loaded with no parameter in url\nvar filename = '';\n\n// JQuery-like selector\nvar $ = function(el) {\n\treturn document.getElementById(el);\n};\n\n\n/**\n * @returns {getUserInput.userInput} an object\n * containing all the user input at the time \n * of the method call.\n */\nfunction getUserInput() {\n  var userInput = {};\n  userInput.fileName = filename;\n  userInput.maxRows = \"0\";\n  userInput.encoding = 'UTF-8';\n  userInput.columnSeparator = ',';\n  userInput.useQuotes = true;\n  userInput.firstRowHeaders = true;\n  userInput.firstRowInlcude = false;\n  return userInput;\n}\n\n/* Tables */\n\nvar tableCount = 1;\n\n/**\n * Creates a table holder with the \n * given table in it.\n * \n * @param {type} title\n * @param {type} tableHtml\n * @returns {String|getTableUnit.tableHolder}\n */\n\nfunction getTableUnit(title, tableHtml){\n  var id = \"table-\" + tableCount;\n  var tableHolder = \"<div class='table-holder' id='\" + id + \"'><b contenteditable>{@name}</b>{@table}</div>\";\n  tableHolder = tableHolder.replace(\"{@name}\", title);\n  tableHolder = tableHolder.replace(\"{@table}\", tableHtml);\n  tableCount++;\n  return tableHolder;\n}\n\n/**\n * Clears all tables from the page.\n */\nfunction clearTables(){\n  tableCount = 1;\n  $('csv-table').innerHTML = '';\n}\n\n/**\n * Adds (appends) a table holder to the page.\n * \n * @param {type} unit\n * @returns {undefined}\n */\nfunction addTableUnit(unit){\n  $('csv-table').innerHTML = unit ;\n}\n\n\nfunction saveTable(filename, text) {\n  var myblob = new Blob([text]);\n  var formData = new FormData();\n  formData.append(\"data\", myblob, filename);\n\n  // POST data using the Fetch API\n  fetch('/edit', {\n    method: 'POST',\n    headers: {\n      'Access-Control-Allow-Origin': '*',\n      'Access-Control-Max-Age': '600',\n      'Access-Control-Allow-Methods': 'PUT,POST,GET,OPTIONS',\n      'Access-Control-Allow-Headers': '*',\n      'filename': filename\n   },\n    body: formData\n  })\n  // Handle the server response\n  .then(response => response.text())\n  .then(text => {\n    console.log(text);\n  });\n}\n\n\n/* Table downloading */\n\n/**\n * Download the specified table to \n * the users computer.\n * \n * @param {type} table table number\n * @param {save} save file to host memory\n * @returns {undefined}\n */\n\nfunction downloadTable(table, save = false) {\n  var tableId = \"table-\" + table;\n   \n\tvar csvArray = [];\n\tvar rows = document.querySelectorAll(\"#\" + tableId + \" > table tr\");\n\t\t\t\n\tfor (var i = 0; i < rows.length; i++) {\n\t\tvar row = [], cols = rows[i].querySelectorAll(\"td, th\");\n\t\tfor (var j = 0; j < cols.length; j++) {\n\t\t  \n\t\t  var value = cols[j].innerText;\n\t\t  \n\t\t  if(customParseFloat(value) ) {\n        row.push(value);\n\t\t  }\n\t\t  else {\n\t\t     row.push('\"' + value +'\"');\n\t\t  }\n    }\n\t\tcsvArray.push(row.join(\",\")); \t\t\n\t\tcsvArray.push(\"\\n\");\n\t}\n\n  var csvString = csvArray.join(\"\");\n  if (save === false)\n    download(filename, csvString);\n  else {\n    saveTable(filename, csvString);\n  }\n}\n\n\n/**\n * Creates and downloads a file to the users computer.\n * \n * @param {type} filename\n * @param {type} text\n * @returns {undefined}\n */\nfunction download(filename, text) {\n  var element = document.createElement('a');\n  element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));\n  element.setAttribute('download', filename);\n\n  element.style.display = 'none';\n  document.body.appendChild(element);\n  element.click();\n  document.body.removeChild(element);\n}\n\n/**\n * Gets the users input and creates\n * a set of tables from it.\n */\nfunction populate(csv){\n  var ui = getUserInput();\n  addTableUnit(\n    getTableUnit(\n      ui.fileName,\n      getTable(\n        csvTo2DArray(\n          csv,\n          ui.columnSeparator,\n          ui.useQuotes,\n          ui.maxRows\n        ),\n        ui.firstRowHeaders, \n        ui.firstRowInlcude\n      )\n    )\n  );\n\n  // Prevents to ad a new line inside cell\n  var cells =  document.querySelectorAll('td');\n  \n  cells.forEach(item => {\n    item.addEventListener('keypress', event => {\n      if ( event.keyCode === 13 ){\n        if (window.event) {\n          window.event.returnValue = false;\n        }\n      }\n    });\n  });\n}\n\n/**\n * Load the csv from webserver location and fit table with data\n*/\nfunction loadCsv(path) {\n  clearTables();\n  filename = path;\n  \n  fetch(path)\n  .then(response => response.text()) \n  .then(textString => {\n      populate(textString);\n  });\n}\n\n\n/**\n * Standard parseFloat don't handle properly \"0\", \"0.0\", \"0.00\" etc strings\n * @param {strNumber} the string representing a number to be parsed\n * @returns {float number} or NaN\n*/\nfunction customParseFloat(strNumber){\n  if (isNaN(parseFloat(strNumber)) === false){\n    let toFixedLength = 0;\n\n    let arr = strNumber.split('.');\n    if (arr.length === 2 ){\n      toFixedLength = arr[1].length;\n    }\n    return parseFloat(strNumber).toFixed(toFixedLength);\n  }\n  return NaN; // Not a number\n}\n\n/**\n * Creates an unstyled, bare-bones html table\n * from the provided 2D(Multidimentional Array).\n * \n * @param {type} tableArray the 2D array.\n * @param {type} useHeaders will make the first row\n * in the table bold if true.\n * @param {type} dupeHeaders will duplicate the first row\n * in the table if true and useHeaders is true.\n * @param {type} tableId HTML ID for the table.\n * @returns {String} the constructed html table as text.\n */\nfunction getTable(tableArray, useHeaders, dupeHeaders, tableId){\n    var tableOpen = \"<table contenteditable id=\\\"\" + tableId + \"\\\">\";\n    var tableClose = \"</table>\";\n    \n    var headerCell = \"<th>{@val}</th>\";\n    var cell = \"<td>{@val}</td>\";\n    \n    var rowOpen = \"<tr>\";\n    var rowClose = \"</tr>\";\n    \n    var table = tableOpen;\n    \n    for(i = 0; i < tableArray.length; i++){\n        //Row\n        if(i === 1 && useHeaders && dupeHeaders){\n            i = 0;\n            useHeaders = false;\n            dupeHeaders = false;\n        }\n        \n        table += rowOpen;\n        for(j = 0; j < tableArray[i].length; j++){\n            //Cell\n            if(i === 0 && useHeaders){\n                table += headerCell.replace(\"{@val}\", tableArray[i][j]);\n            } else {\n                table += cell.replace(\"{@val}\", tableArray[i][j]);\n            }\n        }\n        \n        table += rowClose;\n    }\n    \n    return table + tableClose;\n}\n\n/**\n * Creates a 2D (Multidimentional) array from\n * CSV data in string form.\n * \n * @param {type} csv the CSV data.\n * @param {type} separator the character used\n * to separate the columns/cells.\n * @param {type} quotes ignores the separator\n * in quoted text.\n * @param {type} maxRows the maximum rows\n * to scan.\n * @returns {Array|csvTo2DArray.table} the CSV data\n * as a 2D (Multidimentional) array.\n */\nfunction csvTo2DArray(csv, separator, quotes, maxRows){\n    var table = [];\n    var rows = 0;\n    \n    csv.split(\"\\n\").map(function(row){\n        if(maxRows !== \"0\")\n            if(rows >= maxRows)\n                return;\n        \n        var tableRow = getRow(row, separator,  quotes);\n        \n        if(tableRow === null)\n            return table;\n        \n        table.push(tableRow);\n        rows++;\n    });\n    \n    return table;\n}\n\n/**\n * Creates an array from a CSV row (line)\n * \n * @param {type} row the CSV row.\n * @param {type} separator character used to separate\n * cells/columns\n * @param {type} quotes ignores the separator\n * in quoted text.\n * @returns {Array|getRow.trow} the CSV row as an array.\n */\nfunction getRow(row, separator, quotes){\n    if(row.length === 0)\n        return null;\n        \n    isQuoted = false;\n    var trow = [];\n    var cell = \"\";\n        \n    for(var i = 0; i < row.length; i++){\n        var char = row.charAt(i);\n            \n        if(quotes){\n            if(char === '\\\"' || char === '\\''){\n                isQuoted = !isQuoted;\n                continue;\n            }\n        }\n            \n        if(char === separator && !isQuoted){\n            trow.push(cell);\n            cell = \"\";\n            continue;\n        }\n            \n        cell += char;\n    }\n    \n    trow.push(cell);\n    return trow;\n}"
  },
  {
    "path": "examples/csvLoggerSD/data/assets/js/index.js",
    "content": "var dataFolder = document.getElementById(\"csv-path\").value;\nvar fileList = document.getElementById('file-list');\nvar currentFile = \"\";\n\n// Fetch the list of files and fill the filelist\nfunction listFiles() {\n  var url = '/list?dir=' + dataFolder;\n  if (url.charAt(url.length - 1) === '/')\n    url = url.slice(0, -1);             // Remove the last character\n  fetch(url)                            // Do the request\n  .then(response => response.json())    // Parse the response\n  .then(obj => {                        // DO something with response\n    fileList.innerHTML = '';\n    obj.forEach(function(entry, i) {\n      addEntry(entry.name);\n    });\n    // Load last file\n    loadCsv(dataFolder + obj[obj.length -1].name);    \n  });\n}\n\n// Load selected image inside the preview content\nfunction loadFile(filename) {\n  loadCsv(filename);\n}\n\n// Delete selected file in SD\nasync function deleteFile(filename) {\n  var isExecuted = confirm(\"Are you sure to delete \"+ filename + \"?\");\n  if(isExecuted){\n    const data = new URLSearchParams();\n    data.append('path', filename);\n    fetch('/edit', {\n        method: 'DELETE',\n        body: data\n    });\n    // Update the file browser.\n    listFiles();\n  }\n}\n\nasync function deleteAll() {\n  var isExecuted = confirm(\"Are you sure to delete all files in \"+ dataFolder + \" folder?\");\n  if(isExecuted){\n    var ul = document.getElementById(\"file-list\");\n    var items = ul.getElementsByClassName(\"edit-file\");\n    for (var i=0; i<items.length; i++) {\n      console.log(\"Delete \" + items[i].innerHTML);\n      await deleteFile(imgFolder + items[i].innerHTML);\n    }\n  }\n}\n\n// Add a single entry to the filelist\nfunction addEntry(entryName) {\n  var li = document.createElement('li');\n  var link = document.createElement('a');\n  link.innerHTML = entryName;\n  link.className = 'edit-file';\n  li.appendChild(link);\n\n  var delLink = document.createElement('a');\n  delLink.innerHTML = '<span class=\"delete\">&times;</span>';\n  delLink.className = 'delete-file';\n  li.appendChild(delLink);\n  fileList.insertBefore(li, fileList.firstChild);\n\n  // Setup an event listener that will load the file when the link is clicked.\n  link.addEventListener('click', function(e) {\n    e.preventDefault();\n    loadFile(dataFolder +  entryName);\n    currentFile = dataFolder +  entryName;\n  });\n\n  // Setup an event listener that will delete the file when the delete link is clicked.\n  delLink.addEventListener('click', function(e) {\n    e.preventDefault();\n    deleteFile(dataFolder + entryName);\n  });\n\n}\n\n// Add the event listeners\ndocument.getElementById('download-csv').addEventListener('click', function(e) {\n  downloadTable(1);\n});\ndocument.getElementById('save-csv').addEventListener('click', function(e) {\n  downloadTable(1, true);\n});\n\ndocument.getElementById('load-list').addEventListener('click', function(e) {\n  listFiles();\n});\n\n\n// Start the web page\nlistFiles();"
  },
  {
    "path": "examples/csvLoggerSD/data/index.htm",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\">\n  <title>ESP32 CSV List</title>\n  <!-- Cyustom page styling -->\n  <link rel=\"stylesheet\" href=\"assets/css/style.css\" />\n</head>\n  <body>\n    <main>\n      <div id=\"page-wrapper\" class=\"clearfix\">\n        <h1>CSV list web interface</h1>\n        <div class=container>\n          <div id=\"files\">\n            <ul id=\"file-list\"></ul>\n            <div class=\"field\">\n              <input type=\"text\" id=\"csv-path\" name=\"path\" value='/csv/'>\n              <button id=\"load-list\">Load list</button><br><br>\n              <button id=\"download-csv\">Download</button>\n              <button id=\"save-csv\">Save CSV</button>\n            </div>\n            <hr>\n            <input type=\"button\" onclick=\"deleteAll()\" value=\"Delete all files\" class=\"delete-all\">  \n            <hr>\n          </div>\n          <div id=\"content\">\n            <div class=\"tables text-center\" id=\"csv-table\"></div>\n          </div>\n        </div>\n      </div>\n    </main>\n    <script src=\"assets/js/csv.js\"></script>\n    <script src=\"assets/js/index.js\"></script>\n  </body>\n</html>"
  },
  {
    "path": "examples/csvLoggerSD/partitions.csv",
    "content": "# Name,   Type, SubType, Offset,  Size, Flags\nnvs,      data, nvs,     0x9000,  0x5000,\notadata,  data, ota,     0xe000,  0x2000,\napp0,     app,  ota_0,   0x10000, 0x140000,\napp1,     app,  ota_1,   0x150000,0x140000,\nspiffs,   data, spiffs,  0x290000,0x160000,\ncoredump, data, coredump,0x3F0000,0x10000,\n"
  },
  {
    "path": "examples/csvLoggerSD/platformio.ini",
    "content": "; PlatformIO Project Configuration File\n;\n;   Build options: build flags, source filter\n;   Upload options: custom upload port, speed and extra flags\n;   Library options: dependencies, extra library storages\n;   Advanced options: extra scripting\n;\n; Please visit documentation for the other options and examples\n; https://docs.platformio.org/page/projectconf.html\n\n[platformio]\nsrc_dir = .\n\n[env:esp32-s3-devkitc1-n4r2]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32-s3-devkitc1-n4r2\nframework = arduino\nupload_speed = 921600\nboard_build.partitions = partitions.csv\nlib_extra_dirs = ../../\nlib_ignore = pio_examples\n\n[env:esp32dev]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32dev\nframework = arduino\nupload_speed = 921600\nboard_build.partitions = partitions.csv\nlib_extra_dirs = ../../\nlib_ignore = pio_examples\n\n[env:esp8266-nodemcuv2]\nplatform = espressif8266\nboard = nodemcuv2\nframework = arduino\nupload_speed = 921600\nboard_build.partitions = partitions.csv\nlib_extra_dirs = ../../\nlib_ignore = pio_examples\n"
  },
  {
    "path": "examples/csvLoggerSD/readme.md",
    "content": "An example for logging to a CSV file and viewing the content with the browser.\nIt is also possible to modify or download the file.\n\n![image](https://github.com/cotestatnt/esp-fs-webserver/assets/27758688/a776a217-f634-480c-873c-8914e82f87e3)"
  },
  {
    "path": "examples/customHTML/customElements.h",
    "content": "#pragma once\n#include <Arduino.h>\n\n\n/*\n* This HTML code will be injected in /setup webpage using a <div></div> element as parent\n* The parent element will have the HTML id properties equal to 'raw-html-<id>'\n* where the id value will be equal to the id parameter passed to the function addHTML(html_code, id).\n*/\ninline const char custom_html[] PROGMEM = R\"EOF(\n<label for=url class=input-label>Endpoint</label>\n<input type=text placeholder='https://httpbin.org/' id=url value='https://httpbin.org/' />\n<br>\n<div class=row-wrapper>\n  <input type=\"radio\" id=\"get\" name=\"httpmethod\" value=\"GET\" checked>\n  <label for=\"html\">GET</label><br>\n  <input type=\"radio\" id=\"post\" name=\"httpmethod\" value=\"POST\">\n  <label for=\"css\">POST</label>\n  <a id=fetch class='btn'>\n  <span>Fecth url</span>\n  </a>\n</div>\n<pre id=payload></pre>\n)EOF\";\n\n\n/*\n* In this example, a style sections is added in order to render properly the new\n* <select> and <pre> elements introduced. Since this section will be added at the end of the body,\n* it is also possible to override the style of the elements already present:\n* for example the background color of body will be overridden with a different color\n*/\ninline const char custom_css[] PROGMEM = R\"EOF(\npre{\n    font-family: Monaco,Menlo,Consolas,'Courier New',monospace;\n    color: #333;\n    line-height: 20px;\n    background-color: #f5f5f5;\n    border: 1px solid rgba(0,0,0,0.15);\n    border-radius: 6px;\n    overflow-y: scroll;\n    min-height: 350px;\n    font-size: 85%;\n    width: 95%;\n}\n)EOF\";\n\n\n/*\n* Also the JavaScript will be added at the bottom of body\n* In this example a simple 'click' event listener will be added for the button\n* with id='fetch' (added as HTML). The listener will execute the function 'fetchEndpoint'\n* in order to fetch a remote resource and show the response in a text box.\n*\n* The instruction $('<id-name>') is a \"Jquery like\" selector already defined\n* so you can use for your purposes:\n*      var $ = function(el) {\n*        return document.getElementById(el);\n*      };\n*/\ninline const char custom_script[] PROGMEM = R\"EOF(\nfunction fetchEndpoint() {\n  var mt;\n  document.getElementsByName('httpmethod').forEach(el => {\n    if (el.checked)\n      mt = el.value;\n  })\n\n  var url = $('url').value + mt.toLowerCase();\n  var bd = (mt != 'GET') ? 'body: \"\"' : '';\n  var options = {\n    method: mt,\n    bd\n  };\n  fetch(url, options)\n  .then(response => response.text())\n  .then(txt => {\n    $('payload').innerHTML = txt;\n  });\n}\n\n$('fetch').addEventListener('click', fetchEndpoint);\n)EOF\";\n\n\ninline const char base64_logo[] PROGMEM = R\"EOF(\niVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAAABGdBTUEAALGPC/xhBQAAAAFzUkdC\nAK7OHOkAAAIKUExURQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIhNjGoAAACtdFJOUwBl/fwyM2b+Bfn7mRAHBgEa+veYfx4C\nPNsD6Lbr3T49T6zLhOYOzN7uWmx671yOjR0PU1EJTtTzaj8x9hP1wFDwfrg5gC1fIXeTYrIEVQry\n+I+crg3XFoE1lFnR07HShyVdbW7f1cdo5aPxq8n0cO1xxJsRW2kM52QYwRkLYa0v4LNYb2BEUpd5\n1siCFIaVpbAm7FbkiJKmF2ODxmfqmldJNn0gG0rCRtpNxUVMXLiLfgAABc5JREFUaN7tWflXE0kQ\nbifAQAgJBAiBhAQMEATCfYsKoigIKIKioogHgnjggfe967Guq3vf933O/7gz1T0zfUyGwA4/7Hup\nN/BSNT319VfTXd1TjVBa0pKWtPx/xHtkYTh0vGyrJG0tOx4aXvjR66T34jM7uxRFZq5LO88Ur91T\nfZFoyzu13cV7Vy/1n2v7qTyxfVF9cv/dsryLM1W/UybbSFl9Ne9flruT+e9wKRxC9tthse8GA7jC\nl7NZ/4ri7rD2v3u/9hiNUNKpKKsBKPJ8lOm/atm/28q/J4hZN+TrwW9yySmJ1Ki/ivwGbAl6LAAq\ncb+2lRB9Tw10kO24VFAg8QzUq2aPznkbtlwQ/Y+AL9nVRvRIWGYA3Ocahwabb3q9N5sHh5q+LmAD\nFo6Qx9rIiBsRhvoSJreX6K1+Ogg5J461sO1b3vogh27hbyU39mK9n58kT3BnK4m6xa33Tv2rjeVb\nvbT8r2opEu4txPwQW/axjStk8HWWvJwdZgQU/0lPsnHtOek3o1SwgxjP4ndUwWQaPIJcPU4klh48\n+IJ0tjqNO3vUmdR1FHOqMy3tVWC6Ve0MQMstAKhqNyx1+M0/cCr7PsD+DAql5fBaZkudAiidBYfl\nusMKPAxanVtAWvHQ0gdSCAjNB5wDCMyDyxCZMC5gkEUytCg+/blR2qry91k3UyULGLjxBL0CSv99\n7P9SjiC9cKd5IuinrW8Q6mWahff9agDc74dOXwHlL6BTSAjkiOk4Q7sx1sVZ1Qcy+ZafG0toIehX\ntZ+HcdqJGgBshlb/NIDf3fyCk6sCCOvQ+zpABCeow1peg3tVAVuA7LiSEoD8gz5Sq8CiZcCDQOYg\nsg3Rn6LVKkSybCRR7Paa+usQQH2MbBm8llNjoHykA3wP6iGErrtgKbzDApzPouQ97Z1p1se0NWoA\nxEA/D79zRomje6C6rqMeoBJHbIg2cTMnF6ybOWsmNcpQFl77jE1MHPQI6oZeVCKWgQigWQUAzEAH\nAAYGwEMI2g00AUDTzjOYBr0RTUIvEs4zSACDSXQRGkWdB4gAwLvkXcxxIYplUDKuz/yXWH/kYUO0\nAtYYF6IPQS9HeOvRh+znQRO7v/vtGc2AvpaMnNoHlmV0AG7krQKQ4KzSqyQAd83vCrAcQBIw8SH7\nVDFVyxuzkGWq+NIA8OGNMcJ7Wd8qDLRXyN6onbJicHHUBMBUUwwRQq8kLhetWADM3ENCiJaBSd8q\nIVLl2V2JsTYJIfrkp3ZqDPeBbRnFAXyOYxDLpGRc34o+wvpLYFBoMFgBa8bPPmaSzAGD8hQnGiM1\nXLrOsGylT7RJYJJYJRcxsplbcKwBEnBvEk1AL3Y5z2AaGDSiGyml63UAVJJ03UNShuMhKicLDlky\nv+CWzE2UqEtmlNYfc6PIEuCOBEumR1/0x+wnWq646L+2BxjTF32yv/jOfqLlitZ/7EN0zdi24I3X\nYmCNDOLZtgwCi8bGi2wdS9YG4P4b2QKUmFtHdBXIvFhTiLrGhEWflRfm5pds3xvG7bbvb2jd/8dE\nMzTupbf3jIw3AIPL+AMEb5xv232AlNK6kfN94pcHkdv0BwgKAUCnk59QnQAQMusImrQ5B/Ace6xg\nP2NnHKMQmGE/Y9UPcWD01CmAp3j41lGlBGBU5VApoRq7W2wXiiHDG1UMQd4gALgizpRzAIAp5+gD\nKW4WpMxi2ad2BSmzHVWQAumwLakdY0tqv1iW1GKWJbVKy5IaKl7CVUe9KPjcr1BVR6ui4AkmLZpF\nQWwRioJoBBMzyprRMJvecFlzyuudKh4cajxXwN79NmqUNbFhRGR8YYMLs8gTxI8xpeVUateK1GSW\nlrHFsrSsFcc1KaKL46mUrjtLzCcGwGJdHEeoQ83bctF/K+8P2JT34YCiSDygsAMQDygGlOQHFEmO\nWBLbk5T5rY9YBurXcUh0Wjwk6lrXIZHdMdc3n+nHXLOh4YUjjh5zpSUtaUnLBsu/XvNl5HUuawwA\nAAAASUVORK5CYII=\n)EOF\";"
  },
  {
    "path": "examples/customHTML/customHTML.ino",
    "content": "#include <FS.h>\r\n#include <LittleFS.h>\r\n#include <FSWebServer.h>   // https://github.com/cotestatnt/esp-fs-webserver\r\n\r\nconst char* hostname = \"myserver\";\r\n#define FILESYSTEM LittleFS\r\nFSWebServer server(FILESYSTEM, 80, hostname);\r\n\r\n#ifndef LED_BUILTIN\r\n#define LED_BUILTIN 2\r\n#endif\r\n\r\n// Test \"options\" values\r\nuint8_t ledPin = LED_BUILTIN;\r\nbool boolVar = true;\r\nbool boolVar2 = false;\r\nuint32_t longVar = 1234567890;\r\nfloat floatVar = 15.5F;\r\nString stringVar = \"Test option String\";\r\nString dropdownSelected = \"Item1\";\r\n// ThingsBoard variables\r\nString tb_deviceName = \"ESP Sensor\";\r\ndouble tb_deviceLatitude = 41.88505;\r\ndouble tb_deviceLongitude = 12.50050;\r\nString tb_deviceToken = \"xxxxxxxxxxxxxxxxxxx\";\r\nString tb_device_key = \"xxxxxxxxxxxxxxxxxxx\";\r\nString tb_secret_key = \"xxxxxxxxxxxxxxxxxxx\";\r\nString tb_serverIP = \"thingsboard.cloud\";\r\nuint16_t tb_port = 80;\r\n\r\n// Var labels (in /setup webpage)\r\n#define LED_LABEL \"The LED pin number\"\r\n#define BOOL_LABEL \"A bool variable\"\r\n#define LONG_LABEL \"A long variable\"\r\n#define FLOAT_LABEL \"A float variable\"\r\n#define STRING_LABEL \"A String variable\"\r\n#define DROPDOWN_TEST \"A dropdown listbox\"\r\n\r\n#define TB_DEVICE_NAME \"Device Name\"\r\n#define TB_DEVICE_LAT \"Device Latitude\"\r\n#define TB_DEVICE_LON \"Device Longitude\"\r\n#define TB_SERVER \"ThingsBoard server address\"\r\n#define TB_PORT \"ThingsBoard server port\"\r\n#define TB_DEVICE_TOKEN \"ThingsBoard device token\"\r\n#define TB_DEVICE_KEY \"Provisioning device key\"\r\n#define TB_SECRET_KEY \"Provisioning secret key\"\r\n\r\n// Timezone definition to get properly time from NTP server\r\n//n.u. #define MYTZ \"CET-1CEST,M3.5.0,M10.5.0/3\"\r\n//n.u. struct tm Time;\r\n\r\n/*\r\n* Include the custom HTML, CSS and Javascript to be injected in /setup webpage.\r\n* HTML code will be injected according to the order of options declaration.\r\n* CSS and JavaScript will be appended to the end of body in order to work properly.\r\n* In this manner, is also possible override the default element styles\r\n* like for example background color, margins, padding etc etc\r\n*/\r\n#include \"customElements.h\"\r\n#include \"thingsboard.h\"\r\n\r\n// Callback: notify user when the configuration file is saved\r\nvoid onConfigSaved(const char* path) {\r\n  Serial.printf(\"\\n[Config] File salvato: %s\\n\", path);\r\n}\r\n\r\n////////////////////////////////  Filesystem  /////////////////////////////////////////\r\nbool startFilesystem() {\r\n  if (FILESYSTEM.begin()){\r\n    server.printFileList(FILESYSTEM, \"/\", 1);\r\n    return true;\r\n  }\r\n  else {\r\n    Serial.println(\"ERROR on mounting filesystem. It will be reformatted!\");\r\n    FILESYSTEM.format();\r\n    ESP.restart();\r\n  }\r\n  return false;\r\n}\r\n\r\n////////////////////  Load application options from filesystem  ////////////////////\r\nbool loadOptions() {\r\n  if (FILESYSTEM.exists(server.getConfiFileName())) {\r\n    // Test \"options\" values\r\n    server.getOptionValue(LED_LABEL, ledPin);\r\n    server.getOptionValue(BOOL_LABEL, boolVar);\r\n    server.getOptionValue(BOOL_LABEL \"2\", boolVar2);\r\n    server.getOptionValue(LONG_LABEL, longVar);\r\n    server.getOptionValue(FLOAT_LABEL, floatVar);\r\n    server.getOptionValue(STRING_LABEL, stringVar);\r\n    server.getOptionValue(DROPDOWN_TEST, dropdownSelected);\r\n    // ThingsBoard variables\r\n    server.getOptionValue(TB_DEVICE_NAME, tb_deviceName);\r\n    server.getOptionValue(TB_DEVICE_LAT, tb_deviceLatitude);\r\n    server.getOptionValue(TB_DEVICE_LON, tb_deviceLongitude);\r\n    server.getOptionValue(TB_DEVICE_TOKEN, tb_deviceToken);\r\n    server.getOptionValue(TB_DEVICE_KEY, tb_device_key);\r\n    server.getOptionValue(TB_SECRET_KEY, tb_secret_key);\r\n    server.getOptionValue(TB_SERVER, tb_serverIP);\r\n    server.getOptionValue(TB_PORT, tb_port);\r\n    server.closeSetupConfiguration();  // Close configuration to free resources\r\n\r\n    Serial.println(\"\\nThis are the current values stored: \\n\");\r\n    Serial.printf(\"LED pin value: %d\\n\", ledPin);\r\n    Serial.printf(\"Bool value 1: %s\\n\", boolVar ? \"true\" : \"false\");\r\n    Serial.printf(\"Bool value 2: %s\\n\", boolVar2 ? \"true\" : \"false\");\r\n    Serial.printf(\"Long value: %u\\n\", longVar);\r\n    Serial.printf(\"Float value: %d.%d\\n\", (int)floatVar, (int)(floatVar*1000)%1000);\r\n    Serial.printf(\"String value: %s\\n\", stringVar.c_str());\r\n    Serial.printf(\"Dropdown selected: %s\\n\", dropdownSelected.c_str());\r\n    return true;\r\n  }\r\n  else {\r\n      Serial.println(\"Failed to parse configuration file\");            \r\n      return false;\r\n  }\r\n  return true;\r\n}\r\n\r\n\r\nvoid setup() {\r\n  pinMode(LED_BUILTIN, OUTPUT);\r\n  Serial.begin(115200);\r\n\r\n  // FILESYSTEM INIT\r\n  if (startFilesystem()){\r\n    // Load configuration (if not present, default will be created when webserver will start)\r\n    loadOptions();      \r\n  }\r\n\r\n  // Try to connect to WiFi (will start AP if not connected after timeout)\r\n  if (!server.startWiFi(10000)) {\r\n    Serial.println(\"\\nWiFi not connected! Starting AP mode...\");\r\n    server.startCaptivePortal(\"ESP_AP\", \"123456789\");\r\n  }\r\n\r\n  // Add custom HTTP request handlers to webserver\r\n  server.on(\"/reload\", HTTP_GET, [](){\r\n    server.send(200, \"text/plain\", \"Options loaded\");\r\n    loadOptions();\r\n    Serial.println(\"Application option loaded after web request\");\r\n  });\r\n\r\n  // Add a new options box\r\n  server.addOptionBox(\"My Options\");\r\n  server.addOption(LED_LABEL, ledPin);\r\n  server.addOption(LONG_LABEL, longVar);\r\n  // Float fields can be configured with min, max and step properties\r\n  server.addOption(FLOAT_LABEL, floatVar, 0.0, 100.0, 0.01);\r\n  server.addOption(STRING_LABEL, stringVar);\r\n  server.addOption(BOOL_LABEL, boolVar);\r\n  server.addOption(BOOL_LABEL \"2\", boolVar2);\r\n  static const char* dropItem[] = {\"Item1\", \"Item2\", \"Item3\"};\r\n  FSWebServer::DropdownList dropdownDef{ DROPDOWN_TEST, dropItem, 3, 0 };\r\n  server.addDropdownList(dropdownDef);\r\n\r\n  // Add a new options box with custom code injected\r\n  server.addOptionBox(\"Custom HTML\");\r\n  // How many times you need (for example one in different option box)\r\n  server.addHTML(custom_html, \"fetch-test\", /*overwrite*/ false);\r\n\r\n  // Add a new options box\r\n  server.addOptionBox(\"ThingsBoard\");\r\n  server.addOption(TB_DEVICE_NAME, tb_deviceName);\r\n  server.addOption(TB_DEVICE_LAT, tb_deviceLatitude, -180.0, 180.0, 0.00001);\r\n  server.addOption(TB_DEVICE_LON, tb_deviceLongitude, -180.0, 180.0, 0.00001);\r\n  server.addOption(TB_SERVER, tb_serverIP);\r\n  server.addOption(TB_PORT, tb_port);\r\n  server.addOption(TB_DEVICE_KEY, tb_device_key);\r\n  server.addOption(TB_SECRET_KEY, tb_secret_key);\r\n  server.addOption(TB_DEVICE_TOKEN, tb_deviceToken);\r\n  server.addHTML(thingsboard_htm, \"ts\", /*overwrite file*/ false);\r\n\r\n  // CSS will be appended to HTML head\r\n  server.addCSS(custom_css, \"fetch\", /*overwrite file*/ false);\r\n  // Javascript will be appended to HTML body\r\n  server.addJavascript(custom_script, \"fetch\", /*overwrite file*/ false);\r\n  server.addJavascript(thingsboard_script, \"ts\", /*overwrite file*/ false);\r\n\r\n  // Add custom page title to /setup\r\n  server.setSetupPageTitle(\"Custom HTML Web Server\");\r\n  // Add custom logo to /setup page with custom size\r\n  //server.setLogoBase64(base64_logo, \"128\", \"128\", /*overwrite file*/ false);\r\n\r\n  // Enable ACE FS file web editor and add FS info callback function    \r\n  server.enableFsCodeEditor();\r\n\r\n  // Inform user when config.json is saved via /edit or /upload\r\n  server.setConfigSavedCallback(onConfigSaved);\r\n\r\n  // Start web server\r\n  server.begin();\r\n  Serial.print(F(\"\\n\\nWeb Server started on IP Address: \"));\r\n  Serial.println(server.getServerIP());\r\n  Serial.println(F(\r\n    \"\\nThis is \\\"customHTML.ino\\\" example.\\n\"\r\n    \"Open /setup page to configure optional parameters.\\n\"\r\n    \"Open /edit page to view, edit or upload example or your custom webserver source files.\"\r\n  ));\r\n  Serial.printf(\"Ready! Open http://%s.local in your browser\\n\", hostname);\r\n  if (server.isAccessPointMode())\r\n    Serial.print(F(\"Captive portal is running\"));\r\n}\r\n\r\n\r\nvoid loop() {\r\n  server.run();  // Handle client requests\r\n  \r\n  // Nothing to do here, just a small delay for task yield\r\n  delay(10);  \r\n}\r\n"
  },
  {
    "path": "examples/customHTML/thingsboard.h",
    "content": "#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 valid <b>device token</b> press button \"Device Provisioning\" to start procedure in order to get a new token from ThingsBoard server.<br>\n  <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.\n  <div class='btn-bar'>\n    <a id=device-provisioning class='btn'>\n        <div class=svg>\n          <svg class='icon' viewBox='0 0 24 24'>\n            <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' />\n          </svg>\n        </div>\n        <span> Device provisioning</span>\n    </a>\n  </div>\n</div>\n)EOF\";\n\n\ninline const char thingsboard_script[] PROGMEM = R\"EOF(\nconst TB_DEVICE_NAME = 'Device Name';\nconst TB_DEVICE_LAT = 'Device Latitude';\nconst TB_DEVICE_LON = 'Device Longitude';\nconst TB_SERVER = 'ThingsBoard server address';\nconst TB_PORT = 'ThingsBoard server port';\nconst TB_DEVICE_TOKEN = 'ThingsBoard device token';\nconst TB_DEVICE_KEY = 'Provisioning device key';\nconst TB_SECRET_KEY = 'Provisioning secret key';\n\nfunction getUrl(host, port) {\n  var url;\n  if (port === '80')\n    url = 'http://' + host + '/api/v1/';\n  else if (port === '443')\n    url = 'https://' + host + '/api/v1/'\n  else\n    url = 'http://' + host + ':'+ port + '/api/v1/'\n  return url;\n}\n\nfunction deviceProvisioning() {\n  console.log('Device provisioning');\n  var server = $(TB_SERVER).value;\n  var port = $(TB_PORT).value;\n  var token = $(TB_DEVICE_TOKEN).value;\n  if (token === '')\n    token = 'xxxxx';\n  const url = 'https://corsproxy.io/?' + encodeURIComponent(getUrl(server, port) + token + '/attributes');\n  fetch(url, {\n    method: \"GET\"\n  })\n  .then((response) => {\n    if (response.ok) {\n      openModalMessage('Device provisioning', 'Device already provisioned. Do you want update device client attributes?', setDeviceClientAttribute);\n      return;\n    }\n    throw new Error('Device token not present. Provisioning new device');\n  })\n  .catch((error) => {\n    openModalMessage('New device', 'A new device will be provisioned on ThingsBoard server', createNewDevice);\n  });\n}\n\nfunction createNewDevice() {\n  var server = $(TB_SERVER).value;\n  var port = $(TB_PORT).value;\n  var key = $(TB_DEVICE_KEY).value;\n  var secret = $(TB_SECRET_KEY).value;\n  var name = $(TB_DEVICE_NAME).value;\n  const url = 'https://corsproxy.io/?' + encodeURIComponent(getUrl(server, port) + 'provision');\n  var payload = {\n    'deviceName': name,\n    'provisionDeviceKey': key,\n    'provisionDeviceSecret': secret\n  };\n  fetch(url, {\n    headers: {\n      'Accept': 'application/json',\n      'Content-Type': 'application/json'\n    },\n    method: 'POST',\n    body: JSON.stringify(payload)\n  })\n  .then(response => response.json())\n  .then(obj => {\n    var token = $(TB_DEVICE_TOKEN);\n    token.focus();\n    token.value = obj.credentialsValue;\n    options[token.id] = token.value;     // Manual update, because, it doesn't fire \"change\" event...\n    openModal('Write device attributes', 'Device provisioned correctly.<br>Do you want to set client attributes on ThingsBoard server?', setDeviceClientAttribute);\n  });\n}\n\n\nfunction setDeviceClientAttribute(){\n  var server = $(TB_SERVER).value;\n  var port = $(TB_PORT).value;\n  var token = $(TB_DEVICE_TOKEN).value;\n  var name = $(TB_DEVICE_NAME).value;\n  var latitude = $(TB_DEVICE_LAT).value;\n  var longitude = $(TB_DEVICE_LON).value;\n  const url = 'https://corsproxy.io/?' + encodeURIComponent(getUrl(server, port) + token + '/attributes');\n  var payload = {\n    'DeviceName': name,\n    'latitude': latitude,\n    'longitude': longitude,\n  };\n  fetch(url, {\n    method: 'POST',\n    body: JSON.stringify(payload),\n  })\n  .then(response => response.text())\n  .then(() => {\n    openModalMessage('Device properties', 'Device client properties updated!');\n  });\n}\n$('device-provisioning').addEventListener('click', deviceProvisioning);\n)EOF\";\n"
  },
  {
    "path": "examples/customOptions/.gitignore",
    "content": ".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",
    "content": "#include <FS.h>\r\n#include <LittleFS.h>\r\n#include <FSWebServer.h>  //https://github.com/cotestatnt/esp-fs-webserver\r\n\r\n// Timezone definition to get properly time from NTP server\r\n#define MYTZ \"CET-1CEST,M3.5.0,M10.5.0/3\"\r\nstruct tm Time;\r\n\r\n#define FILESYSTEM LittleFS\r\nFSWebServer server(FILESYSTEM, 80, \"myserver\");\r\n\r\n// Define built-in LED if not defined by board (eg. generic dev boards)\r\n#ifndef LED_BUILTIN\r\n#define LED_BUILTIN 2\r\n#endif\r\n\r\n#ifndef BOOT_PIN\r\n#define BOOT_PIN    0\r\n#endif\r\n\r\n// Labels used in /setup webpage for options\r\n#define LED_LABEL \"The LED pin number\"\r\n#define BOOL_LABEL \"A bool variable\"\r\n#define BOOL_LABEL2 \"A second bool variable\"\r\n#define LONG_LABEL \"A long variable\"\r\n#define FLOAT_LABEL \"A float variable\"\r\n#define STRING_LABEL \"A String variable\"\r\n#define DROPDOWN_LABEL \"Days of week\"\r\n#define BRIGHTNESS_LABEL \"Brightness\"\r\n\r\n// Test \"options\" values\r\nuint8_t ledPin = LED_BUILTIN;\r\nbool boolVar = true;\r\nbool boolVar2 = false;\r\nuint32_t longVar = 1234567890;\r\nfloat floatVar = 15.51F;\r\nString stringVar = \"Test option String\";\r\n\r\n// Add a dropdown list box in /setup page\r\nconst char* days[] = {\"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\", \"Sunday\"};\r\nuint8_t daySelected = 2;// Default to \"Wednesday\"\r\nSetupConfig::DropdownList dayOfWeek{ DROPDOWN_LABEL, days, 7, daySelected}; \r\n\r\n// Add a slider in /setup page\r\nSetupConfig::Slider brightness{ BRIGHTNESS_LABEL, 0.0, 100.0, 1.0, 50.0 };\r\n\r\nstatic const char reload_btn_htm[] PROGMEM = R\"EOF(\r\n<div class=\"btn-bar\">\r\n  <a class=\"btn\" id=\"reload-btn\">Reload options</a>\r\n</div>\r\n)EOF\";\r\n\r\nstatic const char reload_btn_script[] PROGMEM = R\"EOF(\r\n/* Add click listener to button */\r\nconst reloadCfg = () => {\r\n  console.log('Reload configuration options');\r\n  fetch('/reload')\r\n  .then((response) => {\r\n    if (response.ok) {\r\n      openModal('Options loaded', 'Options was reloaded from configuration file');\r\n      return;\r\n    }\r\n    throw new Error('Something goes wrong with fetch');\r\n  })\r\n  .catch((error) => {\r\n    openModal('Error', 'Something goes wrong with your request');\r\n  });\r\n};\r\ndocument.getElementById('reload-btn').addEventListener('click', reloadCfg);\r\n)EOF\";\r\n\r\n\r\n/////////// Callback: notify user when the configuration file is saved  /////////////\r\nvoid onConfigSaved(const char* path) {\r\n  Serial.printf(\"\\n[Config] File salvato: %s\\n\", path);\r\n}\r\n\r\n////////////////////////////////  Filesystem  /////////////////////////////////////////\r\nbool startFilesystem() {\r\n  if (FILESYSTEM.begin()){\r\n    server.printFileList(FILESYSTEM, \"/\", 2);\r\n    return true;\r\n  }\r\n  else {\r\n    Serial.println(\"ERROR on mounting filesystem. It will be reformatted!\");\r\n    FILESYSTEM.format();\r\n    ESP.restart();\r\n  }\r\n  return false;\r\n}\r\n\r\n////////////////////  Load application options from filesystem  ////////////////////\r\nbool loadOptions() {\r\n  if (FILESYSTEM.exists(server.getConfiFileName())) {\r\n    server.getOptionValue(LED_LABEL, ledPin);\r\n    server.getOptionValue(BOOL_LABEL, boolVar);\r\n    server.getOptionValue(BOOL_LABEL2, boolVar2);\r\n    server.getOptionValue(LONG_LABEL, longVar);\r\n    server.getOptionValue(FLOAT_LABEL, floatVar);\r\n    server.getOptionValue(STRING_LABEL, stringVar);\r\n    server.getDropdownSelection(dayOfWeek);    \r\n    server.getSliderValue(brightness);\r\n    server.closeSetupConfiguration();  // Close configuration to free resources\r\n\r\n    Serial.println(\"\\nThis are the current values stored: \\n\");\r\n    Serial.printf(\"LED pin value: %d\\n\", ledPin);\r\n    Serial.printf(\"Bool value: %s\\n\", boolVar ? \"true\" : \"false\");\r\n    Serial.printf(\"Bool value2: %s\\n\", boolVar2 ? \"true\" : \"false\");\r\n    Serial.printf(\"Long value: %u\\n\", longVar);\r\n    Serial.printf(\"Float value: %3.2f\\n\", floatVar);\r\n    Serial.printf(\"String value: %s\\n\", stringVar.c_str());\r\n    Serial.printf(\"Dropdown selected value: %s\\n\", days[dayOfWeek.selectedIndex]);\r\n    Serial.printf(\"Slider value: %3.2f\\n\\n\", brightness.value);\r\n    return true;\r\n  }\r\n  else\r\n    Serial.println(F(\"Config file not exist\"));\r\n  return false;\r\n}\r\n\r\n\r\n////////////////////////////  HTTP Request Handlers  ////////////////////////////////////\r\nvoid handleLoadOptions() {\r\n  server.send(200, \"text/plain\", \"Options loaded\");\r\n  loadOptions();\r\n  Serial.println(\"Application option loaded after web request\");\r\n} \r\n\r\n\r\nvoid setup() {\r\n  pinMode(LED_BUILTIN, OUTPUT);\r\n  pinMode(BOOT_PIN, INPUT_PULLUP);\r\n  Serial.begin(115200);\r\n\r\n  // FILESYSTEM INIT\r\n  if (startFilesystem()){\r\n    // Load configuration (if not present, default will be created when webserver will start)\r\n    loadOptions();\r\n  }\r\n\r\n  // Default firmware version is set to compile time, but you can edit with a custom string\r\n  // Custom firmware version -> Major.Minor.Build (build is set to compile date YYYYMMDDHHMM)\r\n  String version = \"1.0.\" + String(BUILD_TIMESTAMP);\r\n  server.setFirmwareVersion(version);\r\n\r\n  // Try to connect to WiFi (will start AP if not connected after timeout)\r\n  if (!server.startWiFi(10000)) {\r\n    Serial.println(\"\\nWiFi not connected! Starting AP mode...\");\r\n    server.startCaptivePortal(\"ESP_AP\", \"123456789\", \"/setup\");\r\n  }\r\n\r\n  // Add custom page handler\r\n  server.on(\"/reload\", HTTP_GET, handleLoadOptions);\r\n\r\n  // Configure /setup page and start Web Server\r\n  server.addOptionBox(\"My Options\");\r\n  server.addOption(BOOL_LABEL, boolVar);\r\n  server.addOption(BOOL_LABEL2, boolVar2);\r\n  // You can also add a comment to the option,\r\n  // which will be displayed in the UI as helper text. \r\n  server.addOption(LED_LABEL, ledPin, \"Select the GPIO for the LED\");\r\n  server.addOption(LONG_LABEL, longVar, \"Enter a long integer value\");\r\n  server.addOption(FLOAT_LABEL, floatVar, 1.0, 100.0, 0.01);\r\n  server.addOption(STRING_LABEL, stringVar, \"Enter a custom string\");\r\n  server.addDropdownList(dayOfWeek);\r\n  server.addSlider(brightness);  \r\n  server.addHTML(reload_btn_htm, \"buttons\", /*overwrite*/ false);\r\n  server.addJavascript(reload_btn_script, \"js\", /*overwrite*/ false);\r\n\r\n  // Enable ACE FS file web editor and add FS info callback function    \r\n#ifdef ESP32\r\n  server.enableFsCodeEditor([](fsInfo_t* fsInfo) {\r\n    fsInfo->fsName = \"LittleFS\";\r\n    fsInfo->totalBytes = LittleFS.totalBytes();\r\n    fsInfo->usedBytes = LittleFS.usedBytes();\r\n  });\r\n#else\r\n  // ESP8266 core support LittleFS by default\r\n  server.enableFsCodeEditor();\r\n#endif\r\n\r\n  // set /setup and /edit page authentication\r\n  server.setAuthentication(\"admin\", \"admin\");\r\n\r\n  // Inform user when config.json is saved via /edit or /upload\r\n  server.setConfigSavedCallback(onConfigSaved);\r\n\r\n  // Start server\r\n  server.begin();\r\n  Serial.print(F(\"\\nESP Web Server started on IP Address: \"));\r\n  Serial.println(server.getServerIP());\r\n  Serial.println(F(\r\n      \"\\nThis is \\\"customOptions.ino\\\" example.\\n\"\r\n      \"Open /setup page to configure optional parameters.\\n\"\r\n      \"Open /edit page to view, edit or upload example or your custom webserver source files.\"\r\n  ));\r\n}\r\n\r\nvoid loop() {\r\n  server.handleClient();\r\n  if (server.isAccessPointMode())\r\n    server.updateDNS();\r\n\r\n  // Keep BOOT_PIN pressed 5 seconds to clear application options\r\n  static uint32_t buttonPressStart = 0;\r\n  static bool buttonPressed = false;\r\n  \r\n  if (digitalRead(BOOT_PIN) == LOW) {\r\n    if (!buttonPressed) {\r\n      buttonPressed = true;\r\n      buttonPressStart = millis();\r\n    } \r\n    else if (millis() - buttonPressStart >= 5000) {\r\n      Serial.println(\"\\nClearing application options...\");\r\n      server.clearConfigFile();\r\n      delay(1000);\r\n      ESP.restart();\r\n    }\r\n  } else {\r\n    buttonPressed = false;\r\n  }\r\n\r\n  // Nothing to do here, just a small delay for task yield\r\n  delay(10);  \r\n}\r\n"
  },
  {
    "path": "examples/customOptions/partitions.csv",
    "content": "# Name,   Type, SubType, Offset,  Size, Flags\nnvs,      data, nvs,     0x9000,  0x5000,\notadata,  data, ota,     0xe000,  0x2000,\napp0,     app,  ota_0,   0x10000, 0x140000,\napp1,     app,  ota_1,   0x150000,0x140000,\nspiffs,   data, spiffs,  0x290000,0x160000,\ncoredump, data, coredump,0x3F0000,0x10000,\n"
  },
  {
    "path": "examples/customOptions/platformio.ini",
    "content": "; PlatformIO Project Configuration File\n;\n;   Build options: build flags, source filter\n;   Upload options: custom upload port, speed and extra flags\n;   Library options: dependencies, extra library storages\n;   Advanced options: extra scripting\n;\n; Please visit documentation for the other options and examples\n; https://docs.platformio.org/page/projectconf.html\n\n[platformio]\nsrc_dir = .\n\n\n[env:esp32-s3-devkitc1-n4r2]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32-s3-devkitc1-n4r2\nframework = arduino\nupload_speed = 921600\nboard_build.partitions = partitions.csv\nlib_extra_dirs = ../../\nlib_ignore = pio_examples\nmonitor_speed = 115200\n\n[env:esp32dev]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32dev\nframework = arduino\nupload_speed = 921600\nboard_build.partitions = partitions.csv\nlib_extra_dirs = ../../\nlib_ignore = pio_examples\n\n[env:esp8266-nodemcuv2]\nplatform = espressif8266\nboard = nodemcuv2\nframework = arduino\nupload_speed = 921600\nboard_build.partitions = partitions.csv\nlib_extra_dirs = ../../\nlib_ignore = pio_examples\n"
  },
  {
    "path": "examples/esp32-cam/.gitignore",
    "content": ".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",
    "content": "#pragma once\n#include <Arduino.h>\n#include \"esp_camera.h\"\n#include <esp_err.h>\n\n// Select camera model\n//#define CAMERA_MODEL_WROVER_KIT\n//#define CAMERA_MODEL_ESP_EYE\n//#define CAMERA_MODEL_M5STACK_PSRAM\n//#define CAMERA_MODEL_M5STACK_V2_PSRAM\n//#define CAMERA_MODEL_M5STACK_WIDE\n//#define CAMERA_MODEL_M5STACK_ESP32CAM\n#define CAMERA_MODEL_AI_THINKER\n//#define CAMERA_MODEL_TTGO_T_JOURNAL\n\n#if defined(CAMERA_MODEL_WROVER_KIT)\n  //\n  // ESP WROVER\n  // https://dl.espressif.com/dl/schematics/ESP-WROVER-KIT_SCH-2.pdf\n  //\n  #define PWDN_GPIO_NUM    -1\n  #define RESET_GPIO_NUM   -1\n  #define XCLK_GPIO_NUM    21\n  #define SIOD_GPIO_NUM    26\n  #define SIOC_GPIO_NUM    27\n  #define Y9_GPIO_NUM      35\n  #define Y8_GPIO_NUM      34\n  #define Y7_GPIO_NUM      39\n  #define Y6_GPIO_NUM      36\n  #define Y5_GPIO_NUM      19\n  #define Y4_GPIO_NUM      18\n  #define Y3_GPIO_NUM       5\n  #define Y2_GPIO_NUM       4\n  #define VSYNC_GPIO_NUM   25\n  #define HREF_GPIO_NUM    23\n  #define PCLK_GPIO_NUM    22\n  #define LED_PIN           2 // A status led on the RGB; could also use pin 0 or 4\n  #define LED_ON         HIGH //\n  #define LED_OFF         LOW //\n  // #define LAMP_PIN          x // No LED FloodLamp.\n\n#elif defined(CAMERA_MODEL_ESP_EYE)\n  //\n  // ESP-EYE\n  // https://twitter.com/esp32net/status/1085488403460882437\n  #define PWDN_GPIO_NUM    -1\n  #define RESET_GPIO_NUM   -1\n  #define XCLK_GPIO_NUM     4\n  #define SIOD_GPIO_NUM    18\n  #define SIOC_GPIO_NUM    23\n  #define Y9_GPIO_NUM      36\n  #define Y8_GPIO_NUM      37\n  #define Y7_GPIO_NUM      38\n  #define Y6_GPIO_NUM      39\n  #define Y5_GPIO_NUM      35\n  #define Y4_GPIO_NUM      14\n  #define Y3_GPIO_NUM      13\n  #define Y2_GPIO_NUM      34\n  #define VSYNC_GPIO_NUM    5\n  #define HREF_GPIO_NUM    27\n  #define PCLK_GPIO_NUM    25\n  #define LED_PIN          21 // Status led\n  #define LED_ON         HIGH //\n  #define LED_OFF         LOW //\n  // #define LAMP_PIN          x // No LED FloodLamp.\n\n#elif defined(CAMERA_MODEL_M5STACK_PSRAM)\n  //\n  // ESP32 M5STACK\n  //\n  #define PWDN_GPIO_NUM     -1\n  #define RESET_GPIO_NUM    15\n  #define XCLK_GPIO_NUM     27\n  #define SIOD_GPIO_NUM     25\n  #define SIOC_GPIO_NUM     23\n  #define Y9_GPIO_NUM       19\n  #define Y8_GPIO_NUM       36\n  #define Y7_GPIO_NUM       18\n  #define Y6_GPIO_NUM       39\n  #define Y5_GPIO_NUM        5\n  #define Y4_GPIO_NUM       34\n  #define Y3_GPIO_NUM       35\n  #define Y2_GPIO_NUM       32\n  #define VSYNC_GPIO_NUM    22\n  #define HREF_GPIO_NUM     26\n  #define PCLK_GPIO_NUM     21\n  // M5 Stack status/illumination LED details unknown/unclear\n  // #define LED_PIN            x // Status led\n  // #define LED_ON          HIGH //\n  // #define LED_OFF          LOW //\n  // #define LAMP_PIN          x  // LED FloodLamp.\n\n#elif defined(CAMERA_MODEL_M5STACK_V2_PSRAM)\n  //\n  // ESP32 M5STACK V2\n  //\n  #define PWDN_GPIO_NUM     -1\n  #define RESET_GPIO_NUM    15\n  #define XCLK_GPIO_NUM     27\n  #define SIOD_GPIO_NUM     22\n  #define SIOC_GPIO_NUM     23\n  #define Y9_GPIO_NUM       19\n  #define Y8_GPIO_NUM       36\n  #define Y7_GPIO_NUM       18\n  #define Y6_GPIO_NUM       39\n  #define Y5_GPIO_NUM        5\n  #define Y4_GPIO_NUM       34\n  #define Y3_GPIO_NUM       35\n  #define Y2_GPIO_NUM       32\n  #define VSYNC_GPIO_NUM    25\n  #define HREF_GPIO_NUM     26\n  #define PCLK_GPIO_NUM     21\n  // M5 Stack status/illumination LED details unknown/unclear\n  // #define LED_PIN            x // Status led\n  // #define LED_ON          HIGH //\n  // #define LED_OFF          LOW //\n  // #define LAMP_PIN          x  // LED FloodLamp.\n\n#elif defined(CAMERA_MODEL_M5STACK_WIDE)\n  //\n  // ESP32 M5STACK WIDE\n  //\n  #define PWDN_GPIO_NUM     -1\n  #define RESET_GPIO_NUM    15\n  #define XCLK_GPIO_NUM     27\n  #define SIOD_GPIO_NUM     22\n  #define SIOC_GPIO_NUM     23\n  #define Y9_GPIO_NUM       19\n  #define Y8_GPIO_NUM       36\n  #define Y7_GPIO_NUM       18\n  #define Y6_GPIO_NUM       39\n  #define Y5_GPIO_NUM        5\n  #define Y4_GPIO_NUM       34\n  #define Y3_GPIO_NUM       35\n  #define Y2_GPIO_NUM       32\n  #define VSYNC_GPIO_NUM    25\n  #define HREF_GPIO_NUM     26\n  #define PCLK_GPIO_NUM     21\n  // M5 Stack status/illumination LED details unknown/unclear\n  // #define LED_PIN            x // Status led\n  // #define LED_ON          HIGH //\n  // #define LED_OFF          LOW //\n  // #define LAMP_PIN          x  // LED FloodLamp.\n\n#elif defined(CAMERA_MODEL_M5STACK_ESP32CAM)\n  //\n  // Common M5 Stack without PSRAM\n  //\n  #define PWDN_GPIO_NUM     -1\n  #define RESET_GPIO_NUM    15\n  #define XCLK_GPIO_NUM     27\n  #define SIOD_GPIO_NUM     25\n  #define SIOC_GPIO_NUM     23\n  #define Y9_GPIO_NUM       19\n  #define Y8_GPIO_NUM       36\n  #define Y7_GPIO_NUM       18\n  #define Y6_GPIO_NUM       39\n  #define Y5_GPIO_NUM        5\n  #define Y4_GPIO_NUM       34\n  #define Y3_GPIO_NUM       35\n  #define Y2_GPIO_NUM       17\n  #define VSYNC_GPIO_NUM    22\n  #define HREF_GPIO_NUM     26\n  #define PCLK_GPIO_NUM     21\n  // Note NO PSRAM,; so maximum working resolution is XGA 1024×768\n  // M5 Stack status/illumination LED details unknown/unclear\n  // #define LED_PIN            x // Status led\n  // #define LED_ON          HIGH //\n  // #define LED_OFF          LOW //\n  // #define LAMP_PIN          x  // LED FloodLamp.\n\n#elif defined(CAMERA_MODEL_AI_THINKER)\n  //\n  // AI Thinker\n  // https://github.com/SeeedDocument/forum_doc/raw/master/reg/ESP32_CAM_V1.6.pdf\n  //\n  #define PWDN_GPIO_NUM     32\n  #define RESET_GPIO_NUM    -1\n  #define XCLK_GPIO_NUM      0\n  #define SIOD_GPIO_NUM     26\n  #define SIOC_GPIO_NUM     27\n  #define Y9_GPIO_NUM       35\n  #define Y8_GPIO_NUM       34\n  #define Y7_GPIO_NUM       39\n  #define Y6_GPIO_NUM       36\n  #define Y5_GPIO_NUM       21\n  #define Y4_GPIO_NUM       19\n  #define Y3_GPIO_NUM       18\n  #define Y2_GPIO_NUM        5\n  #define VSYNC_GPIO_NUM    25\n  #define HREF_GPIO_NUM     23\n  #define PCLK_GPIO_NUM     22\n  #define LED_PIN           33 // Status led\n  #define LED_ON           LOW // - Pin is inverted.\n  #define LED_OFF         HIGH //\n  #define LAMP_PIN           4 // LED FloodLamp.\n\n#elif defined(CAMERA_MODEL_TTGO_T_JOURNAL)\n  //\n  // LilyGO TTGO T-Journal ESP32; with OLED! but not used here.. :-(\n  #define PWDN_GPIO_NUM      0\n  #define RESET_GPIO_NUM    15\n  #define XCLK_GPIO_NUM     27\n  #define SIOD_GPIO_NUM     25\n  #define SIOC_GPIO_NUM     23\n  #define Y9_GPIO_NUM       19\n  #define Y8_GPIO_NUM       36\n  #define Y7_GPIO_NUM       18\n  #define Y6_GPIO_NUM       39\n  #define Y5_GPIO_NUM        5\n  #define Y4_GPIO_NUM       34\n  #define Y3_GPIO_NUM       35\n  #define Y2_GPIO_NUM       17\n  #define VSYNC_GPIO_NUM    22\n  #define HREF_GPIO_NUM     26\n  #define PCLK_GPIO_NUM     21\n  // TTGO T Journal status/illumination LED details unknown/unclear\n  // #define LED_PIN           33 // Status led\n  // #define LED_ON           LOW // - Pin is inverted.\n  // #define LED_OFF         HIGH //\n  // #define LAMP_PIN           4 // LED FloodLamp.\n\n#else\n  // Well.\n  // that went badly...\n  #error \"Camera model not selected, did you forget to uncomment it in myconfig?\"\n#endif\n\ncamera_config_t camera_config = {\n  .pin_pwdn = PWDN_GPIO_NUM,\n  .pin_reset = RESET_GPIO_NUM,\n  .pin_xclk = XCLK_GPIO_NUM,\n  .pin_sscb_sda = SIOD_GPIO_NUM,\n  .pin_sscb_scl = SIOC_GPIO_NUM,\n  .pin_d7 = Y9_GPIO_NUM,\n  .pin_d6 = Y8_GPIO_NUM,\n  .pin_d5 = Y7_GPIO_NUM,\n  .pin_d4 = Y6_GPIO_NUM,\n  .pin_d3 = Y5_GPIO_NUM,\n  .pin_d2 = Y4_GPIO_NUM,\n  .pin_d1 = Y3_GPIO_NUM,\n  .pin_d0 = Y2_GPIO_NUM,\n  .pin_vsync = VSYNC_GPIO_NUM,\n  .pin_href = HREF_GPIO_NUM,\n  .pin_pclk = PCLK_GPIO_NUM,\n  .xclk_freq_hz = 20000000,        //XCLK 20MHz or 10MHz\n  .ledc_timer = LEDC_TIMER_0,\n  .ledc_channel = LEDC_CHANNEL_0,\n  .pixel_format = PIXFORMAT_JPEG,  //YUV422,GRAYSCALE,RGB565,JPEG\n  .frame_size = FRAMESIZE_SXGA,    //QQVGA-UXGA Do not use sizes above QVGA when not JPEG\n  .jpeg_quality = 8,              //0-63 lower number means higher quality\n  .fb_count = 2,                    //if more than one, i2s runs in continuous mode. Use only with JPEG\n  .fb_location = CAMERA_FB_IN_PSRAM,\n  .grab_mode = CAMERA_GRAB_LATEST\n};\n\n\nint lampChannel = 7;           // a free PWM channel (some channels used by camera)\nconst int pwmfreq = 50000;     // 50K pwm frequency\nconst int pwmresolution = 9;   // duty cycle bit range\nconst int pwmMax = pow(2,pwmresolution)-1;\n\ninline esp_err_t init_camera() {\n  //initialize the camera\n  Serial.print(\"Camera init... \");\n  esp_err_t err = esp_camera_init(&camera_config);\n\n  if (err != ESP_OK) {\n    delay(100);  // need a delay here or the next serial o/p gets missed\n    Serial.printf(\"\\n\\nCRITICAL FAILURE: Camera sensor failed to initialise.\\n\\n\");\n    Serial.printf(\"A full (hard, power off/on) reboot will probably be needed to recover from this.\\n\");\n    return err;\n  } else {\n    Serial.println(\"succeeded\");\n\n    // Get a reference to the sensor\n    sensor_t* s = esp_camera_sensor_get();\n\n    // Dump camera module, warn for unsupported modules.\n    switch (s->id.PID) {\n      case OV9650_PID: Serial.println(\"WARNING: OV9650 camera module is not properly supported, will fallback to OV2640 operation\"); break;\n      case OV7725_PID: Serial.println(\"WARNING: OV7725 camera module is not properly supported, will fallback to OV2640 operation\"); break;\n      case OV2640_PID: Serial.println(\"OV2640 camera module detected\"); break;\n      case OV3660_PID: Serial.println(\"OV3660 camera module detected\"); break;\n      default: Serial.println(\"WARNING: Camera module is unknown and not properly supported, will fallback to OV2640 operation\");\n    }\n    s->set_sharpness(s, 1);\n    s->set_vflip(s, 1);\n    //s->set_hmirror(s, 1);\n\n  }\n  return ESP_OK;\n}\n"
  },
  {
    "path": "examples/esp32-cam/data/index.htm",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en-US\">\n    <head>\n        <meta charset=\"UTF-8\">\n        <meta http-equiv=\"refresh\" content=\"0; url=/www/index.htm\">\n        <script type=\"text/javascript\">\n            window.location.href = \"/www/index.htm\"\n        </script>\n        <title>Page Redirection</title>\n    </head>\n    <body>\n        <!-- Note: don't tell people to `click` the link, just tell them that it is a link. -->\n        If you are not redirected automatically, follow this <a href='/www/index.htm'>link</a>.\n    </body>\n</html>"
  },
  {
    "path": "examples/esp32-cam/data/www/app.js",
    "content": "const imgFolder = '/img/';\nvar fileList = document.getElementById('file-list');\nvar intervalID;\n\n// Load list of files every x milliseconds (used in order to get fresh list)\nvar myInterval = function(val){\n  if (val !== 0)\n    intervalID = setInterval(listFiles, val*1000 + 1000);\n  else\n    clearInterval(intervalID);\n};\n\n// Fetch the list of files and fill the filelist\nfunction listFiles() {\n  fetch('/list?dir=/img')      // Do the request\n  .then(response => response.json())    // Parse the response\n  .then(obj => {                        // DO something with response\n    fileList.innerHTML = '';\n    obj.forEach(function(entry, i) {\n      addEntry(entry.name);\n    });\n  });\n}\n\n// Load selected image inside the preview content\nfunction loadFile(filename) {\n  clearInterval(intervalID);\n  var name = document.getElementById(\"image-name\");\n  var content = document.getElementById(\"image-content\");\n\n  name.innerHTML = 'Image path: ' + filename;\n  content.src = filename;\n  content.alt = 'SD: ' + filename;\n\n  // Get the modal\n  var modal = document.getElementById(\"modal-container\");\n\n  // Get the image and insert it inside the modal - use its \"alt\" text as a caption\n  var img = document.getElementById(\"image-content\");\n  var modalImg = document.getElementById(\"modal-image\");\n  var captionText = document.getElementById(\"caption\");\n  img.onclick = function(){\n    modal.style.display = \"block\";\n    modalImg.src = this.src;\n    captionText.innerHTML = this.alt;\n  };\n\n  // Get the <span> element that closes the modal\n  var span = document.getElementsByClassName(\"close\")[0];\n\n  // When the user clicks on <span> (x), close the modal\n  span.onclick = function() {\n    modal.style.display = \"none\";\n  };\n}\n\n// Delete selected file in SD\nasync function deleteFile(filename) {\n  console.log(filename);\n  const data = new URLSearchParams();\n  data.append('path', filename);\n  fetch('/edit', {\n      method: 'DELETE',\n      body: data\n  });\n\n  // Update the file browser.\n  listFiles();\n}\n\nasync function deleteAll() {\n  let isExecuted = confirm(\"Are you sure to delete all files in /img/ folder?\");\n  if(isExecuted){\n    var ul = document.getElementById(\"file-list\");\n    var items = ul.getElementsByClassName(\"edit-file\");\n\n    for (var i=0; i<items.length; i++) {\n      console.log(\"Delete \" + items[i].innerHTML);\n      await deleteFile(imgFolder + items[i].innerHTML);\n    }\n  }\n}\n\n// Add a single entry to the filelist\nfunction addEntry(entryName) {\n  console.log(entryName);\n  var li = document.createElement('li');\n  var link = document.createElement('a');\n  link.innerHTML = entryName;\n  link.className = 'edit-file';\n  li.appendChild(link);\n\n  var delLink = document.createElement('a');\n  delLink.innerHTML = '<span class=\"delete\">&times;</span>';\n  delLink.className = 'delete-file';\n  li.appendChild(delLink);\n  fileList.insertBefore(li, fileList.firstChild);\n\n  // Setup an event listener that will load the file when the link is clicked.\n  link.addEventListener('click', function(e) {\n    e.preventDefault();\n    loadFile(imgFolder +  entryName);\n  });\n\n  // Setup an event listener that will delete the file when the delete link is clicked.\n  delLink.addEventListener('click', function(e) {\n    e.preventDefault();\n    deleteFile(imgFolder + entryName);\n  });\n\n}\n\n// Add the buttons event listeners\nvar getNew = document.getElementById('get-new');\nvar getInterval = document.getElementById('set-interval');\n\ngetNew.addEventListener('click', function(e) {\n  e.preventDefault();\n  fetch('/getPicture')                  // Do the request\n  .then(response => response.text())    // Parse the response\n  .then(txt => {                        // DO something with response\n    console.log(txt);\n    addEntry(txt);\n    loadFile(imgFolder + txt);\n  });\n});\n\ngetInterval.addEventListener('click', function(e) {\n  e.preventDefault();\n  let val = document.getElementById('interval').value;\n\n  fetch('/setInterval?val='+ val)       // Do the request\n  .then(response => response.text())    // Parse the response\n  .then(txt => {                        // DO something with response\n    console.log('Set interval ' + txt);\n    myInterval(val);\n  });\n});\n\n// Start the web page\nclearInterval(intervalID);\nlistFiles();\nloadFile('espressif.jpg');"
  },
  {
    "path": "examples/esp32-cam/data/www/index.htm",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\">\n  <title>ESP32-CAM-WEBPAGE.ino</title>\n\n  <!-- Cyustom page styling -->\n  <link rel=\"stylesheet\" href=\"styles.css\" />\n\n</head>\n  <body>\n    <main>\n      <div id=\"page-wrapper\" class=\"clearfix\">\n        <h1>ESP32-CAM web interface</h1><br>\n        \n        <form action=\"#\" method=\"POST\" id=\"file-form\">\n          <h2 id=\"image-name\">filename</h2>\n          <div class=\"field\">\n            <!-- The preview image -->\n            <img id=\"image-content\" style=\"width:100%\">\n            \n            <!-- The Modal -->\n            <div id=\"modal-container\" class=\"modal\">\n              <span class=\"close\">&times;</span>\n              <img class=\"modal-content\" id=\"modal-image\">\n              <div id=\"caption\"></div>\n            </div>\n          </div>\n          \n          <button id=\"get-new\">Get new image</button>\n          <div style=\"float: right\">\n            <label for=\"interval\">Set interval (seconds):</label>\n            <input type=\"number\" id=\"interval\" name=\"interval\" style=\"width: 75px\" value=300 title=\"Set 0 to disable\">\n            <button id=\"set-interval\" title=\"Set 0 to disable\">Set interval</button>\n          </div>\n        </form>\n    \n        <div id=\"files\">\n          <h2>Select image</h2>\n          <ul id=\"file-list\"></ul>\n          <input type=\"button\" onclick=\"deleteAll()\" value=\"Delete All\" class=\"delete-all\">  \n        </div>\n        \n        \n      </div>\n    </main>\n    \n    <script src=\"app.js\"></script>\n    \n  </body>\n</html>"
  },
  {
    "path": "examples/esp32-cam/data/www/styles.css",
    "content": "*, *:before, *:after {\n  -moz-box-sizing: border-box;\n  -webkit-box-sizing: border-box;\n  box-sizing: border-box;\n}\n\nhtml {\n  font-family: Helvetica, Arial, sans-serif;\n  font-size: 100%;\n  background: #333;\n  color: #33383D;\n  -webkit-font-smoothing: antialiased;\n}\n\n#page-wrapper {\n  width: 960px;\n  background: #FFF;\n  padding: 1.25rem;\n  margin: 1rem auto;\n  min-height: 300px;\n  border-top: 5px solid #69c773;\n  box-shadow: 0 2px 10px rgba(0,0,0,0.8);\n}\n\n\nh2 {\n  margin-top: 0;\n  font-size: 0.9rem;\n  letter-spacing: 1px;\n  color: #999;\n}\n\np {\n  font-size: 0.9rem;\n  margin: 0.5rem 0 1.5rem 0;\n}\n\na,\na:visited {\n  color: #08C;\n  text-decoration: none;\n}\n\na:hover,\na:focus {\n  color: #69c773;\n  cursor: pointer;\n}\n\na.delete-file,\na.delete-file:visited {\n  color: #CC0000;\n  margin-left: 0.5rem;\n  vertical-align: middle;\n}\n\n\n\n.field {\n  margin-bottom: 1rem;\n}\n\nbutton {\n  width: 150px;\n  display: inline-block;\n  border-radius: 3px;\n  border: none;\n  font-size: 0.9rem;\n  padding: 0.6rem 1em;\n  background: #86b32d;\n  border-bottom: 1px solid #5d7d1f;\n  color: white;\n  margin: 0 0.25rem;\n  text-align: center;\n}\n\nbutton:hover {\n  opacity: 0.75;\n  cursor: pointer;\n}\n\n#file-form {\n  width: 75%;\n  float: left;\n}\n\n#files {\n  width: 23%;\n  float: right;\n}\n\n#files ul {\n  margin: 0;\n  padding: 0.5rem 1rem;\n  max-height: 600px;\n  overflow-y: auto;\n  list-style: square;\n  background: #F7F7F7;\n  border: 1px solid #D9D9D9;\n  border-radius: 3px;\n  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.1);\n}\n\n#files li {\n  margin-left: 8px;\n  font-size: 14px;\n}\n\n\n/* Clearfix Utils */\n\n.clearfix {\n  *zoom: 1;\n}\n\n.clearfix:before,\n.clearfix:after {\n  display: table;\n  line-height: 0;\n  content: \"\";\n}\n\n.clearfix:after {\n  clear: both;\n}\n\n#image-content {\n  border-radius: 5px;\n  cursor: pointer;\n  transition: 0.3s;\n}\n\n#image-content:hover {opacity: 0.7;}\n\n/* The Modal (background) */\n.modal {\n  display: none; /* Hidden by default */\n  position: fixed; /* Stay in place */\n  z-index: 1; /* Sit on top */\n  padding-top: 100px; /* Location of the box */\n  left: 0;\n  top: 0;\n  width: 100%; /* Full width */\n  height: 100%; /* Full height */\n  overflow: auto; /* Enable scroll if needed */\n  background-color: rgb(0,0,0); /* Fallback color */\n  background-color: rgba(0,0,0,0.9); /* Black w/ opacity */\n}\n\n\n/* Modal Content (image) */\n.modal-content {\n  margin: auto;\n  display: block;\n  width: 90%;\n}\n\n/* Caption of Modal Image */\n#caption {\n  margin: auto;\n  display: block;\n  text-align: center;\n  color: #ccc;\n  padding: 10px 0;\n}\n\n/* Add Animation */\n.modal-content, #caption {  \n  -webkit-animation-name: zoom;\n  -webkit-animation-duration: 0.6s;\n  animation-name: zoom;\n  animation-duration: 0.6s;\n}\n\n@-webkit-keyframes zoom {\n  from {-webkit-transform:scale(0)} \n  to {-webkit-transform:scale(1)}\n}\n\n@keyframes zoom {\n  from {transform:scale(0)} \n  to {transform:scale(1)}\n}\n\n/* The Close Button */\n.close {\n  position: absolute;\n  top: 15px;\n  right: 35px;\n  color: #f1f1f1;\n  font-size: 40px;\n  font-weight: bold;\n  transition: 0.3s;\n}\n\n.close:hover,\n.close:focus {\n  color: #bbb;\n  text-decoration: none;\n  cursor: pointer;\n}\n\n.delete {\n  font-size: 30px;\n  font-weight: bold;\n  transition: 0.3s;\n}\n\n.delete-all{\n  color: #f44336;\n  font-size: 12px;\n  background-color: transparent;\n  background-repeat: no-repeat;\n  border: none;\n  cursor: pointer;\n  overflow: hidden;\n  outline: none;\n}\n/* 100% Image Width on Smaller Screens */\n@media only screen and (max-width: 700px){\n  .modal-content {\n    width: 100%;\n  }\n}\n"
  },
  {
    "path": "examples/esp32-cam/esp32-cam.ino",
    "content": "\n#include <FS.h>\n#include <SD_MMC.h>\n#include <LittleFS.h>\n#include <FSWebServer.h>   // https://github.com/cotestatnt/esp-fs-webserver/\n#include \"esp_camera.h\"\n#include \"soc/soc.h\"          // Brownout error fix\n#include \"soc/rtc_cntl_reg.h\" // Brownout error fix\n#if ESP_ARDUINO_VERSION_MAJOR >= 3\n  #include \"soc/soc_caps.h\"\n#endif\n// Local include files with camera pin definition and configuration\n#include \"camera_pins.h\"\n// Timezone definition to get properly time from NTP server\n#define MYTZ \"CET-1CEST,M3.5.0,M10.5.0/3\" \n\n// Set default file system type\n#define USE_LITTLEFS          1\n#define USE_SDMMC             2\n#define FILESYSTEM_TYPE       USE_LITTLEFS\n\n#if (FILESYSTEM_TYPE == USE_SDMMC)\n  #define FILESYSTEM SD_MMC\n#elif (FILESYSTEM_TYPE == USE_LITTLEFS)\n  #define FILESYSTEM LittleFS\n#endif\n\nFSWebServer server(FILESYSTEM, 80, \"esp32cam\");\n\n// Functions prototype\nvoid setInterval();\nvoid getPicture();\n\n// Struct for saving time datas (needed for time-naming the image files)\nstruct tm tInfo;\nuint16_t grabInterval = 0;  // Grab a picture every x seconds\nuint32_t lastGrabTime = 0;\nconst char* getFolder = \"/img\";\n\n///////////////////////////////////  SETUP  ///////////////////////////////////////\nvoid setup() {\n  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); // disable brownout detect\n\n  // Flash LED setup\n    pinMode(LAMP_PIN, OUTPUT); // set the lamp pin as output\n  #if ESP_ARDUINO_VERSION_MAJOR >= 3\n    // Nuova API: ledcAttach(pin, freq, resolution)\n    ledcAttach(LAMP_PIN, pwmfreq, pwmresolution);\n  #else\n    ledcSetup(lampChannel, pwmfreq, pwmresolution); // configure LED PWM channel\n    ledcAttachPin(LAMP_PIN, lampChannel);\n  #endif\n    setLamp(0); // set default value\n\n  Serial.begin(115200);\n  Serial.println();\n\n  // Try to connect to WiFi (will start AP if not connected after timeout)\n  if (!server.startWiFi(10000)) {\n    Serial.println(\"\\nWiFi not connected! Starting AP mode...\");\n    server.startCaptivePortal(\"ESP32CAM_AP\", \"123456789\", \"/setup\");\n  }\n\n  // Sync time with NTP\n  configTzTime(MYTZ, \"time.google.com\", \"time.windows.com\", \"pool.ntp.org\");\n\n\n#if (FILESYSTEM_TYPE == USE_SDMMC)\n  /*\n   Init onboard SD filesystem (format if necessary)\n   SD_MMC.begin(const char * mountpoint, bool mode1bit, bool format_if_mount_failed, int sdmmc_frequency, uint8_t maxOpenFiles)\n   To avoid led glowing, set mode1bit = true (SD HS_DATA1 is tied to GPIO4, the same of on-board flash led)\n  */\n  if (!SD_MMC.begin(\"/sdcard\", true, true, SDMMC_FREQ_HIGHSPEED, 5)) {\n    Serial.println(\"\\nSD Mount Failed.\\n\");\n  }\n  else if (!SD_MMC.exists(getFolder)) {\n    if(SD_MMC.mkdir(getFolder))\n      Serial.println(\"Dir created\");\n    else\n      Serial.println(\"mkdir failed\");\n  }\n#elif (FILESYSTEM_TYPE == USE_LITTLEFS)\n  if (!LittleFS.begin()) {\n    Serial.println(\"ERROR on mounting LittleFS. It will be formmatted!\");\n    LittleFS.format();\n    ESP.restart();\n  }\n  else if (!LittleFS.exists(getFolder)) {\n    if(LittleFS.mkdir(getFolder))\n      Serial.println(\"Dir created\");\n    else\n      Serial.println(\"mkdir failed\");     \n  }\n  Serial.println(\"LittleFS mounted successfully\");\n#endif\n  \n  // List all files stored in filesystem\n  server.printFileList(FILESYSTEM, \"/\", 1, Serial);\n\n  // Enable ACE FS file web editor and add FS info callback function\n  server.enableFsCodeEditor();\n\n  // Add custom handlers to webserver\n  server.on(\"/getPicture\", getPicture);\n  server.on(\"/setInterval\", setInterval);\n\n  // Start server with built-in websocket event handler\n  server.begin();\n  Serial.print(F(\"\\nESP Web Server started on IP Address: \"));\n  Serial.println(server.getServerIP());\n  Serial.println(F(\n    \"This is \\\"remoteOTA.ino\\\" example.\\n\"\n    \"Open /setup page to configure optional parameters.\\n\"\n    \"Open /edit page to view, edit or upload example or your custom webserver source files.\"\n  ));\n\n  // Init the camera module (according the camera_config_t defined)\n  init_camera();\n}\n\n///////////////////////////////////  LOOP  ///////////////////////////////////////\nvoid loop() {\n  server.run();\n\n  if (grabInterval) {\n    if (millis() - lastGrabTime > grabInterval *1000) {\n      lastGrabTime = millis();\n      getPicture();\n    }\n  }\n}\n\n//////////////////////////////////  FUNCTIONS//////////////////////////////////////\nvoid setInterval() {\n  if (server.hasArg(\"val\")) {\n    grabInterval = server.arg(\"val\").toInt();\n    Serial.printf(\"Set grab interval every %d seconds\\n\", grabInterval);\n  }\n  server.send(200, \"text/plain\", \"OK\");\n}\n\n// Lamp Control\nvoid setLamp(int newVal) {\n  if (newVal < 0) return;\n  // Apply exponential scaling to have a better visual effect\n  int brightness = round((pow(2, (1 + (newVal * 0.02))) - 2) / 6 * pwmMax);\n#if ESP_ARDUINO_VERSION_MAJOR >= 3\n  // ledcWrite(pin, value) with new API\n  ledcWrite(LAMP_PIN, brightness);\n#else\n  ledcWrite(lampChannel, brightness);\n#endif\n  Serial.print(\"Lamp: \");\n  Serial.print(newVal);\n  Serial.print(\"%, pwm = \");\n  Serial.println(brightness);\n}\n\n// Send a picture taken from CAM to a Telegram chat\nvoid getPicture() {\n\n  // Take Picture with Camera;\n  Serial.println(\"Camera capture requested\");\n\n  // Take Picture with Camera and store in ram buffer fb\n  setLamp(100);\n  delay(100);\n  camera_fb_t *fb = esp_camera_fb_get();\n  setLamp(0);\n\n  if (!fb) {\n    Serial.println(\"Camera capture failed\");    \n    server.send(500, \"text/plain\", \"ERROR. Image grab failed\");\n    return;\n  }\n\n  // Keep files on SD memory, filename is time based (YYYYMMDD_HHMMSS.jpg)\n  // Embedded filesystem is too small to keep all images, overwrite the same file\n  char filename[20];\n  time_t now = time(nullptr);\n  tInfo = *localtime(&now);\n  strftime(filename, sizeof(filename), \"%Y%m%d_%H%M%S.jpg\", &tInfo);\n\n  char filePath[30];\n  strcpy(filePath, getFolder);\n  strcat(filePath, \"/\");\n  strcat(filePath, filename);\n  File file = FILESYSTEM.open(filePath, \"w\");\n  if (!file) {\n    Serial.println(\"Failed to open file in writing mode\");\n    server.send(500, \"text/plain\", \"ERROR. Image grab failed\");\n    return;\n  }\n  // size_t _jpg_buf_len = 0;\n  // uint8_t *_jpg_buf = NULL;\n  // bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len);\n  file.write(fb->buf, fb->len);\n  file.close();\n  Serial.printf(\"Saved file to path: %s - %zu bytes\\n\", filePath, fb->len);\n\n  // Clear buffer\n  esp_camera_fb_return(fb);\n  server.send(200, \"text/plain\", filename);\n}\n\n\n"
  },
  {
    "path": "examples/esp32-cam/partitions.csv",
    "content": "# Name,   Type, SubType, Offset,  Size, Flags\nnvs,      data, nvs,     0x9000,  0x5000,\notadata,  data, ota,     0xe000,  0x2000,\napp0,     app,  ota_0,   0x10000, 0x140000,\napp1,     app,  ota_1,   0x150000,0x140000,\nspiffs,   data, spiffs,  0x290000,0x160000,\ncoredump, data, coredump,0x3F0000,0x10000,\n"
  },
  {
    "path": "examples/esp32-cam/platformio.ini",
    "content": "; PlatformIO Project Configuration File\n;\n;   Build options: build flags, source filter\n;   Upload options: custom upload port, speed and extra flags\n;   Library options: dependencies, extra library storages\n;   Advanced options: extra scripting\n;\n; Please visit documentation for the other options and examples\n; https://docs.platformio.org/page/projectconf.html\n\n[platformio]\nsrc_dir = .\n\n\n# Questo esempio è compatibile solo con schede ESP32 dotate di CAM (ad es. AI Thinker, ESP32-CAM, TTGO T-Journal, ecc.)\n# Scegli la board corretta e decommenta la sezione corrispondente, oppure aggiungi la tua board compatibile.\n\n[env:esp32cam]\nplatform = espressif32\nboard = esp32cam\nframework = arduino\nupload_speed = 921600\nboard_build.partitions = partitions.csv\nlib_extra_dirs = ../../\nlib_ignore = pio_examples\n\n# Esempio per AI Thinker (decommenta se necessario)\n# [env:esp32cam-ai-thinker]\n# platform = espressif32\n# board = esp32cam\n# framework = arduino\n# upload_speed = 921600\n# board_build.partitions = partitions.csv\n# lib_extra_dirs = ../../\n# lib_ignore = pio_examples\n"
  },
  {
    "path": "examples/gpio_list/.gitignore",
    "content": ".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",
    "content": "<!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=\"viewport\" content=\"width=device-width, initial-scale=1\">\r\n    <link rel=stylesheet href=/pico.classless.min.css>\r\n    <style>\r\n      body { margin: 40px; }\r\n      #container { max-width: 680px; margin: 0 auto; text-align: center; border-radius: var(--border-radius); }\r\n      button { max-width: 70%; }\r\n      td, th{ text-align: center; }\r\n      th { font-weight: bold; }\r\n    </style>\r\n  </head>\r\n  <body>\r\n    <div id=\"container\">\r\n      <article class=\"grid\">\r\n        <!--<img src=\"img/logo.svg\" alt=\"Espressif logo\" width=\"100\">-->\r\n        <svg width=128px height=128px viewBox=\"0 0 420 420\">\r\n          <g fill=\"#e7352c\">\r\n            <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\"/>\r\n            <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\"/>\r\n            <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\"/>\r\n          </g>\r\n        </svg>\r\n\r\n        <h3 class=\"box\">GPIO status list</h3>\r\n        <div class=\"box\">\r\n          <table>\r\n            <thead>\r\n              <tr>\r\n                <th>Pin Name</th>\r\n                <th>Pin number</th>\r\n                <th>Type</th>\r\n                <th>Level</th>\r\n              </tr>\r\n            </thead>\r\n            <tbody id='gpio-list'>\r\n            <!-- The content of table body will be injected here with javascript function updateGpiosList() -->\r\n            </tbody>\r\n          </table>\r\n        </div>\r\n      </article>\r\n    </div>\r\n    <script src=\"script.js\"></script>\r\n  </body>\r\n</html>\r\n"
  },
  {
    "path": "examples/gpio_list/data/script.js",
    "content": "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>' ;\nconst 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>';\n\n/**\n* Custom selector \"JQuery style\", but in plain \"Vanilla JS\"\n*/\nvar $ = function(el) {\n\treturn document.getElementById(el);\n};\n\n/**\n* Start a websocket client and set event callback functions\n*/\nfunction ws_connect() {\n  var ws = new WebSocket('ws://' + location.hostname + ':81/');\n  ws.onopen = function() {\n    ws.send('Connected - ' + new Date());\n    getGpioList();\n  };\n  ws.onmessage = function(e) {\n    parseMessage(e.data);\n  };\n  ws.onclose = function(e) {\n    setTimeout(function() {\n    ws_connect();\n    }, 1000);\n  };\n  ws.onerror = function(err) {\n    ws.close();\n  };\n  return ws;\n}\n\n/**\n* Send data \"cmds\" to ESP\n*/\nfunction sendCommand(cmd, pin, level) {\n  var data = {\n    cmd: cmd,\n    pin: parseInt(pin),\n    level: level\n  };\n  console.log(data);\n  connection.send(JSON.stringify(data));\n}\n\n/**\n* Parse messages receveid via websocket\n*/\nfunction parseMessage(msg) {\n  const obj = JSON.parse(msg);\n  if (typeof obj === 'object' && obj !== null) {\n    if (obj.esptime !== null) {\n      var date = new Date(0); // The 0 sets the date to epoch\n      if( date.setUTCSeconds(obj.esptime))\n        document.getElementById(\"esp-time\").innerHTML = date;\n    }\n    updateGpiosList(obj);\n  }\n}\n\n/**\n* Read GPIO list status\n*/\nfunction getGpioList() {\n  fetch('/getGpioList')                 // Do the request\n  .then(response => response.json())    // Parse the response\n  .then(obj => {                        // DO something with response\n    updateGpiosList(obj);\n  });\n}\n\n\n/**\n* Iterate to the gpio list passed as parameter anc create DOMs dinamically\n*/\nfunction updateGpiosList(elems) {\n\n  // Get reference to gpio-list element and clear content\n  const list = document.querySelector('#gpio-list');\n  list.innerHTML = \"\";\n\n  // Draw all input rows\n  const inputs = Object.entries(elems).filter((item) => item[1].type === 'input');\n\n  inputs.forEach(el => {\n    const obj = el[1];\n    var lbl = obj.level ? `${svgLightOn} HIGH` : `${svgLightOff}  LOW`;\n    // Create a single row with all columns\n    var row = document.createElement('tr');\n\t  row.innerHTML  = '<td>' + obj.label + '</td>';\n    row.innerHTML += '<td>' + obj.pin + '</td>';\n    row.innerHTML += '<td style=\"text-align:center;\">' + obj.type + '</td>';\n    row.innerHTML += `<td style=\"text-align:center;\"><label id=\"pin-${obj}\" for=\"\">${lbl}</label></td>` ;\n    // Append this row to list\n    list.appendChild(row);\n  });\n\n  // Draw all output rows\n  const outputs = Object.entries(elems).filter((item) => item[1].type === 'output');\n\n  outputs.forEach(el => {\n    const obj = el[1];\n    var lbl = obj.level ? ` checked>Turn OFF` : `>Turn ON`;\n    // Create a single row with all columns\n    var row = document.createElement('tr');\n\t  row.innerHTML  = '<td>' + obj.label + '</td>';\n    row.innerHTML += '<td>' + obj.pin + '</td>';\n    row.innerHTML += '<td style=\"text-align:center;\">' + obj.type + '</td>';\n    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>`;\n    // Append this row to list\n    list.appendChild(row);\n  });\n\n  // Add event listener to each out switch\n  const outSwitches = document.querySelectorAll('input[type=\"checkbox\"]');\n  outSwitches.forEach( (outSw) => {\n    outSw.addEventListener('change', function() {\n      sendCommand('writeOut', this.dataset.pin, this.checked);\n    });\n  });\n}\n\nvar connection = ws_connect();\nwindow.addEventListener('load', getGpioList);\n"
  },
  {
    "path": "examples/gpio_list/gpio_list.ino",
    "content": "#include <FS.h>\r\n#include <LittleFS.h>\r\n#include <FSWebServer.h>   // https://github.com/cotestatnt/esp-fs-webserver/\r\n\r\n#define FILESYSTEM LittleFS\r\nFSWebServer server(FILESYSTEM, 80);\r\n  \r\n#ifndef LED_BUILTIN\r\n  #define LED_BUILTIN 2 // Pin for built-in LED on ESP32  \r\n#endif\r\n\r\n#ifndef BOOT_PIN\r\n  #define BOOT_PIN 0 // Pin for BOOT button on ESP32\r\n#endif  \r\n\r\n// Define a struct for store all info about each gpio\r\nstruct gpio_type {\r\n  const char* type;\r\n  const char* label;\r\n  int pin;\r\n  bool level;\r\n};\r\n\r\n// Define an array of struct GPIO and initialize with values\r\n\r\n/* ESP8266 - Wemos D1-mini */\r\n/*\r\n  gpio_type gpios[] = {\r\n  {\"input\", \"INPUT 5\", D5},\r\n  {\"input\", \"INPUT 6\", D6},\r\n  {\"input\", \"INPUT 7\", D7},\r\n  {\"output\", \"OUTPUT 2\", D2},\r\n  {\"output\", \"OUTPUT 3\", D3},\r\n  {\"output\", \"LED BUILTIN\", LED_BUILTIN} // Led ON with signal LOW usually\r\n  };\r\n*/\r\n\r\n/* ESP32 - NodeMCU-32S */\r\ngpio_type gpios[] = {\r\n  {\"input\", \"INPUT 0\", BOOT_PIN},\r\n  {\"input\", \"INPUT 1\", 19},\r\n  {\"input\", \"INPUT 2\", 21},\r\n  {\"output\", \"OUTPUT 1\", 4},\r\n  {\"output\", \"OUTPUT 2\", 5},\r\n  {\"output\", \"LED BUILTIN\", LED_BUILTIN} // Led ON with signal LOW usually\r\n};\r\n\r\n\r\n/////////////////////////   WebSocket event callback////////////////////////////////   WebSocket Handler  /////////////////////////////\r\nvoid webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {\r\n  switch (type) {\r\n    case WStype_DISCONNECTED:\r\n      Serial.printf(\"[%u] Disconnected!\\n\", num);\r\n      break;\r\n    case WStype_CONNECTED:{\r\n        IPAddress ip = server.getWebSocketServer()->remoteIP(num);\r\n        server.getWebSocketServer()->sendTXT(num, \"{\\\"Connected\\\": true}\");\r\n        Serial.printf(\"Hello client #%d [%s]\\n\", (int)num, ip.toString().c_str());\r\n      }\r\n      break;\r\n    case WStype_TEXT:\r\n      Serial.printf(\"[%u] got Text: %s\\n\", num, payload);   // Got text message from a client\r\n      if (payload[0] == '{')\r\n        parseMessage((const char*)payload);\r\n      break;\r\n    case WStype_BIN:\r\n      Serial.printf(\"[%u] got binary length: %u\\n\", num, length); // Got binary message from a client\r\n      break;\r\n    default:\r\n      break;\r\n  }\r\n}\r\n\r\n\r\nvoid parseMessage(const String json) {\r\n  CJSON::Json doc;\r\n  \r\n  if (doc.parse(json)) {\r\n    String cmd;\r\n    if (doc.getString(\"cmd\", cmd)) {\r\n      // If this is a \"writeOut\" command, set the pin level to value\r\n      if (cmd == \"writeOut\") {\r\n        // getNumber returns double, so use double and cast to int\r\n        double pin, level;\r\n        if (doc.getNumber(\"pin\", pin) && doc.getNumber(\"level\", level)) {\r\n          // Find the gpio in the array and set the level\r\n          for (gpio_type &gpio : gpios) {\r\n            if (gpio.pin == (int)pin) {\r\n              Serial.printf(\"Set pin %d to %d\\n\", (int)pin, (int)level);\r\n              gpio.level = (int)level;\r\n              digitalWrite((int)pin, (int)level);\r\n              // Update all clients with new gpio list\r\n              updateGpioList();\r\n              return;\r\n            }\r\n          }\r\n        }\r\n      }\r\n    }\r\n  } else {\r\n    Serial.println(F(\"Failed to parse JSON message\"));\r\n  }\r\n}\r\n\r\nvoid updateGpioList() {\r\n  // Build JSON array using CJSON::Json with nesting support\r\n  CJSON::Json doc;\r\n  doc.createArray();\r\n  // Iterate the array of GPIO struct and add each as JSON object\r\n  for (gpio_type &gpio : gpios) {\r\n    CJSON::Json item;\r\n    item.createObject();\r\n    item.setString(\"type\", String(gpio.type));\r\n    item.setNumber(\"pin\", gpio.pin);\r\n    item.setString(\"label\", String(gpio.label));\r\n    item.setBool(\"level\", gpio.level);\r\n    doc.add(item);\r\n  }\r\n  // Serialize JSON document to string\r\n  String json = doc.serialize();\r\n\r\n  // Update client via websocket\r\n  server.broadcastWebSocket(json);\r\n  server.send(200, \"text/plain\", json);\r\n}\r\n\r\nbool updateGpioState() {\r\n  // Iterate the array of GPIO struct and check level of inputs\r\n  for (gpio_type &gpio : gpios) {\r\n    if (strcmp(gpio.type, \"input\") == 0) {\r\n      // Input value != from last read\r\n      if (digitalRead(gpio.pin) != gpio.level) {\r\n        gpio.level = digitalRead(gpio.pin);\r\n        return true;\r\n      }\r\n    }\r\n  }\r\n  return false;\r\n}\r\n\r\n\r\n\r\nvoid setup() {\r\n  Serial.begin(115200);\r\n\r\n  // FILESYSTEM initialization\r\n  if (!FILESYSTEM.begin()) {\r\n    Serial.println(\"ERROR on mounting filesystem.\");\r\n    //FILESYSTEM.format();\r\n    ESP.restart();\r\n  }\r\n\r\n  // Try to connect to WiFi (will start AP if not connected after timeout)\r\n  if (!server.startWiFi(10000)) {\r\n    Serial.println(\"\\nWiFi not connected! Starting AP mode...\");\r\n    server.startCaptivePortal(\"ESP_AP\", \"123456789\", \"/setup\");\r\n  }\r\n\r\n  // Enable ACE FS file web editor and add FS info callback function\r\n  server.enableFsCodeEditor();\r\n\r\n  /*\r\n  * Getting FS info (total and free bytes) is strictly related to\r\n  * filesystem library used (LittleFS, FFat, SPIFFS etc etc) and ESP framework\r\n  */\r\n  #ifdef ESP32\r\n  server.setFsInfoCallback([](fsInfo_t* fsInfo) {\r\n\t  fsInfo->fsName = \"LittleFS\";\r\n\t  fsInfo->totalBytes = LittleFS.totalBytes();\r\n\t  fsInfo->usedBytes = LittleFS.usedBytes();  \r\n  });\r\n  #endif\r\n\r\n  // Add custom page handlers\r\n  server.on(\"/getGpioList\", HTTP_GET, [](){ updateGpioList(); });\r\n\r\n  // Start server with custom websocket event handler\r\n  server.begin(webSocketEvent);\r\n  Serial.print(F(\"ESP Web Server started on IP Address: \"));\r\n  Serial.println(server.getServerIP());\r\n  Serial.println(F(\r\n    \"This is \\\"gpio_list.ino\\\" example.\\n\"\r\n    \"Open /setup page to configure optional parameters.\\n\"\r\n    \"Open /edit page to view, edit or upload example or your custom webserver source files.\"\r\n  ));\r\n\r\n  // GPIOs configuration\r\n  for (gpio_type &gpio : gpios) {\r\n    if (strcmp(gpio.type, \"input\") == 0)\r\n        pinMode(gpio.pin, INPUT_PULLUP);\r\n    else\r\n      pinMode(gpio.pin, OUTPUT);\r\n  }\r\n}\r\n\r\nvoid loop() {\r\n  server.run();  // Handle client requests\r\n\r\n  // True on pin state change\r\n  if (updateGpioState()) {\r\n    updateGpioList();   // Push new state to web clients via websocket\r\n  }\r\n}"
  },
  {
    "path": "examples/gpio_list/partitions.csv",
    "content": "# Name,   Type, SubType, Offset,  Size, Flags\nnvs,      data, nvs,     0x9000,  0x5000,\notadata,  data, ota,     0xe000,  0x2000,\napp0,     app,  ota_0,   0x10000, 0x140000,\napp1,     app,  ota_1,   0x150000,0x140000,\nspiffs,   data, spiffs,  0x290000,0x160000,\ncoredump, data, coredump,0x3F0000,0x10000,\n"
  },
  {
    "path": "examples/gpio_list/platformio.ini",
    "content": "; PlatformIO Project Configuration File\n;\n;   Build options: build flags, source filter\n;   Upload options: custom upload port, speed and extra flags\n;   Library options: dependencies, extra library storages\n;   Advanced options: extra scripting\n;\n; Please visit documentation for the other options and examples\n; https://docs.platformio.org/page/projectconf.html\n\n[platformio]\nsrc_dir = .\n\n[env:esp32-s3-devkitc1-n4r2]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32-s3-devkitc1-n4r2\nframework = arduino\nupload_speed = 921600\nboard_build.partitions = partitions.csv\nlib_extra_dirs = ../../\nlib_ignore = pio_examples\n\n[env:esp32dev]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32dev\nframework = arduino\nupload_speed = 921600\nboard_build.partitions = partitions.csv\nlib_extra_dirs = ../../\nlib_ignore = pio_examples\n\n[env:esp8266-nodemcuv2]\nplatform = espressif8266\nboard = nodemcuv2\nframework = arduino\nupload_speed = 921600\nboard_build.partitions = partitions.csv\nlib_extra_dirs = ../../\nlib_ignore = pio_examples\n"
  },
  {
    "path": "examples/gpio_list/readme.md",
    "content": "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.\n\n![image](https://user-images.githubusercontent.com/27758688/152009153-68f3c70d-8b7f-4852-b8ad-fdccaa294d8f.png)\n"
  },
  {
    "path": "examples/handleFormData/.gitignore",
    "content": ".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",
    "content": "<!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, 0.04),\r\n        0 0.125rem 2rem rgba(27, 40, 50, 0.08),\r\n        0 0 0 0.0625rem rgba(27, 40, 50, 0.024);\r\n      border-radius: 5px;\r\n      box-sizing: border-box;\r\n      width: 100%;\r\n      margin: auto;\r\n      max-width: 860px;\r\n      padding: 40px;\r\n    }\r\n\r\n    h3 {\r\n      text-align: center;\r\n    }\r\n\r\n    .collapsible {\r\n      background-color: #777;\r\n      color: white;\r\n      cursor: pointer;\r\n      padding: 18px;\r\n      width: 100%;\r\n      border: none;\r\n      text-align: left;\r\n      outline: none;\r\n      font-size: 15px;\r\n      max-width: 800px;\r\n    }\r\n\r\n    .collapsible:after {\r\n      content: '\\002B';   /* The '+' character*/\r\n      color: white;\r\n      font-weight: bold;\r\n      float: right;\r\n      margin-left: 5px;\r\n    }\r\n\r\n    .content {\r\n      padding: 0 15px;\r\n      max-height: 0;\r\n      overflow: hidden;\r\n      transition: max-height 0.4s ease-out;\r\n      background-color: #f1f1f1;\r\n      border-bottom: solid 1px #f1f1f1;\r\n    }\r\n\r\n    p{\r\n      margin-left: 10px;\r\n      padding: 5px;\r\n    }\r\n\r\n    /* Form button styling */\r\n    input, select  {\r\n      border-radius: 5px;\r\n      border: solid 1px #ccc;\r\n      padding: 10px 20px;\r\n      cursor: pointer;\r\n      margin-top: 5px;\r\n    }\r\n\r\n    input[type=submit] {\r\n      background-color: green;\r\n      color: white;\r\n    }\r\n\r\n    input[type=submit]:hover {\r\n      background-color: #45a049;\r\n      border: solid 1px #000;\r\n    }\r\n\r\n    label {\r\n      text-align: right;\r\n      margin: auto 0 auto 0;\r\n    }\r\n\r\n    form {\r\n      margin-top: 10px;\r\n      display: grid;\r\n      grid-template-columns: 1fr 1fr 1fr;\r\n      grid-gap: 20px;\r\n    }\r\n  </style>\r\n</head>\r\n\r\n<body>\r\n  <div class=\"container\">\r\n    <h3>Collapsible form option menu</h3>\r\n    <button class=\"collapsible\">Open Section 1</button>\r\n    <div class=\"content\" style=\"max-height: 90px;\">\r\n      <p>This example show how to handle a form without reload web page on submit.</p>\r\n      <p>In addition show a way to build a nice collapsible menu with CSS and JS</p>\r\n    </div>\r\n\r\n    <button class=\"collapsible\">Open Section 2</button>\r\n    <div class=\"content\">\r\n\r\n      <!-- With HTML 5 we can use custom element attributes, so keep track where we want the result of form submit -->\r\n      <!-- By default, HTML forms will send to request to the action link with GET method -->\r\n      <form action=\"/setForm1\" data-result=\"form1-result\" enctype=\"text/plain\" method=\"post\">\r\n        <label for=\"cars\">Choose a car brand:</label>\r\n        <select id=\"cars\" name=\"cars\">\r\n          <option value=\"Alfa Romeo\">Alfa Romeo</option>\r\n          <option value=\"Ferrari\">Ferrari</option>\r\n          <option value=\"Lamborghini\">Lamborghini</option>\r\n          <option value=\"Maserati\">Maserati</option>\r\n        </select>\r\n        <input type=\"submit\">\r\n      </form>\r\n      <br><br>\r\n      <p id=\"form1-result\"></p>\r\n    </div>\r\n\r\n    <button class=\"collapsible\">Open Section 3</button>\r\n    <div class=\"content\">\r\n       <!-- With HTML 5 we can use custom element attributes, so keep track where we want the result of form submit -->\r\n      <form action=\"/setForm2\" data-result=\"form2-result\" enctype=\"text/plain\" method=\"post\">\r\n        <p><label for=\"fname\">Firstname</label><input type=\"text\" id=\"fname\" name=\"firstname\" placeholder=\"Your name..\"></p>\r\n        <p><label for=\"lname\">Lastname</label><input type=\"text\" id=\"lname\" name=\"lastname\" placeholder=\"Your last name..\"></p>\r\n        <p><label for=\"age\">Your Age</label><input type=\"number\" id=\"age\" name=\"age\"></p>\r\n        <br>\r\n    \t  <input type=\"submit\">\r\n      </form>\r\n      <br><br>\r\n      <p id=\"form2-result\"></p>\r\n    </div>\r\n  </div>\r\n\r\n  <!-- Load document Javascript  -->\r\n  <script>\r\n    // Expand or collapse the content according to current state\r\n    function expandCollapse() {\r\n      // Get the HTML element immediately following the button (content)\r\n      var content = this.nextElementSibling;\r\n      if (content.style.maxHeight)\r\n        content.style.maxHeight = null;\r\n      else\r\n        content.style.maxHeight = content.scrollHeight + \"px\";\r\n    }\r\n\r\n    // Select all \"+\" HTML buttons in webpage and add a listener for each one\r\n    const btnList = document.querySelectorAll(\".collapsible\");\r\n    btnList.forEach(elem => {\r\n      elem.addEventListener(\"click\", expandCollapse ); // Set callback function linked to the button click\r\n    });\r\n\r\n    // This listener will execute an \"arrow\" function once the page was fully loaded\r\n    document.addEventListener(\"DOMContentLoaded\", () => {\r\n      console.log('Webpage is fully loaded');\r\n\r\n      // At first, get the default values for form input elements from ESP\r\n      fetch('/getDefault')\r\n        .then(response => response.json())  // Parse the server response\r\n        .then(jsonObj => {                     // Do something with parsed response\r\n          console.log(jsonObj);\r\n          document.getElementById('cars').value = jsonObj.car;\r\n          document.getElementById('fname').value = jsonObj.firstname;\r\n          document.getElementById('lname').value = jsonObj.lastname;\r\n          document.getElementById('age').value = jsonObj.age;\r\n        });\r\n    });\r\n\r\n    // This listener will prevent each form to reload page after submitting data\r\n    document.addEventListener(\"submit\", (e) => {\r\n      const form = e.target;        // Store reference to form to make later code easier to read\r\n      fetch(form.action, {          // Send form data to server using the Fetch API\r\n          method: form.method,\r\n          body: new FormData(form),\r\n        })\r\n\r\n        .then(response => response.text())  // Parse the server response\r\n        .then(text => {                     // Do something with parsed response\r\n          console.log(text);\r\n          const resEl = document.getElementById(form.dataset.result).innerHTML= text;\r\n        });\r\n\r\n      e.preventDefault();                 // Prevent the default form submit wich reload page\r\n    });\r\n  </script>\r\n\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "examples/handleFormData/data/myScript.js",
    "content": "// Expand or collapse the content according to current state\r\nfunction expandCollapse() {\r\n  // Get the HTML element immediately following the button (content)\r\n  var content = this.nextElementSibling; \r\n  if (content.style.maxHeight) \r\n    content.style.maxHeight = null;\r\n  else \r\n    content.style.maxHeight = content.scrollHeight + \"px\";\r\n}\r\n\r\n// Select all \"+\" HTML buttons in webpage and add a listener for each one\r\nconst btnList = document.querySelectorAll(\".collapsible\");\r\nbtnList.forEach(elem => {\r\n  elem.addEventListener(\"click\", expandCollapse ); // Set callback function linked to the button click\r\n});\r\n\r\n// This listener will execute an \"arrow\" function once the page was fully loaded\r\ndocument.addEventListener(\"DOMContentLoaded\", () => {\r\n  console.log('Webpage is fully loaded');\r\n  \r\n  // At first, get the default values for form input elements from ESP\r\n  fetch('/getDefault')\r\n    .then(response => response.json())  // Parse the server response\r\n    .then(jsonObj => {                     // Do something with parsed response\r\n      console.log(jsonObj);\r\n      document.getElementById('cars').value = jsonObj.car;\r\n      document.getElementById('fname').value = jsonObj.firstname;\r\n      document.getElementById('lname').value = jsonObj.lastname;\r\n      document.getElementById('age').value = jsonObj.age;\r\n    });\r\n});\r\n\r\n// This listener will prevent each form to reload page after submitting data\r\ndocument.addEventListener(\"submit\", (e) => {\r\n  const form = e.target;        // Store reference to form to make later code easier to read\r\n  fetch(form.action, {          // Send form data to server using the Fetch API\r\n      method: form.method,\r\n      body: new FormData(form),\r\n    })\r\n \r\n    .then(response => response.text())  // Parse the server response\r\n    .then(text => {                     // Do something with parsed response\r\n      console.log(text);\r\n      const resEl = document.getElementById(form.dataset.result).innerHTML= text;\r\n    });\r\n  \r\n  e.preventDefault();                 // Prevent the default form submit wich reload page\r\n});\r\n////////////////////////////////////////////////////////////////////////////////////////////"
  },
  {
    "path": "examples/handleFormData/data/myStyle.css",
    "content": "\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),\r\n    0 0 0 0.0625rem rgba(27, 40, 50, 0.024);\r\n  border-radius: 5px;\r\n  box-sizing: border-box;\r\n  width: 100%;\r\n  margin: auto;\r\n  max-width: 860px;\r\n  padding: 40px;\r\n}\r\n\r\nh3 {\r\n  text-align: center;\r\n}\r\n\r\n.collapsible {\r\n  background-color: #777;\r\n  color: white;\r\n  cursor: pointer;\r\n  padding: 18px;\r\n  width: 100%;\r\n  border: none;\r\n  text-align: left;\r\n  outline: none;\r\n  font-size: 15px;\r\n  max-width: 800px;\r\n}\r\n\r\n.collapsible:after {\r\n  content: '\\002B';   /* The '+' character*/\r\n  color: white;\r\n  font-weight: bold;\r\n  float: right;\r\n  margin-left: 5px;\r\n}\r\n\r\n.content {\r\n  padding: 0 15px;\r\n  max-height: 0;\r\n  overflow: hidden;\r\n  transition: max-height 0.4s ease-out;\r\n  background-color: #f1f1f1;\r\n  border-bottom: solid 1px #f1f1f1;\r\n}\r\n\r\np{\r\n  margin-left: 10px;\r\n  padding: 5px;\r\n}\r\n\r\n/* Form button styling */\r\ninput, select  {\r\n  border-radius: 5px;\r\n  border: solid 1px #ccc;\r\n  padding: 10px 20px;\r\n  cursor: pointer;\r\n  margin-top: 5px;\r\n}\r\n       \r\ninput[type=submit] {\r\n  background-color: green;\r\n  color: white;\r\n}\r\n\r\ninput[type=submit]:hover {\r\n  background-color: #45a049;\r\n  border: solid 1px #000;\r\n}\r\n\r\nlabel {\r\n  text-align: right; \r\n  margin: auto 0 auto 0;\r\n}\r\n\r\nform {\r\n  margin-top: 10px;\r\n  display: grid;\r\n  grid-template-columns: 1fr 1fr 1fr;\r\n  grid-gap: 20px;\r\n}\r\n"
  },
  {
    "path": "examples/handleFormData/handleFormData.ino",
    "content": "#include <FS.h>\r\n#include <LittleFS.h>\r\n#include <FSWebServer.h>   // https://github.com/cotestatnt/esp-fs-webserver/\r\n\r\n#define FILESYSTEM LittleFS\r\nFSWebServer server(LittleFS, 80, \"esphost\");\r\n\r\n////////////////////////////  HTTP Request Handlers  ////////////////////////////////////\r\nvoid getDefaultValue () {\r\n  // Send to client default values as JSON string because it's very easy to parse JSON in Javascript\r\n  String defaultVal = \"{\\\"car\\\":\\\"Ferrari\\\", \\\"firstname\\\":\\\"Enzo\\\", \\\"lastname\\\":\\\"Ferrari\\\",\\\"age\\\":90}\";\r\n  server.send(200, \"text/json\", defaultVal);\r\n}\r\n\r\nvoid handleForm1() {\r\n  String reply;\r\n  if(server.hasArg(\"cars\")) {\r\n    reply += \"You have submitted with Form1: \";\r\n    reply += server.arg(\"cars\");\r\n  }\r\n  Serial.println(reply);\r\n  server.send(200, \"text/plain\", reply);\r\n}\r\n\r\nvoid handleForm2() {\r\n  String reply;\r\n  if(server.hasArg(\"firstname\")) {\r\n    reply += \"You have submitted with Form2: \";\r\n    reply += server.arg(\"firstname\");\r\n  }\r\n  if(server.hasArg(\"lastname\")) {\r\n    reply += \" \";\r\n    reply += server.arg(\"lastname\");\r\n  }\r\n  if(server.hasArg(\"age\")) {\r\n    reply += \", age: \";\r\n    reply += server.arg(\"age\");\r\n  }\r\n  Serial.println(reply);\r\n  server.send(200, \"text/plain\", reply);\r\n}\r\n\r\n////////////////////////////////  Filesystem  /////////////////////////////////////////\r\nbool startFilesystem() {\r\n  if (FILESYSTEM.begin()){\r\n    server.printFileList(LittleFS, \"/\", 1, Serial);   \r\n    return true;\r\n  }\r\n  else {\r\n      Serial.println(\"ERROR on mounting filesystem. It will be reformatted!\");\r\n      FILESYSTEM.format();\r\n      ESP.restart();\r\n  }\r\n  return false;\r\n}\r\n\r\n\r\nvoid setup(){\r\n  Serial.begin(115200);\r\n  \r\n  // FILESYSTEM INIT\r\n  startFilesystem();\r\n\r\n  // Try to connect to WiFi (will start AP if not connected after timeout)\r\n  if (!server.startWiFi(10000)) {\r\n    Serial.println(\"\\nWiFi not connected! Starting AP mode...\");\r\n    server.startCaptivePortal(\"ESP_AP\", \"123456789\", \"/setup\");\r\n  }\r\n\r\n  // Add custom page handlers to webserver\r\n  server.on(\"/getDefault\", HTTP_GET, getDefaultValue);\r\n  server.on(\"/setForm1\", HTTP_POST, handleForm1);\r\n  server.on(\"/setForm2\", HTTP_POST, handleForm2);\r\n\r\n  // Enable ACE FS file web editor and add FS info callback function\r\n  server.enableFsCodeEditor();\r\n\r\n  // Start server\r\n  server.begin();\r\n  Serial.print(F(\"ESP Web Server started on IP Address: \"));\r\n  Serial.println(server.getServerIP());\r\n  Serial.println(F(\r\n    \"This is \\\"handleFormData.ino\\\" example.\\n\"\r\n    \"Open /setup page to configure optional parameters.\\n\"\r\n    \"Open /edit page to view, edit or upload example or your custom webserver source files.\"\r\n  ));\r\n}\r\n\r\n\r\nvoid loop() {\r\n  // Handle client requests\r\n  server.run();\r\n  \r\n  // Nothing to do here, just a small delay\r\n  delay(10);  \r\n}\r\n"
  },
  {
    "path": "examples/handleFormData/partitions.csv",
    "content": "# Name,   Type, SubType, Offset,  Size, Flags\nnvs,      data, nvs,     0x9000,  0x5000,\notadata,  data, ota,     0xe000,  0x2000,\napp0,     app,  ota_0,   0x10000, 0x140000,\napp1,     app,  ota_1,   0x150000,0x140000,\nspiffs,   data, spiffs,  0x290000,0x160000,\ncoredump, data, coredump,0x3F0000,0x10000,\n"
  },
  {
    "path": "examples/handleFormData/platformio.ini",
    "content": "; PlatformIO Project Configuration File\n;\n;   Build options: build flags, source filter\n;   Upload options: custom upload port, speed and extra flags\n;   Library options: dependencies, extra library storages\n;   Advanced options: extra scripting\n;\n; Please visit documentation for the other options and examples\n; https://docs.platformio.org/page/projectconf.html\n\n[platformio]\nsrc_dir = .\n\n\n[env:esp32-s3-devkitc1-n4r2]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32-s3-devkitc1-n4r2\nframework = arduino\nupload_speed = 921600\nmonitor_speed = 115200  \nboard_build.partitions = partitions.csv\nlib_extra_dirs = ../../\nlib_ignore = pio_examples\n\n[env:esp32dev]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32dev\nframework = arduino\nupload_speed = 921600\nmonitor_speed = 115200  \nboard_build.partitions = partitions.csv\nlib_extra_dirs = ../../\nlib_ignore = pio_examples\n\n[env:esp8266-nodemcuv2]\nplatform = espressif8266\nboard = nodemcuv2\nframework = arduino\nupload_speed = 921600\nmonitor_speed = 115200  \nboard_build.partitions = partitions.csv\nlib_extra_dirs = ../../\nlib_ignore = pio_examples\n"
  },
  {
    "path": "examples/leanWebserver/.gitignore",
    "content": ".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",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\">\n  <title>ESP simpleServer.ino</title>\n  \n  <!--Pico - Minimal CSS Framework for semantic info: HTML https://picocss.com/ -->\n  <link rel=\"stylesheet\" type=\"text/css\" href=\"css/pico.fluid.classless.css\">\n\n  <!-- Cyustom page styling -->\n  <style>\n    html {\n      background-color: #92938fd1\n    }\n    .container {\n      display: flex;\n      justify-content: center;\n      min-height: calc(100vh - 6rem);\n      padding: 1rem 0;\n      \n    }    \n    .container {\n      max-width: 860px;\n      padding: 40px;\n    }\n  </style>\n\n</head>\n  <body>\n    <main class=\"container\">\n      <article class=\"grid\">\n        <div>\n          <hgroup>\n            <h1>ESP Async FS WebServer - LED Switcher - simpleServer.ino</h1>\n          </hgroup>\n          <label for=\"remember\">\n            <input type=\"checkbox\" role=\"switch\" id=\"toggle-led\" name=\"toggle-led\">\n            Toggle built-in LED\n          </label>\n          <br>\n          <p id=\"esp-response\"></p>\n          <img src=\"img/espressif.jpg\" style=\"opacity: 0.75\"/>\n        </div>\n      </article>\n    </main>\n    \n    <script type=\"text/javascript\">\n      // This function fetch the GET request to the ESP webserver\n      function toggleLed() {\n        const pars = new URLSearchParams({\n          val:  document.getElementById('toggle-led').checked ? '1' : '0'\n        });\n        \n        fetch('/led?' + pars )                // Do the request\n        .then(response => response.text())    // Parse the response \n        .then(text => {                       // DO something with response\n          console.log(text);\n          document.getElementById('esp-response').innerHTML = text + ' <i>(Builtin LED is ON with a low signal)</i>';\n        });\n      }\n      \n      // Add event listener to the LED checkbox (the function will be called on every change)\n      document.getElementById('toggle-led').addEventListener('change', toggleLed );\n    </script>\n    \n  </body>\n</html>\n"
  },
  {
    "path": "examples/leanWebserver/leanWebserver.ino",
    "content": "/**\n * @file leanWebserver.ino\n * @brief Example of a lean ESP webserver using FSWebServer and LittleFS.\n *\n * This sketch demonstrates how to configure the FSWebServer library for minimal resource usage\n * by disabling unneeded features such as the built-in file editor, setup page, setup HTML, and websocket server.\n * It connects to a WiFi network using provided SSID and password, mounts the LittleFS filesystem,\n * and starts a web server on port 80.\n *\n * Features:\n * - Minimal webserver configuration for ESP boards.\n * - Serves files from LittleFS.\n * - HTTP endpoint `/led` to control the built-in LED via GET requests (e.g., `/led?val=1`).\n * - Serial output for debugging and status.\n *\n * Usage:\n * - Update `ssid` and `password` with your WiFi credentials.\n * - Access the device's IP address in a browser to interact with the server.\n * - Use `/led?val=1` or `/led?val=0` to turn the LED on or off.\n *\n * Note:\n * - The built-in `/setup` and `/edit` pages are disabled for a leaner firmware.\n * - If the filesystem fails to mount, it will be formatted and the device will restart.\n * - mDNS is kept enabled for captive portal redirection, but can be disabled if not needed.\n *\n * @author cotestatnt\n * @see https://github.com/cotestatnt/async-esp-fs-webserver\n */\n\n#include <Arduino.h>\n#include <FS.h>\n#include <LittleFS.h>\n\n// Configuration to disable unneeded features for a lean webserver\n\n// #define ESP_FS_WS_MDNS 0     // Disable mDNS responder\n// Keep MDNS active so OS captive portal redirection works (even if useless without setup page)\n\n#define ESP_FS_WS_EDIT 0        // Disable built-in file editor\n#define ESP_FS_WS_SETUP 0       // Disable built-in setup page handlers\n#define ESP_FS_WS_SETUP_HTM 0   // Disable built-in setup page HTML \n#define ESP_FS_WS_WEBSOCKET 0   // Disable built-in websocket server\n\n#include <FSWebServer.h>  //https://github.com/cotestatnt/async-esp-fs-webserver\n\n#define FILESYSTEM LittleFS\nFSWebServer server(FILESYSTEM, 80, \"myserver\");\n\n// Define built-in LED if not defined by board (eg. generic dev boards)\n#ifndef LED_BUILTIN\n#define LED_BUILTIN 2\n#endif\n\n/*\n  In this example the built-in /setup page is disabled as for /edit page.\n  You can still try to start the captive portal, but it will redirect to inesistent /setup \n  (or other URL you provide), so user will see 404 error until you load your own pages.\n\n  For this reason, this example uses the usual WiFi connection method.\n*/\nconst char* ssid = \"your-ssid\";\nconst char* password = \"your-password\";\n\n////////////////////////////////  Filesystem  /////////////////////////////////////////\nbool startFilesystem() {\n  if (FILESYSTEM.begin()){\n    server.printFileList(FILESYSTEM, \"/\", 2);\n    return true;\n  }\n  else {\n    Serial.println(\"ERROR on mounting filesystem. It will be reformatted!\");\n    FILESYSTEM.format();\n    ESP.restart();\n  }\n  return false;\n}\n\n///////////////////////////////  HTTP endpoint  ///////////////////////////////////////\nvoid handleLed() {\n  static int value = false;\n  // http://xxx.xxx.xxx.xxx/led?val=1\n  if(server.hasArg(\"val\")) {\n    value = server.arg(\"val\").toInt();\n    digitalWrite(LED_BUILTIN, value);\n  }\n  String reply = \"LED is now \";\n  reply += value ? \"ON\" : \"OFF\";\n  server.send(200, \"text/plain\", reply);\n}\n\n\nvoid setup() {\n  pinMode(LED_BUILTIN, OUTPUT);\n  Serial.begin(115200);\n\n  // FILESYSTEM INIT\n  startFilesystem();\n  \n  WiFi.begin(\"your-ssid\", \"your-password\");\n  Serial.print(\"Connecting to WiFi ..\");\n  while (WiFi.status() != WL_CONNECTED) {\n    Serial.print('.');\n    delay(1000);\n  }\n\n  // Try to connect to WiFi (will start AP if not connected after timeout)\n  // if (!server.startWiFi(10000)) {\n  //   Serial.println(\"\\nWiFi not connected! Starting AP mode...\");\n  //   server.startCaptivePortal(\"ESP_AP\", \"123456789\", \"/setup\");\n  // }\n\n  // Define HTTP endpoints\n  server.on(\"/led\", HTTP_GET, handleLed);\n  \n  // Start server\n  server.begin();\n  Serial.print(F(\"\\nESP Web Server started on IP Address: \"));\n  Serial.println(server.getServerIP());\n  Serial.println(F(\n      \"\\nThis is \\\"customOptions.ino\\\" example.\\n\"\n      \"Open /setup page to configure optional parameters.\\n\"\n      \"Open /edit page to view, edit or upload example or your custom webserver source files.\"\n  ));\n\n  Serial.print(F(\"Compile time (default firmware version): \"));\n  Serial.println(BUILD_TIMESTAMP);\n}\n\nvoid loop() {\n  // Handle client requests\n  server.run(); \n\n  // Nothing to do here, just a small delay for task yield\n  delay(10);  \n}\n"
  },
  {
    "path": "examples/leanWebserver/partitions.csv",
    "content": "# Name,   Type, SubType, Offset,  Size, Flags\nnvs,      data, nvs,     0x9000,  0x5000,\notadata,  data, ota,     0xe000,  0x2000,\napp0,     app,  ota_0,   0x10000, 0x140000,\napp1,     app,  ota_1,   0x150000,0x140000,\nspiffs,   data, spiffs,  0x290000,0x160000,\ncoredump, data, coredump,0x3F0000,0x10000,\n"
  },
  {
    "path": "examples/leanWebserver/platformio.ini",
    "content": "; PlatformIO Project Configuration File\n;\n;   Build options: build flags, source filter\n;   Upload options: custom upload port, speed and extra flags\n;   Library options: dependencies, extra library storages\n;   Advanced options: extra scripting\n;\n; Please visit documentation for the other options and examples\n; https://docs.platformio.org/page/projectconf.html\n\n[platformio]\nsrc_dir = .\n\n[env:esp32-s3-devkitc1-n4r2-lean]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32-s3-devkitc1-n4r2\nframework = arduino\nboard_build.partitions = partitions.csv\nbuild_flags =    \n    -D ESP_FS_WS_EDIT=0\n    -D ESP_FS_WS_SETUP=0\n    -D ESP_FS_WS_SETUP_HTM=0\n    -D ESP_FS_WS_WEBSOCKET=0\n\nlib_extra_dirs = ../../\nmonitor_speed = 115200\n\n[env:esp32dev-lean]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32dev\nframework = arduino\nboard_build.partitions = partitions.csv\nbuild_flags =    \n    -D ESP_FS_WS_EDIT=0\n    -D ESP_FS_WS_SETUP=0\n    -D ESP_FS_WS_SETUP_HTM=0  \n    -D ESP_FS_WS_WEBSOCKET=0  \n\nlib_extra_dirs = ../../\n\n"
  },
  {
    "path": "examples/localRFID/.gitignore",
    "content": ".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",
    "content": "#pragma once\n#include <Arduino.h>\n#include <ArduinoJson.h>\n#include <FS.h>\n#include <LittleFS.h>\n\nclass TableManager {\npublic:\n  TableManager(const char* filename) : filename(filename) {\n    // Initialize as empty JSON array (will store our records)\n    document.to<JsonArray>();\n  }\n\n  // Loads JSON data from LittleFS file into memory\n  bool loadTable() {    \n    File file = LittleFS.open(filename, FILE_READ);\n    if (!file) {\n      // File doesn't exist - initialize empty table (not an error)\n      document.to<JsonArray>();\n      return true;\n    }\n\n    document.clear();\n    DeserializationError error = deserializeJson(document, file);\n    file.close();\n\n    if (error) {\n      Serial.print(F(\"deserializeJson() failed: \"));\n      Serial.println(error.f_str());\n      return false;\n    }\n    return true;\n  }\n\n  // Checks if a record with same uniqueKey value already exists\n  bool isDuplicate(JsonObject& newRecord, const char* uniqueKey) {\n    JsonArray table = document.as<JsonArray>();\n    if (newRecord[uniqueKey].is<const char*>()) {\n      const char* idValue = newRecord[uniqueKey];\n      for (JsonObject record : table) {\n        if (record[uniqueKey].is<const char*>() && \n            strcmp(record[uniqueKey].as<const char*>(), idValue) == 0) {\n          return true;\n        }\n      }\n    }\n    return false;\n  }\n\n  // Adds new record to the table with optional duplicate check\n  bool addRecord(JsonObject newRecord, const char* uniqueKey = nullptr) {\n    JsonArray table = document.as<JsonArray>();\n    \n    // Skip duplicate check if uniqueKey not provided\n    if (uniqueKey && isDuplicate(newRecord, uniqueKey)) {\n      return false;\n    }\n      \n    if (table.add(newRecord))\n      return saveTable();\n    return false;\n  }\n\n  // Version that checks multiple unique keys\n  bool addRecord(JsonObject newRecord, const char* uniqueKeys[], int numUniqueKeys) {\n    JsonArray table = document.as<JsonArray>();\n\n    // Check all specified unique keys for duplicates\n    for (int i = 0; i < numUniqueKeys; i++) {          \n      if (isDuplicate(newRecord, uniqueKeys[i]))\n        return false;\n    }\n\n    if (table.add(newRecord))\n      return saveTable();\n    return false;\n  }\n\n  // Delete existing record matching key-value pair\n  bool deleteRecord(const char* key, const char* value) {\n    JsonArray table = document.as<JsonArray>();\n    \n    // Iterate through the array to find the matching record\n    for (size_t i = 0; i < table.size(); i++) {\n      JsonObject record = table[i].as<JsonObject>();\n      if (record[key] == value) {          \n        table.remove(i);\n        return saveTable(); \n      }\n    }\n    return false; // Record not found\n  }\n\n  // Finds first record matching key-value pair\n  JsonObject findRecord(const char* key, const char* value) {\n    JsonArray table = document.as<JsonArray>();\n    for (JsonObject record : table) {\n      if (strcmp(record[key].as<const char*>(), value) == 0) {\n        return record;        \n      }\n    }\n    return JsonObject();\n  }\n\n  JsonArray getUsers() {\n    loadTable();\n    return document.as<JsonArray>();\n  }\n\n  // Persists current in-memory table to LittleFS\n  bool saveTable() {\n    File file = LittleFS.open(filename, FILE_WRITE);\n    if (!file) return false;\n    \n    bool success = serializeJsonPretty(document, file) > 0;\n    file.close();\n    return success;\n  }\n\n  // Debug helper - prints current table to Serial\n  void printTable() {\n    serializeJsonPretty(document, Serial);\n    Serial.println();\n  }\n\nprivate:\n  const char* filename;  // LittleFS filename for persistence\n  JsonDocument document; // In-memory JSON data storage\n};\n"
  },
  {
    "path": "examples/localRFID/data/html_login.h",
    "content": "/* Generated with https://cotestatnt.github.io/fsdata.html */\n/* File: login */\n/* Content-Type: application/octet-stream */\n/* Original Size: 5712 bytes */\n/* Compressed Size: 2258 bytes */\n/* Compression Ratio: 39.5% */\n/* Compression: GZIP (remember to add \"Content-Encoding: gzip\" in HTTP headers) */\n\n#define LOGIN_SIZE 2258\n\nstatic const unsigned char login[] PROGMEM = {\n    0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xad, 0x58, 0x7b, 0x6f, 0xdb, 0x38, \n    0x12, 0xff, 0xbf, 0x9f, 0x62, 0xd6, 0x45, 0xd7, 0xd2, 0xc5, 0xb2, 0x2c, 0xbb, 0x4e, 0xb3, 0x7e, \n    0x2d, 0xd2, 0x36, 0x45, 0x17, 0xe8, 0x62, 0x83, 0xa6, 0xc5, 0xe1, 0x90, 0xf3, 0x1d, 0x68, 0x89, \n    0x8e, 0xd9, 0xc8, 0xa2, 0x4e, 0xa4, 0xec, 0x64, 0x5d, 0xdf, 0x67, 0xbf, 0x19, 0x52, 0x92, 0x25, \n    0xc7, 0xdd, 0x2d, 0x16, 0x97, 0xb4, 0xb1, 0x38, 0x1c, 0xce, 0xe3, 0x37, 0x0f, 0x0d, 0x3d, 0xf9, \n    0xe1, 0xed, 0x6f, 0x6f, 0x3e, 0xfd, 0xe3, 0xfa, 0x0a, 0x56, 0x7a, 0x1d, 0xcf, 0x9e, 0x4d, 0xe8, \n    0x03, 0x62, 0x96, 0xdc, 0x4d, 0x5b, 0x3c, 0x69, 0x11, 0x81, 0xb3, 0x68, 0xf6, 0x0c, 0xf0, 0x67, \n    0xb2, 0xe6, 0x9a, 0x41, 0xb8, 0x62, 0x99, 0xe2, 0x7a, 0xda, 0xfa, 0xfc, 0xe9, 0x9d, 0x77, 0xd1, \n    0xaa, 0x6f, 0x25, 0x6c, 0xcd, 0xa7, 0xad, 0x8d, 0xe0, 0xdb, 0x54, 0x66, 0xba, 0x05, 0xa1, 0x4c, \n    0x34, 0x4f, 0x90, 0x75, 0x2b, 0x22, 0xbd, 0x9a, 0x46, 0x7c, 0x23, 0x42, 0xee, 0x99, 0x45, 0x07, \n    0x44, 0x22, 0xb4, 0x60, 0xb1, 0xa7, 0x42, 0x16, 0xf3, 0x69, 0xd0, 0xed, 0x95, 0xa2, 0xb4, 0xd0, \n    0x31, 0x9f, 0x7d, 0x90, 0x77, 0x22, 0x99, 0xf8, 0x76, 0x61, 0x37, 0x94, 0x7e, 0x2c, 0x9f, 0x9f, \n    0xb3, 0x85, 0xcc, 0x35, 0xec, 0x42, 0x19, 0xcb, 0x6c, 0x14, 0x8b, 0xbb, 0x95, 0xbe, 0xcb, 0xd8, \n    0xe3, 0x7e, 0x6c, 0x76, 0x17, 0x32, 0x7a, 0x84, 0xdd, 0x82, 0x85, 0xf7, 0x77, 0x99, 0xcc, 0x93, \n    0xc8, 0xb3, 0x6c, 0xf0, 0x7c, 0x39, 0xa4, 0xdf, 0x31, 0x2c, 0xd1, 0x30, 0x6f, 0xc9, 0xd6, 0x22, \n    0x7e, 0x1c, 0xc1, 0x65, 0x86, 0x66, 0x74, 0x40, 0xb1, 0x44, 0x79, 0x8a, 0x67, 0x62, 0x39, 0xde, \n    0x1b, 0x29, 0xe4, 0x38, 0xcf, 0x3a, 0xc8, 0x2c, 0x35, 0xcf, 0x60, 0x97, 0xb2, 0x28, 0x12, 0xc9, \n    0xdd, 0x08, 0x82, 0x5e, 0xfa, 0x30, 0x86, 0x13, 0xe2, 0x7b, 0xfd, 0x97, 0xe1, 0x70, 0x31, 0x86, \n    0x4a, 0xdd, 0x72, 0x39, 0x06, 0xe3, 0xee, 0x08, 0x86, 0xbd, 0x17, 0x63, 0xd0, 0xfc, 0x41, 0x7b, \n    0x0c, 0xcd, 0x4d, 0x46, 0x10, 0x22, 0x32, 0x3c, 0x6b, 0xe8, 0x82, 0xdd, 0x9a, 0x65, 0xe8, 0xb6, \n    0xa7, 0x65, 0x3a, 0x82, 0x97, 0xa4, 0xc6, 0x6e, 0xc7, 0x6c, 0xc1, 0x63, 0xd8, 0x45, 0x42, 0xa5, \n    0x31, 0x43, 0x93, 0x17, 0xb1, 0x0c, 0xef, 0xc7, 0x60, 0xb9, 0x51, 0x76, 0xc5, 0x28, 0x92, 0x34, \n    0xd7, 0xb7, 0xfa, 0x31, 0xc5, 0x38, 0x90, 0xb2, 0xd6, 0xbc, 0xd3, 0xa0, 0xa5, 0x4c, 0xa9, 0xad, \n    0xcc, 0xa2, 0x63, 0x3a, 0x5f, 0x33, 0x11, 0xb7, 0xe6, 0xb0, 0x2b, 0xac, 0xbd, 0x20, 0x6b, 0x2b, \n    0x87, 0x2f, 0x8c, 0xbf, 0x78, 0x8c, 0xa3, 0x57, 0x41, 0xfa, 0x00, 0x4a, 0xc6, 0x22, 0x82, 0xe7, \n    0x51, 0x14, 0x95, 0x74, 0x2f, 0x63, 0x91, 0xc8, 0xd5, 0x08, 0x06, 0x95, 0x2d, 0x8b, 0x5c, 0x6b, \n    0x99, 0x14, 0x0a, 0x54, 0xbe, 0x58, 0x0b, 0x4d, 0x1a, 0xea, 0x2e, 0xf6, 0x0d, 0x92, 0x75, 0x84, \n    0xd6, 0xb8, 0x55, 0xac, 0xcf, 0xcd, 0xe6, 0x9f, 0xa3, 0x7e, 0xde, 0x7b, 0xf5, 0xf6, 0xe2, 0x75, \n    0x85, 0xfa, 0x76, 0x25, 0x34, 0x3f, 0x98, 0x9b, 0xc8, 0x04, 0x57, 0x61, 0x9e, 0x29, 0xda, 0x4c, \n    0xa5, 0xa8, 0xa1, 0x7e, 0xd2, 0xc2, 0xd1, 0x4a, 0x6e, 0x28, 0xec, 0xdd, 0x84, 0x6d, 0x4e, 0x26, \n    0x51, 0x70, 0xfe, 0xaa, 0xff, 0xd3, 0xbb, 0x42, 0x44, 0x97, 0x72, 0x9c, 0x89, 0x84, 0x82, 0x57, \n    0x85, 0x67, 0x19, 0x73, 0x34, 0x95, 0xfe, 0x7a, 0x91, 0xc8, 0x78, 0xa8, 0x85, 0xa4, 0x80, 0xcb, \n    0x38, 0x5f, 0x27, 0x63, 0x30, 0xf1, 0xf7, 0xd0, 0xc8, 0xb5, 0xaa, 0xb2, 0xc0, 0xf8, 0xbd, 0xe2, \n    0x94, 0xc7, 0xe4, 0x69, 0x6f, 0xb3, 0xaa, 0xe4, 0xe7, 0x4a, 0xcb, 0xb5, 0x57, 0x53, 0x73, 0x84, \n    0x48, 0x1d, 0xbd, 0x85, 0x7c, 0xf0, 0xd4, 0x8a, 0x45, 0x72, 0x3b, 0x82, 0x1e, 0xfe, 0x12, 0x07, \n    0x64, 0x77, 0x0b, 0xe6, 0xf4, 0x3a, 0x50, 0xfc, 0xeb, 0x06, 0xee, 0x49, 0x18, 0x97, 0xe6, 0x67, \n    0xbc, 0x07, 0x38, 0x38, 0x86, 0xc6, 0xd5, 0xf4, 0xd9, 0x68, 0x7d, 0x9f, 0x97, 0xa5, 0xf5, 0xc6, \n    0x3d, 0xd8, 0x3d, 0x4d, 0x7c, 0x53, 0xaa, 0x13, 0xbf, 0xa8, 0xe9, 0x89, 0x6f, 0xdb, 0xcc, 0x84, \n    0x4a, 0x97, 0x4a, 0x7c, 0x12, 0x89, 0x0d, 0x84, 0x31, 0xa6, 0xeb, 0xb4, 0x55, 0xf9, 0x5e, 0x76, \n    0x08, 0x5b, 0x2d, 0x76, 0x41, 0xcb, 0x60, 0x76, 0x75, 0x73, 0x3d, 0xe8, 0xc3, 0xc7, 0x77, 0xbf, \n    0xbc, 0x05, 0x6c, 0x1c, 0x0a, 0x3c, 0x28, 0xfa, 0x07, 0xee, 0x59, 0x7f, 0xac, 0x86, 0xf2, 0x54, \n    0x43, 0xfc, 0x11, 0xc2, 0xad, 0x4a, 0xf0, 0x91, 0x0d, 0x68, 0x77, 0xb5, 0x87, 0xbb, 0x4b, 0x99, \n    0xad, 0x41, 0x44, 0xd3, 0x56, 0x4c, 0xaa, 0xde, 0xe1, 0xaa, 0x55, 0x71, 0x1b, 0x17, 0x6b, 0xcc, \n    0x56, 0x58, 0x7d, 0x8d, 0x14, 0x5b, 0xd5, 0x28, 0x66, 0xda, 0xca, 0xb1, 0xf1, 0x50, 0xf7, 0x6c, \n    0xcd, 0x3e, 0x17, 0x4f, 0xa3, 0x89, 0x6f, 0xf6, 0x8f, 0xce, 0x98, 0xa2, 0x85, 0x5a, 0x81, 0x1b, \n    0x0b, 0xaa, 0xe3, 0x45, 0x0b, 0x3e, 0xac, 0x33, 0xfe, 0x9f, 0x1c, 0x83, 0x13, 0x35, 0x4c, 0xf1, \n    0x8f, 0x6c, 0xf9, 0x63, 0xdb, 0xaa, 0x9e, 0x31, 0xbb, 0x2e, 0x9e, 0xbe, 0xc3, 0xb6, 0xea, 0x90, \n    0xb1, 0xef, 0xb0, 0xb2, 0xf6, 0x1d, 0xd6, 0xdf, 0x67, 0x9f, 0x2d, 0x54, 0x68, 0x14, 0x6a, 0xf9, \n    0x86, 0xb0, 0x7b, 0xb5, 0xb8, 0xf8, 0x14, 0x98, 0x2a, 0x86, 0x07, 0x59, 0xf5, 0xc7, 0xa2, 0xa7, \n    0x17, 0xe1, 0xb2, 0xab, 0x93, 0x71, 0x5f, 0x31, 0xe5, 0x99, 0xe4, 0xb5, 0x21, 0xe5, 0x51, 0x3d, \n    0x03, 0xd2, 0x59, 0x95, 0x72, 0xf0, 0x63, 0x28, 0xd3, 0xc7, 0x31, 0xd6, 0x48, 0x7f, 0xd8, 0x85, \n    0xcb, 0x38, 0x86, 0x8c, 0x8a, 0x59, 0xa1, 0x83, 0x18, 0x8b, 0x0d, 0x8f, 0xba, 0x13, 0x3f, 0xad, \n    0x1d, 0x65, 0x84, 0x8b, 0x7d, 0x89, 0x69, 0x6c, 0x88, 0xf8, 0x3e, 0xfd, 0xf7, 0x02, 0xdf, 0xba, \n    0xf7, 0xc8, 0x1f, 0x4f, 0x13, 0x29, 0x53, 0x4e, 0xb5, 0xbe, 0xca, 0xf8, 0x12, 0x6d, 0xd0, 0x3a, \n    0x55, 0x23, 0xdf, 0xbf, 0x13, 0x7a, 0x95, 0x2f, 0xb0, 0x30, 0xd7, 0x7e, 0x88, 0x06, 0x2b, 0xcd, \n    0x74, 0xa2, 0x7d, 0xa6, 0x1e, 0x93, 0xd0, 0xe3, 0x2a, 0xf5, 0x96, 0xca, 0xdb, 0xf2, 0x85, 0x51, \n    0x87, 0xce, 0xbc, 0xc9, 0x38, 0xd3, 0x3c, 0xc2, 0xfe, 0xa0, 0x57, 0xf0, 0x57, 0x44, 0x4c, 0x7c, \n    0x76, 0x12, 0x45, 0x8b, 0x96, 0x29, 0x52, 0x4b, 0xa6, 0x27, 0x15, 0x66, 0x22, 0xd5, 0x96, 0xc5, \n    0xf7, 0xe1, 0x93, 0x04, 0xb6, 0x91, 0xf8, 0x8e, 0xc0, 0x6e, 0x94, 0x61, 0xf3, 0x00, 0x6c, 0x19, \n    0x22, 0x31, 0xef, 0x3f, 0x58, 0x70, 0xbd, 0xe5, 0x3c, 0x41, 0x84, 0x05, 0xf5, 0x17, 0x96, 0x20, \n    0x97, 0x51, 0xd8, 0x81, 0xf2, 0xb8, 0xe2, 0x48, 0xbc, 0x79, 0x7f, 0xd9, 0x1f, 0x9e, 0x83, 0x5c, \n    0x42, 0x99, 0x2e, 0x50, 0x64, 0x18, 0x49, 0x71, 0x16, 0x4c, 0xa1, 0x77, 0x98, 0x15, 0x95, 0x6f, \n    0x3c, 0x43, 0x1d, 0x3a, 0xce, 0x97, 0xcb, 0x6e, 0xe1, 0xa7, 0x90, 0x3e, 0x1a, 0x80, 0x52, 0x7c, \n    0xd7, 0x88, 0x5e, 0xe6, 0x89, 0x69, 0x53, 0x60, 0xa9, 0x0e, 0x53, 0xa1, 0x10, 0x2e, 0xec, 0x0a, \n    0x37, 0x37, 0x2c, 0xb3, 0x61, 0xfb, 0x28, 0x11, 0x18, 0x0e, 0x53, 0x70, 0x36, 0x2c, 0xce, 0x79, \n    0x07, 0xd8, 0x1a, 0x7b, 0xa5, 0x76, 0x61, 0x3a, 0x2b, 0x48, 0x30, 0x9b, 0xcd, 0x2a, 0xea, 0xd7, \n    0x92, 0x38, 0x99, 0x80, 0x83, 0x7d, 0xc8, 0x2b, 0x77, 0xdc, 0x71, 0x4d, 0xf2, 0x9a, 0xe9, 0xd5, \n    0xb5, 0xdc, 0xa2, 0xd4, 0x5f, 0xf1, 0xa9, 0x9b, 0xca, 0x6d, 0x07, 0x69, 0x0f, 0x7f, 0x27, 0xc7, \n    0xa6, 0xe5, 0xae, 0xd3, 0xef, 0xc0, 0xa0, 0xef, 0x76, 0x20, 0xe6, 0xc9, 0x1d, 0x52, 0x32, 0xcc, \n    0x84, 0x4c, 0x3f, 0x22, 0x43, 0xdb, 0x52, 0xda, 0x1d, 0x4a, 0xa9, 0x3c, 0xd6, 0x44, 0xc2, 0x05, \n    0xe1, 0xa2, 0xf0, 0xf9, 0x16, 0xdf, 0xe8, 0xc6, 0x9d, 0xd7, 0x42, 0x7f, 0x30, 0x9c, 0x48, 0x34, \n    0x84, 0xdb, 0xa6, 0xa8, 0x39, 0xfc, 0x0d, 0x2e, 0xea, 0x76, 0x61, 0x8a, 0x13, 0xaf, 0x85, 0xa4, \n    0xdb, 0x78, 0xfc, 0xfa, 0xd5, 0xc8, 0xbd, 0x3f, 0xd0, 0x1a, 0x8f, 0xc5, 0x76, 0x9a, 0x89, 0x35, \n    0x7f, 0x43, 0x0e, 0x63, 0xca, 0x4e, 0xe1, 0xfe, 0x58, 0x61, 0x5d, 0x99, 0x50, 0x6f, 0xe4, 0x3a, \n    0x95, 0x4a, 0x18, 0x78, 0x77, 0xfb, 0x72, 0x0f, 0x6b, 0x96, 0x40, 0xc4, 0x92, 0xc4, 0x7c, 0x10, \n    0x91, 0x45, 0xbf, 0x3f, 0x6e, 0xca, 0x9e, 0xc0, 0xf9, 0xcb, 0xf1, 0x81, 0xe3, 0xec, 0xec, 0x10, \n    0x3a, 0x1c, 0x7d, 0x96, 0xe0, 0xfc, 0x50, 0x13, 0x7f, 0x5b, 0xf1, 0xcd, 0xeb, 0x6c, 0x85, 0x2a, \n    0x81, 0xe2, 0x7b, 0x63, 0x10, 0x28, 0x73, 0x10, 0x0c, 0xe8, 0xe1, 0x6c, 0x7a, 0x90, 0xec, 0xd6, \n    0xed, 0xbc, 0x15, 0x73, 0xa8, 0xed, 0x8d, 0x6b, 0xa2, 0x08, 0xba, 0xdb, 0xba, 0x89, 0xc4, 0xe9, \n    0x94, 0xa1, 0xac, 0x8e, 0xe0, 0x30, 0x31, 0x74, 0x11, 0xf6, 0x22, 0xda, 0x94, 0x30, 0xbd, 0xba, \n    0x98, 0xfb, 0x86, 0x8c, 0xb3, 0xb3, 0x6f, 0x49, 0x09, 0xc0, 0x87, 0xc1, 0xb7, 0x05, 0xed, 0x9f, \n    0x35, 0x3f, 0x4d, 0xf4, 0xc9, 0xaf, 0xf6, 0x3f, 0x1f, 0x2e, 0x7a, 0xed, 0x92, 0x11, 0x07, 0xa4, \n    0x98, 0x83, 0x73, 0x3a, 0x37, 0x5e, 0x20, 0xc4, 0x98, 0xbf, 0xc3, 0x73, 0xb7, 0x71, 0xbc, 0x77, \n    0x38, 0x7e, 0x0c, 0xdf, 0x49, 0x39, 0xb8, 0xd5, 0x8c, 0xce, 0x97, 0x32, 0x1b, 0xbb, 0x74, 0x7f, \n    0x78, 0x23, 0x23, 0x7e, 0xa9, 0x1d, 0xe1, 0x8e, 0x1b, 0xe1, 0xfb, 0x82, 0x55, 0x05, 0x17, 0x2e, \n    0x26, 0xb8, 0xce, 0xb3, 0xe4, 0xb0, 0x67, 0x72, 0xfc, 0x56, 0xd0, 0x6e, 0x7f, 0x0e, 0x5f, 0xa7, \n    0x28, 0x8d, 0x2a, 0xcd, 0x19, 0xa0, 0xa5, 0x58, 0xbe, 0x2f, 0xe0, 0xa5, 0x5b, 0x4f, 0xea, 0xd2, \n    0x7f, 0x7b, 0xcc, 0xfe, 0x3d, 0xb2, 0xcf, 0x20, 0xec, 0x1c, 0xd5, 0x8b, 0xdf, 0xc0, 0xb5, 0xb2, \n    0xed, 0xcf, 0xc4, 0x34, 0xa5, 0xb8, 0x0d, 0x9c, 0xbe, 0x58, 0x9c, 0xd0, 0x5e, 0x38, 0x29, 0x60, \n    0x5c, 0xc7, 0x88, 0xd2, 0x9f, 0xda, 0x83, 0xe1, 0xec, 0xaa, 0x18, 0x2f, 0x4b, 0xce, 0x97, 0x0e, \n    0x1e, 0xc6, 0x18, 0x04, 0xe7, 0x35, 0xac, 0x88, 0x51, 0xc6, 0xd1, 0x7b, 0x5b, 0xb8, 0x94, 0x84, \n    0x05, 0x37, 0xce, 0x77, 0x17, 0x35, 0xbe, 0xe3, 0x50, 0x51, 0xf5, 0x1c, 0xc5, 0xa5, 0xa8, 0xca, \n    0x3e, 0x32, 0x61, 0xac, 0x01, 0xd5, 0x6d, 0x83, 0x21, 0xd9, 0x80, 0x70, 0x7b, 0x10, 0x0c, 0xb1, \n    0xbc, 0xb7, 0xfd, 0x6a, 0xdd, 0x9f, 0x8f, 0x8f, 0x4e, 0xb2, 0xc2, 0x80, 0xdb, 0x1e, 0x72, 0xf2, \n    0x72, 0xf1, 0xf2, 0x09, 0x1f, 0x4e, 0xbb, 0x69, 0x50, 0x6e, 0xbf, 0x9a, 0xa3, 0x2a, 0xa7, 0xd6, \n    0x69, 0x1d, 0x4c, 0x6e, 0xcc, 0xb8, 0x7f, 0xc1, 0x11, 0x2d, 0x08, 0x4e, 0x10, 0xfb, 0x43, 0xd7, \n    0xa5, 0xf3, 0x0e, 0x87, 0x1f, 0xad, 0xbc, 0xe1, 0x9c, 0xd8, 0x1c, 0xe7, 0xbf, 0x58, 0xb8, 0x05, \n    0xe9, 0x7c, 0x6e, 0x98, 0xee, 0xa9, 0x78, 0xcf, 0x1a, 0x63, 0x0a, 0x80, 0xb3, 0xb5, 0x25, 0xed, \n    0x10, 0x26, 0x08, 0x2c, 0xfc, 0x0c, 0x86, 0x32, 0x32, 0x3b, 0xe4, 0xf6, 0xf9, 0x13, 0x03, 0x11, \n    0x95, 0x0e, 0xbc, 0x3a, 0xb6, 0xc6, 0x50, 0x83, 0x0b, 0xa3, 0x9d, 0x70, 0xa3, 0x97, 0xc2, 0xc0, \n    0x28, 0xb6, 0x82, 0x5e, 0x3d, 0x55, 0x6e, 0x0c, 0x68, 0xc8, 0xc0, 0x8e, 0x1f, 0x3c, 0x95, 0x4c, \n    0xd4, 0x9f, 0xac, 0xe0, 0xbe, 0x91, 0x1b, 0xf4, 0x5c, 0xb7, 0x4c, 0xcc, 0x13, 0xe0, 0x52, 0x90, \n    0x1a, 0x72, 0x19, 0x22, 0x75, 0x2c, 0x15, 0x69, 0xc1, 0xe0, 0x04, 0xb1, 0xdf, 0x2f, 0x20, 0x65, \n    0x25, 0x7e, 0x81, 0x85, 0xb4, 0x5a, 0xf7, 0xed, 0xba, 0xd8, 0xab, 0x51, 0xdd, 0xe3, 0x7e, 0x48, \n    0xef, 0x22, 0xc7, 0x86, 0xfb, 0xcc, 0x5a, 0x66, 0xac, 0x9e, 0xd3, 0x85, 0x22, 0x64, 0xda, 0x88, \n    0x78, 0x72, 0x08, 0x33, 0x86, 0xec, 0x2f, 0x1f, 0xed, 0xc1, 0xe0, 0x1b, 0xfd, 0xed, 0x69, 0x62, \n    0x5f, 0x14, 0x79, 0x6d, 0xce, 0x8b, 0x83, 0x28, 0x0a, 0x7e, 0x59, 0x29, 0xb8, 0x68, 0xc8, 0xdb, \n    0x7f, 0xa3, 0x9f, 0x15, 0xb2, 0x9a, 0xba, 0xa8, 0x8e, 0x07, 0x54, 0xc7, 0x67, 0x10, 0xe0, 0x87, \n    0xe7, 0xb9, 0x35, 0x07, 0x8a, 0xf7, 0xf1, 0x19, 0xb5, 0x94, 0x52, 0x2d, 0x06, 0x0c, 0x0f, 0x61, \n    0x53, 0x72, 0x29, 0x25, 0xfb, 0xc3, 0xa1, 0xdb, 0xd5, 0xf2, 0x46, 0xd3, 0x2c, 0xe4, 0x60, 0xca, \n    0x75, 0xf1, 0x52, 0x75, 0x83, 0x83, 0x9f, 0xa6, 0xf7, 0x7d, 0xbb, 0xd7, 0xae, 0x00, 0xb1, 0xad, \n    0xaf, 0x10, 0x69, 0x89, 0x7b, 0x1b, 0xef, 0x48, 0x86, 0xf9, 0x1a, 0x07, 0xa6, 0x2e, 0x0e, 0x8b, \n    0x57, 0x31, 0xa7, 0xc7, 0xd7, 0x8f, 0xbf, 0x44, 0x4e, 0xbb, 0xba, 0x7e, 0xb4, 0xdd, 0x2e, 0x5e, \n    0xd5, 0xae, 0x36, 0xb8, 0xf3, 0x41, 0x28, 0x4d, 0x13, 0xa4, 0xd3, 0xb6, 0xd3, 0x72, 0x9b, 0x86, \n    0x03, 0x1c, 0xf2, 0xaa, 0x09, 0xc8, 0xe1, 0xc4, 0x56, 0x76, 0x02, 0xb3, 0xe8, 0xa6, 0x99, 0xf9, \n    0x7c, 0xcb, 0x97, 0x0c, 0x95, 0x3b, 0x85, 0x4d, 0x18, 0x36, 0xa5, 0xa1, 0xbc, 0x51, 0x20, 0x0e, \n    0xdf, 0x34, 0xa4, 0xe4, 0x41, 0x3b, 0xcc, 0x38, 0x54, 0x3f, 0x5f, 0x8d, 0x70, 0x7f, 0x70, 0xbe, \n    0xe4, 0x39, 0x75, 0xbe, 0x31, 0xa4, 0x38, 0x25, 0xa7, 0x31, 0xb1, 0x1c, 0x1a, 0x83, 0x2e, 0x5c, \n    0x2d, 0x97, 0x5c, 0xeb, 0x1c, 0x87, 0xeb, 0x18, 0x0c, 0x2c, 0x74, 0x1a, 0xae, 0x7f, 0xbb, 0xf9, \n    0x54, 0x37, 0x45, 0x2a, 0xfd, 0x11, 0x87, 0x5d, 0x5c, 0x90, 0x3b, 0x6c, 0xcb, 0x84, 0x06, 0x3c, \n    0x17, 0xae, 0x9c, 0xb6, 0x9f, 0x2d, 0x45, 0x84, 0x60, 0x95, 0x0d, 0x72, 0xcd, 0xf5, 0x4a, 0x46, \n    0x23, 0x68, 0x93, 0x8c, 0x76, 0xa7, 0xa0, 0xd2, 0x25, 0x75, 0x04, 0x09, 0xdf, 0xc2, 0xe7, 0x8f, \n    0x1f, 0x6e, 0x38, 0xcb, 0xc2, 0xd5, 0x35, 0xcb, 0xd8, 0x5a, 0x39, 0xbb, 0x0a, 0xa8, 0x8e, 0x35, \n    0x79, 0xef, 0xda, 0x43, 0xfb, 0x83, 0xa9, 0x66, 0x50, 0xa9, 0x1b, 0xd1, 0x95, 0xf7, 0x87, 0x96, \n    0xcc, 0x62, 0x7c, 0x31, 0x38, 0x2d, 0x73, 0xb9, 0x81, 0x25, 0x8b, 0x63, 0xa1, 0x65, 0xeb, 0x28, \n    0x3d, 0x8a, 0xbc, 0xa8, 0xf9, 0xde, 0xef, 0xc2, 0x0d, 0x27, 0xaf, 0xc9, 0x4e, 0xd4, 0x0c, 0x2a, \n    0x0f, 0x43, 0xae, 0x94, 0xa4, 0xaf, 0x79, 0x36, 0x82, 0x41, 0x9e, 0x30, 0x2c, 0xfa, 0x70, 0x25, \n    0x68, 0xe6, 0x87, 0xeb, 0xcf, 0x0d, 0x40, 0xf2, 0xbf, 0x80, 0xc7, 0xe7, 0xff, 0x27, 0x1c, 0x35, \n    0x0b, 0x1a, 0x60, 0xa0, 0x67, 0x03, 0x8c, 0xaa, 0xd2, 0x38, 0xd9, 0x43, 0xdc, 0x7e, 0xff, 0xe9, \n    0xd7, 0x0f, 0x10, 0x21, 0x26, 0xe4, 0x8b, 0x22, 0x08, 0x19, 0xbe, 0x73, 0x14, 0x7e, 0x0a, 0x9d, \n    0x0b, 0x7c, 0x0d, 0x23, 0x8f, 0xf9, 0x7e, 0x47, 0x56, 0x49, 0x26, 0x0b, 0x49, 0x45, 0x16, 0xd1, \n    0x97, 0x9b, 0xa5, 0x8b, 0x75, 0xa5, 0x74, 0xa5, 0x70, 0x2a, 0x94, 0x0f, 0xda, 0x2f, 0x71, 0x30, \n    0x43, 0xec, 0x20, 0xc9, 0xe5, 0xa6, 0x26, 0x94, 0xb4, 0xe2, 0x85, 0x67, 0x53, 0x19, 0x85, 0xd0, \n    0xf2, 0x4d, 0x5e, 0x69, 0xab, 0x52, 0x9c, 0xee, 0x71, 0x07, 0xb9, 0x15, 0x79, 0x9b, 0xe1, 0x5c, \n    0xe9, 0x90, 0x35, 0x4f, 0xf7, 0xc2, 0x58, 0x2a, 0x8e, 0x67, 0x48, 0x3d, 0x56, 0xf5, 0xef, 0x0c, \n    0x75, 0xe0, 0x1d, 0x32, 0xcc, 0x7f, 0xc7, 0xb2, 0xe5, 0x10, 0xf1, 0xbb, 0x58, 0x80, 0xbd, 0x6e, \n    0x1d, 0xec, 0x5c, 0x61, 0xbd, 0xcb, 0xec, 0xb1, 0x9b, 0xe6, 0x6a, 0x75, 0x63, 0xba, 0xfa, 0x6e, \n    0xdf, 0x81, 0x56, 0x0b, 0xff, 0x9b, 0x10, 0x62, 0x06, 0x59, 0xaf, 0xf6, 0xc0, 0x63, 0x0c, 0xf3, \n    0x51, 0xb6, 0x5d, 0x65, 0x99, 0xcc, 0x50, 0x76, 0x9e, 0x31, 0x84, 0x0f, 0x15, 0xd2, 0x78, 0xc2, \n    0xac, 0x42, 0x8c, 0x74, 0x99, 0x7f, 0x94, 0x71, 0x36, 0x72, 0x13, 0xbf, 0xbc, 0xf0, 0xe1, 0x05, \n    0xdc, 0x7c, 0x65, 0x33, 0xf1, 0xcd, 0x17, 0xc8, 0xff, 0x03, 0x30, 0xf4, 0x27, 0x97, 0x50, 0x16, \n    0x00, 0x00\n};\n\n\n"
  },
  {
    "path": "examples/localRFID/data/html_rfid.h",
    "content": "/* Generated with https://cotestatnt.github.io/fsdata.html */\n/* File: rfid */\n/* Content-Type: application/octet-stream */\n/* Original Size: 28563 bytes */\n/* Compressed Size: 6973 bytes */\n/* Compression Ratio: 24.4% */\n/* Compression: GZIP (remember to add \"Content-Encoding: gzip\" in HTTP headers) */\n\n#define RFID_SIZE 6973\n\nstatic const unsigned char rfid[] PROGMEM = {\n    0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe5, 0x3d, 0xfb, 0x77, 0xd3, 0x46, \n    0xb3, 0xbf, 0xdf, 0x73, 0xee, 0xff, 0xb0, 0xb8, 0x29, 0x96, 0xc1, 0xf2, 0x0b, 0x02, 0x89, 0x13, \n    0xbb, 0x25, 0x01, 0x5a, 0xce, 0xc7, 0xeb, 0x40, 0xe8, 0x3d, 0xf7, 0x50, 0x5a, 0x64, 0x6b, 0x6d, \n    0xab, 0x91, 0x25, 0x5f, 0x49, 0x4e, 0x48, 0xd3, 0xfc, 0xef, 0x77, 0x66, 0x5f, 0xda, 0x5d, 0xad, \n    0x1c, 0x07, 0x68, 0x2f, 0xdf, 0xb9, 0x50, 0x12, 0x4b, 0xda, 0x99, 0x9d, 0x99, 0x9d, 0xf7, 0xae, \n    0xdc, 0xc3, 0x5b, 0x8f, 0x5f, 0x1d, 0x9f, 0xfc, 0xf7, 0xeb, 0x27, 0x64, 0x51, 0x2c, 0xe3, 0xf1, \n    0x7f, 0xfe, 0xc7, 0x21, 0xfe, 0x26, 0x71, 0x90, 0xcc, 0x47, 0x0d, 0x9a, 0x34, 0xd8, 0x1d, 0x1a, \n    0x84, 0xf0, 0x9b, 0x90, 0xc3, 0x25, 0x2d, 0x02, 0x32, 0x5d, 0x04, 0x59, 0x4e, 0x8b, 0x51, 0xe3, \n    0xdd, 0xc9, 0x53, 0x7f, 0xaf, 0xa1, 0x3d, 0x49, 0x82, 0x25, 0x1d, 0x35, 0xce, 0x22, 0x7a, 0xbe, \n    0x4a, 0xb3, 0xa2, 0x41, 0xa6, 0x69, 0x52, 0xd0, 0x04, 0x46, 0x9e, 0x47, 0x61, 0xb1, 0x18, 0x85, \n    0xf4, 0x2c, 0x9a, 0x52, 0x9f, 0x5d, 0xb4, 0x49, 0x94, 0x44, 0x45, 0x14, 0xc4, 0x7e, 0x3e, 0x0d, \n    0x62, 0x3a, 0xea, 0x77, 0x7a, 0x02, 0x53, 0x11, 0x15, 0x31, 0x1d, 0xbf, 0x79, 0xfa, 0xec, 0x31, \n    0x79, 0x9e, 0xce, 0x0f, 0xbb, 0xfc, 0x9a, 0x3d, 0xca, 0x8b, 0x0b, 0xf1, 0x91, 0x90, 0xee, 0x1d, \n    0x72, 0x14, 0xe4, 0x94, 0xbc, 0xc5, 0x7b, 0x39, 0xb9, 0xd3, 0xe5, 0xb7, 0x27, 0x69, 0x78, 0x41, \n    0x2e, 0x27, 0xc1, 0xf4, 0x74, 0x9e, 0xa5, 0xeb, 0x24, 0x1c, 0x7e, 0x37, 0xdb, 0xc5, 0xbf, 0x07, \n    0x64, 0x19, 0x64, 0xf3, 0x28, 0x19, 0xf6, 0x0e, 0xc8, 0x0c, 0xc8, 0xf2, 0x67, 0xc1, 0x32, 0x8a, \n    0x2f, 0x86, 0x8f, 0x32, 0xa0, 0xa1, 0x9d, 0x07, 0x49, 0xee, 0xe7, 0x34, 0x8b, 0x66, 0x72, 0x9c, \n    0x5f, 0xa4, 0xab, 0xe1, 0xa0, 0xb7, 0xfa, 0x74, 0x70, 0xc5, 0x11, 0xa3, 0x10, 0x68, 0xd6, 0x06, \n    0xe0, 0xb4, 0xa0, 0x19, 0xb9, 0x5c, 0x05, 0x61, 0x18, 0x25, 0xf3, 0x61, 0x1f, 0xc7, 0x10, 0x7d, \n    0xc2, 0xde, 0xe0, 0xfe, 0x74, 0x77, 0x72, 0x00, 0xdc, 0xc7, 0x69, 0x06, 0xf3, 0xcf, 0x00, 0x29, \n    0xe3, 0x79, 0xb8, 0xdf, 0xfb, 0xfe, 0x80, 0x14, 0xf4, 0x53, 0xe1, 0x07, 0x71, 0x34, 0x4f, 0x86, \n    0x53, 0x10, 0x0e, 0xcd, 0xe4, 0x0c, 0x71, 0x30, 0xa1, 0x31, 0xb9, 0x0c, 0xa3, 0x7c, 0x15, 0x07, \n    0x17, 0xc3, 0x49, 0x9c, 0x4e, 0x4f, 0x15, 0x39, 0x93, 0xb4, 0x28, 0xd2, 0xe5, 0x70, 0xb7, 0x24, \n    0x28, 0x4a, 0x56, 0xeb, 0xe2, 0x7d, 0x71, 0xb1, 0x02, 0x91, 0x23, 0xce, 0xc6, 0x87, 0xb6, 0x71, \n    0x6f, 0x15, 0xe4, 0xf9, 0x79, 0x9a, 0x85, 0xf6, 0x7d, 0xba, 0x0c, 0xa2, 0xb8, 0xf1, 0x81, 0x5c, \n    0x72, 0x9a, 0xfa, 0x3d, 0x24, 0x4a, 0x72, 0xb3, 0xc7, 0x98, 0x01, 0x28, 0x9a, 0x0d, 0xfb, 0xab, \n    0x4f, 0x24, 0x4f, 0xe3, 0x28, 0x24, 0xdf, 0x85, 0x61, 0x28, 0x6f, 0xfb, 0x59, 0x10, 0x46, 0xeb, \n    0x7c, 0x78, 0xaf, 0xa4, 0x64, 0xb2, 0x06, 0xda, 0x92, 0x4e, 0xbe, 0x9e, 0x2c, 0xa3, 0x82, 0x5c, \n    0x4a, 0x41, 0x13, 0x14, 0x1f, 0xf1, 0x51, 0x40, 0xec, 0xa3, 0x94, 0xc2, 0x1e, 0x4e, 0xb8, 0x04, \n    0x9e, 0xf8, 0xe5, 0x03, 0xf6, 0xa8, 0x5e, 0x9c, 0x0f, 0x7a, 0x0f, 0x1f, 0xef, 0x1d, 0x49, 0x71, \n    0x9e, 0x2f, 0xa2, 0x82, 0x2a, 0x12, 0x93, 0x34, 0x81, 0x8b, 0xe9, 0x3a, 0xcb, 0xe1, 0xd1, 0x2a, \n    0x8d, 0x74, 0x71, 0x1a, 0x54, 0x0d, 0x17, 0xe9, 0x19, 0xae, 0x9a, 0x8e, 0xb8, 0xff, 0xe0, 0xe1, \n    0x60, 0xff, 0xa9, 0x39, 0xfc, 0x3d, 0x48, 0x3f, 0x98, 0xc4, 0x34, 0xfc, 0xc0, 0x21, 0xda, 0x95, \n    0xfb, 0x26, 0x8e, 0xe9, 0x74, 0xaa, 0x16, 0xfa, 0xc1, 0x83, 0x07, 0x12, 0x59, 0x4e, 0x63, 0x3a, \n    0x2d, 0x94, 0x44, 0x38, 0xa3, 0xc4, 0x14, 0x35, 0xd1, 0x65, 0x4d, 0x36, 0x0b, 0x9b, 0xdc, 0x33, \n    0xa5, 0xe2, 0xf3, 0x29, 0x89, 0x90, 0x86, 0x10, 0x00, 0xb1, 0x24, 0x60, 0x50, 0xa1, 0x24, 0xc0, \n    0x11, 0x0b, 0x04, 0x96, 0x10, 0x94, 0x69, 0x3d, 0x0f, 0x2e, 0xd2, 0x75, 0xa1, 0xac, 0xaa, 0x83, \n    0x96, 0x1c, 0x44, 0x09, 0x22, 0x90, 0xfa, 0x39, 0x8b, 0x29, 0xd0, 0x84, 0x3f, 0xfd, 0x30, 0xca, \n    0x60, 0xa2, 0x28, 0x05, 0x8d, 0x4e, 0xe3, 0xf5, 0x32, 0x39, 0x20, 0x4c, 0xbf, 0x7d, 0x20, 0x6e, \n    0x99, 0x4b, 0x2d, 0x67, 0x4b, 0xbe, 0xa0, 0xd1, 0x7c, 0x51, 0xa0, 0xd2, 0x9d, 0x2d, 0xe4, 0xa4, \n    0x9d, 0xe9, 0x3a, 0x07, 0xdd, 0xf6, 0xb5, 0x39, 0x4c, 0x6d, 0xd0, 0x8c, 0x67, 0x92, 0x7e, 0xf2, \n    0xf3, 0x45, 0x10, 0xa6, 0xe7, 0xa0, 0x5f, 0x3d, 0xc2, 0x74, 0x2b, 0x9b, 0x4f, 0x02, 0xaf, 0xd7, \n    0x66, 0x7f, 0x3b, 0xfd, 0x96, 0xa9, 0x3d, 0x68, 0x7d, 0x72, 0x9e, 0x3c, 0x0a, 0xe9, 0x24, 0xb0, \n    0xb4, 0x60, 0xb6, 0x37, 0xdb, 0x9f, 0x05, 0x9c, 0x11, 0x81, 0x13, 0x8d, 0x34, 0x03, 0x8f, 0x10, \n    0x31, 0x96, 0x82, 0x38, 0x26, 0xbd, 0xce, 0xbd, 0x9c, 0x50, 0x70, 0x37, 0x0a, 0x57, 0x11, 0x4c, \n    0x4c, 0x32, 0x85, 0x8a, 0xbb, 0x75, 0x11, 0x87, 0x77, 0x02, 0x10, 0xd1, 0x19, 0x35, 0x67, 0xa7, \n    0xfb, 0x74, 0x4a, 0x4b, 0x0a, 0x85, 0xc3, 0x24, 0x97, 0x4c, 0xac, 0x30, 0xe8, 0x7c, 0xd8, 0x3f, \n    0x20, 0xa6, 0xc4, 0xff, 0x00, 0x69, 0x45, 0xb3, 0x0b, 0x5f, 0x8c, 0x55, 0xe2, 0xd5, 0x45, 0xce, \n    0xc0, 0xf3, 0x22, 0xc8, 0x0a, 0x85, 0x1a, 0xac, 0x3e, 0xf1, 0x15, 0x7e, 0x97, 0xed, 0x23, 0xf3, \n    0x83, 0x92, 0x13, 0x6d, 0xb9, 0x1e, 0xea, 0xab, 0x95, 0xf3, 0xa5, 0x06, 0xee, 0x53, 0x21, 0xa1, \n    0x8c, 0xc6, 0x01, 0xb2, 0xa6, 0xb1, 0x11, 0xc7, 0xc1, 0x2a, 0xa7, 0xfa, 0x92, 0x9a, 0x7e, 0x4c, \n    0xf7, 0xac, 0xe6, 0x70, 0x46, 0x9e, 0xe4, 0x98, 0xdb, 0xb7, 0xe5, 0x1b, 0xb6, 0xf1, 0x4e, 0xbb, \n    0x8c, 0x83, 0xed, 0xa7, 0xec, 0xe4, 0x8b, 0xf4, 0xdc, 0xf6, 0xbd, 0x72, 0xf0, 0x2c, 0xcd, 0x96, \n    0x7e, 0xa6, 0x3f, 0xd7, 0x74, 0xff, 0x3c, 0x0b, 0x56, 0x43, 0xfc, 0x61, 0xcf, 0xc7, 0xa9, 0xad, \n    0xac, 0x0a, 0x4d, 0x42, 0x13, 0x31, 0x37, 0x19, 0xbe, 0xe4, 0xb8, 0xda, 0xa6, 0xfb, 0xe4, 0xbf, \n    0x5c, 0x10, 0xc3, 0x38, 0xc8, 0x0b, 0x7f, 0xba, 0x88, 0xe2, 0x50, 0x49, 0x37, 0x63, 0xcb, 0x75, \n    0x5f, 0x87, 0x00, 0xff, 0xe5, 0xa2, 0x5d, 0xcc, 0x22, 0xe6, 0x60, 0x0a, 0xdc, 0xab, 0x78, 0x81, \n    0x67, 0xa8, 0x59, 0x42, 0x6d, 0x9f, 0xc4, 0x74, 0x09, 0x82, 0x2a, 0x23, 0x6d, 0x87, 0x7b, 0x46, \n    0xe9, 0x57, 0x2c, 0xc5, 0x27, 0xf5, 0x9e, 0x56, 0x02, 0x4a, 0x7b, 0x60, 0xb6, 0x86, 0x8c, 0x0d, \n    0x59, 0x2a, 0xe0, 0xf5, 0x3a, 0x7b, 0x2d, 0x39, 0xf6, 0xbb, 0x60, 0x82, 0x7e, 0xe8, 0x92, 0xbb, \n    0xab, 0x18, 0xd9, 0x9b, 0x67, 0xc1, 0x85, 0x7c, 0x1c, 0x42, 0xd6, 0x11, 0xc5, 0x79, 0x9b, 0xa0, \n    0x76, 0xb7, 0x49, 0xbe, 0x5e, 0x02, 0x5f, 0x17, 0x35, 0xeb, 0xa8, 0xd8, 0x7a, 0x1a, 0xa7, 0xa0, \n    0xaf, 0xc9, 0xdc, 0xc1, 0xd3, 0x4c, 0x3e, 0x2a, 0xb5, 0x3b, 0x98, 0x80, 0x9a, 0xad, 0xd1, 0xcd, \n    0xb2, 0x87, 0x43, 0x26, 0xe3, 0x03, 0xf2, 0xa7, 0x1f, 0x25, 0x21, 0x5f, 0x31, 0x2e, 0x75, 0xff, \n    0xbe, 0xbe, 0x4e, 0x02, 0x4f, 0x07, 0xd2, 0x08, 0xe0, 0x10, 0x72, 0x09, 0x7f, 0xcf, 0xf5, 0x98, \n    0xeb, 0x0a, 0x7a, 0x65, 0xa6, 0x33, 0xee, 0x41, 0x51, 0x12, 0x83, 0x0d, 0x69, 0x24, 0x81, 0x03, \n    0x98, 0x9e, 0x5e, 0x54, 0x06, 0x4a, 0xc9, 0x92, 0x4b, 0xfe, 0x80, 0x08, 0xcf, 0x39, 0x40, 0xca, \n    0x88, 0xb0, 0x65, 0x7e, 0xa1, 0x27, 0x39, 0xa5, 0x51, 0x29, 0xeb, 0x41, 0xb7, 0x20, 0x71, 0x18, \n    0x2e, 0x17, 0x06, 0x93, 0xfb, 0xb6, 0xcf, 0xdd, 0x6b, 0x39, 0xac, 0x92, 0xad, 0xea, 0x2a, 0xc8, \n    0x40, 0xbe, 0x25, 0x2e, 0x3d, 0xf3, 0x5a, 0xa6, 0x49, 0x0a, 0x03, 0xa6, 0x54, 0x24, 0x64, 0x79, \n    0xf4, 0x27, 0x05, 0x9d, 0xce, 0xe6, 0xa8, 0x3d, 0x7c, 0xbd, 0xa7, 0x69, 0x06, 0x5e, 0x38, 0x3e, \n    0x3d, 0xe0, 0xf0, 0xf6, 0x3a, 0x1e, 0xe3, 0x20, 0x72, 0x0c, 0x76, 0x90, 0x6b, 0x19, 0x60, 0x67, \n    0x9e, 0x51, 0x9a, 0x18, 0x6e, 0x56, 0x50, 0xdb, 0x1f, 0xec, 0x31, 0x7a, 0x1f, 0xde, 0x6b, 0x95, \n    0xba, 0x18, 0xaf, 0x2d, 0x97, 0x2c, 0xf2, 0x0d, 0x7b, 0xb2, 0x13, 0x0c, 0xff, 0xda, 0x2c, 0x19, \n    0xcd, 0x57, 0x29, 0x50, 0x77, 0x46, 0xfd, 0x02, 0x1f, 0x99, 0x3e, 0x15, 0x6d, 0x02, 0x96, 0xe5, \n    0xdc, 0xff, 0x34, 0x0c, 0xd6, 0x45, 0x7a, 0x40, 0xfc, 0x73, 0x3a, 0x39, 0x8d, 0x0a, 0x5f, 0x3d, \n    0xc8, 0xa7, 0x19, 0xf8, 0x20, 0x74, 0x6b, 0x45, 0xba, 0x9e, 0x2e, 0x2a, 0xde, 0x23, 0xa3, 0x4b, \n    0x3d, 0x7a, 0xc4, 0xa6, 0x33, 0xd5, 0xb2, 0xa7, 0x1b, 0x3a, 0x45, 0x49, 0xc0, 0x70, 0x11, 0x85, \n    0x21, 0x4d, 0xac, 0x39, 0x64, 0x96, 0x2b, 0x2e, 0x37, 0x3a, 0xbd, 0x24, 0x65, 0x6e, 0xcf, 0x85, \n    0xc0, 0x0a, 0xb1, 0x03, 0xfc, 0x2b, 0x96, 0xf9, 0x9c, 0xab, 0xe1, 0x24, 0x8d, 0x43, 0x43, 0x0d, \n    0xfb, 0xba, 0xd3, 0xd2, 0x71, 0x8d, 0xc3, 0xe8, 0x4c, 0x27, 0x08, 0xaf, 0xad, 0xd0, 0xbb, 0x2b, \n    0xe3, 0x15, 0x97, 0x49, 0xaf, 0xca, 0x25, 0xcf, 0xba, 0xd5, 0x5d, 0x0a, 0x92, 0x5f, 0xe5, 0x51, \n    0x7e, 0xc0, 0x33, 0x28, 0x9f, 0xe9, 0xa1, 0x9b, 0x21, 0x26, 0x02, 0x21, 0x47, 0xb9, 0x36, 0xa6, \n    0x94, 0x2b, 0xe3, 0x87, 0x49, 0xb1, 0xe0, 0x8e, 0xd9, 0xa3, 0x67, 0x34, 0x69, 0x59, 0xd2, 0xd8, \n    0xc7, 0xbf, 0x55, 0xa8, 0x0e, 0x4f, 0xd7, 0x68, 0x68, 0x0e, 0x0f, 0xef, 0xe1, 0x5f, 0xc7, 0x0a, \n    0xa7, 0x60, 0x60, 0x73, 0x2a, 0x4d, 0x05, 0xf5, 0xd8, 0xad, 0xb3, 0x68, 0x26, 0x10, 0x2e, 0x72, \n    0x3d, 0x99, 0x8b, 0x41, 0x67, 0xe7, 0x32, 0xe4, 0x90, 0x3e, 0x19, 0xec, 0x6e, 0x2a, 0x4c, 0x18, \n    0x40, 0x26, 0x16, 0x56, 0xc1, 0xf4, 0x37, 0x16, 0x33, 0x0c, 0x26, 0x06, 0xf6, 0x63, 0x01, 0x82, \n    0xd9, 0xc5, 0xf5, 0xb3, 0x14, 0xd1, 0x92, 0x42, 0xde, 0xb2, 0x5c, 0x69, 0x13, 0xdd, 0x83, 0x89, \n    0xf4, 0x31, 0x58, 0x65, 0x5a, 0xb4, 0xeb, 0x8f, 0x59, 0x7d, 0xb3, 0xe1, 0xf9, 0x1a, 0xea, 0x3c, \n    0x1b, 0x45, 0x39, 0x83, 0x25, 0xc1, 0x17, 0x69, 0x18, 0xc4, 0xa5, 0xe4, 0x42, 0x7f, 0xc9, 0x6e, \n    0x58, 0x0e, 0x56, 0x1a, 0xf7, 0x2c, 0x8a, 0x63, 0x3f, 0x38, 0x83, 0xf9, 0x51, 0xf0, 0xb6, 0xf9, \n    0xd5, 0xa4, 0x2f, 0xcc, 0x39, 0xed, 0xf6, 0xdb, 0xfc, 0xbf, 0x5e, 0xe7, 0x3e, 0x78, 0x27, 0xa7, \n    0xef, 0x85, 0x1a, 0x00, 0x6b, 0x06, 0xd3, 0xf7, 0x0e, 0xee, 0x83, 0xf3, 0x8d, 0xe9, 0xac, 0xe0, \n    0x4e, 0xdb, 0x11, 0xb5, 0xd0, 0xb8, 0x1e, 0xe8, 0x0e, 0xbd, 0x8c, 0xb7, 0xec, 0x13, 0xe4, 0x6e, \n    0xd4, 0xf3, 0x01, 0xba, 0x8d, 0x3f, 0x5a, 0xb5, 0x29, 0xbd, 0xa6, 0x94, 0x0b, 0x00, 0xf2, 0x06, \n    0x3d, 0xf0, 0xa8, 0x7b, 0xdf, 0xb7, 0xef, 0x3f, 0xf8, 0x1e, 0xe8, 0xd8, 0x6f, 0x95, 0x21, 0x71, \n    0x7f, 0x7f, 0xdf, 0xf4, 0xd8, 0x52, 0x6e, 0x3e, 0xab, 0xe5, 0x65, 0x34, 0xff, 0xae, 0xdf, 0xef, \n    0xef, 0x0d, 0x1e, 0x6a, 0xf9, 0x5d, 0x67, 0x17, 0x9c, 0x1e, 0xa9, 0xe6, 0x95, 0x42, 0xcc, 0x90, \n    0x1d, 0x4c, 0x3d, 0xf4, 0xaf, 0xc4, 0x27, 0xf7, 0x71, 0x6c, 0xe9, 0xc6, 0xe5, 0x04, 0x2a, 0x83, \n    0x14, 0xa2, 0x67, 0x9e, 0xa5, 0x34, 0x57, 0xda, 0xc3, 0xbf, 0x95, 0x19, 0x95, 0x5f, 0x60, 0xbe, \n    0x5a, 0x85, 0x86, 0x22, 0x29, 0x9d, 0x1f, 0x8f, 0xc1, 0x3e, 0xf7, 0x81, 0x86, 0xf3, 0xe9, 0xef, \n    0xda, 0x15, 0xeb, 0xe0, 0xc9, 0xde, 0xd1, 0x71, 0xcf, 0x6c, 0x00, 0x88, 0x65, 0xef, 0x55, 0x2a, \n    0x04, 0xcd, 0x6f, 0xdd, 0xe7, 0x15, 0x4e, 0xc5, 0x65, 0xd7, 0x64, 0xfc, 0x1a, 0x9d, 0x32, 0x09, \n    0x03, 0x05, 0x84, 0x27, 0xc3, 0x09, 0xcb, 0x48, 0x12, 0x9a, 0xe7, 0xde, 0xde, 0xee, 0xf7, 0x2d, \n    0x7d, 0xa4, 0xcf, 0x0a, 0x20, 0xd3, 0xa5, 0x4b, 0x76, 0x78, 0xde, 0x5f, 0x57, 0x5f, 0xd8, 0xd9, \n    0xee, 0x1c, 0x3e, 0xdd, 0x2b, 0xcb, 0x05, 0xce, 0xdf, 0xfe, 0xfe, 0x5e, 0xc5, 0x0b, 0xbd, 0x2b, \n    0xa2, 0x38, 0x2a, 0x2e, 0xaa, 0x81, 0x1a, 0x7c, 0x33, 0xb5, 0x72, 0x7d, 0x49, 0x29, 0x64, 0x34, \n    0xcc, 0xdb, 0x98, 0x76, 0xd6, 0xe7, 0x53, 0xc9, 0x22, 0x72, 0xa0, 0x1b, 0x14, 0xe6, 0x35, 0xba, \n    0x45, 0x0d, 0x06, 0x83, 0xf6, 0xa0, 0xbf, 0xcf, 0xfe, 0xf5, 0x3a, 0x0f, 0x1e, 0xb4, 0xf4, 0x8c, \n    0x46, 0xe9, 0x86, 0x0d, 0xb5, 0x3b, 0x68, 0xf7, 0x77, 0x07, 0x02, 0x68, 0x6f, 0xbf, 0xe5, 0xca, \n    0x8c, 0x24, 0x9a, 0x20, 0x89, 0x96, 0x01, 0xcf, 0xc7, 0x56, 0x51, 0x42, 0x06, 0x39, 0x41, 0x15, \n    0x01, 0xf1, 0x46, 0xc9, 0x0c, 0x9b, 0x5b, 0x54, 0x53, 0xe5, 0x59, 0xf4, 0x89, 0x86, 0xdc, 0x16, \n    0x99, 0x9d, 0x96, 0x16, 0x2b, 0xb1, 0x89, 0x84, 0xdc, 0xe7, 0xed, 0x10, 0x43, 0x28, 0x86, 0x2d, \n    0xfd, 0x78, 0x4a, 0x2f, 0x66, 0x19, 0xf8, 0xaf, 0x9c, 0xb0, 0x69, 0x2f, 0x7b, 0xdf, 0x6b, 0x19, \n    0x74, 0x96, 0x16, 0x68, 0xce, 0xbd, 0x90, 0xce, 0x61, 0xd9, 0x59, 0xc7, 0xa1, 0xfa, 0xf4, 0xde, \n    0x03, 0xf1, 0xdc, 0x5e, 0xa9, 0x17, 0xe9, 0x24, 0x02, 0xfb, 0x94, 0x0b, 0xf4, 0xe3, 0x92, 0x86, \n    0x51, 0x40, 0xbc, 0x65, 0xf0, 0x49, 0x68, 0xe8, 0xc3, 0x07, 0xe0, 0x80, 0x5a, 0xe5, 0xb2, 0x38, \n    0x4a, 0x77, 0x51, 0xac, 0xef, 0x6a, 0x85, 0xa5, 0xd6, 0xb4, 0xba, 0x51, 0x6c, 0x47, 0x67, 0xc7, \n    0x9d, 0x66, 0x99, 0x20, 0x82, 0xa7, 0x29, 0x13, 0x23, 0x77, 0x09, 0xc5, 0x62, 0x13, 0xeb, 0x31, \n    0xc9, 0x4a, 0xca, 0x2e, 0x70, 0xb6, 0x2d, 0xa4, 0x74, 0x00, 0x55, 0x47, 0x59, 0x76, 0xa0, 0x46, \n    0xb8, 0xbb, 0x60, 0xbb, 0x6c, 0x5e, 0xa2, 0xe5, 0x86, 0x57, 0xda, 0x72, 0x1e, 0x76, 0x65, 0x4b, \n    0xf3, 0xb0, 0x2b, 0xba, 0xac, 0x87, 0xd8, 0xc1, 0xe4, 0xed, 0x4e, 0x14, 0xc6, 0x14, 0x8d, 0x66, \n    0xd4, 0xe0, 0xf6, 0xd0, 0x20, 0x51, 0xa8, 0x3e, 0x8f, 0x0f, 0xbb, 0x30, 0x80, 0x8d, 0xe4, 0xa3, \n    0x79, 0x2d, 0x84, 0x43, 0xb8, 0x2f, 0x04, 0x15, 0xc9, 0x83, 0xb9, 0xec, 0x97, 0x1e, 0xca, 0xe2, \n    0x88, 0xcd, 0x38, 0x6a, 0x48, 0x0d, 0x23, 0xac, 0x46, 0x42, 0x6c, 0x62, 0x80, 0x1c, 0x5f, 0xce, \n    0x2e, 0x9c, 0xeb, 0x58, 0x72, 0x5a, 0x7d, 0xc4, 0x1d, 0xfb, 0xf8, 0x70, 0x31, 0x60, 0xd3, 0xf3, \n    0x89, 0xc5, 0xcd, 0x02, 0x58, 0x1b, 0x68, 0xc4, 0xd6, 0x60, 0x10, 0xee, 0x66, 0x7c, 0xb8, 0xd2, \n    0x51, 0x30, 0x61, 0x1c, 0x76, 0x57, 0xf5, 0xf0, 0xc2, 0xa7, 0xa9, 0x47, 0xec, 0x71, 0x80, 0x38, \n    0xd2, 0x53, 0x11, 0xad, 0x85, 0x0c, 0xd1, 0x9f, 0xa3, 0xcb, 0x69, 0x90, 0x34, 0x99, 0xc6, 0x50, \n    0x41, 0x8d, 0xa6, 0x71, 0x9a, 0x53, 0x16, 0xe2, 0xbd, 0x22, 0x5b, 0xd3, 0xd6, 0xf8, 0x10, 0x12, \n    0xc1, 0x64, 0xfc, 0xea, 0x5f, 0x20, 0x0c, 0xfc, 0x70, 0xd8, 0x0d, 0x1c, 0x78, 0x19, 0x54, 0x05, \n    0xb5, 0x13, 0xeb, 0x2c, 0x88, 0x73, 0x85, 0xf6, 0x18, 0xef, 0x3b, 0x30, 0xeb, 0xac, 0x95, 0x9f, \n    0xe1, 0x13, 0x5f, 0x50, 0x83, 0x02, 0xa5, 0x17, 0xa8, 0x08, 0xac, 0xa5, 0x03, 0x29, 0x42, 0x43, \n    0x92, 0xa1, 0xcc, 0xb0, 0x21, 0xd1, 0x09, 0x33, 0x53, 0x53, 0x2d, 0xfa, 0x72, 0x2c, 0x0f, 0xc5, \n    0x51, 0xee, 0xdf, 0x6b, 0x8c, 0x9f, 0xbc, 0x7d, 0x7d, 0x6f, 0x40, 0x64, 0xcb, 0x3d, 0x87, 0x05, \n    0xeb, 0x8f, 0x89, 0x24, 0xc8, 0x40, 0xa1, 0xeb, 0xa4, 0x6d, 0xfb, 0x0d, 0xd7, 0xf2, 0x34, 0xc4, \n    0xba, 0x36, 0x34, 0x36, 0xf4, 0xc7, 0xa2, 0x27, 0xd7, 0x30, 0xe5, 0xac, 0x0d, 0x00, 0xdf, 0x20, \n    0xd5, 0x7e, 0x9e, 0x9f, 0xe0, 0x45, 0x18, 0x14, 0x01, 0xe4, 0xaf, 0x50, 0x28, 0x16, 0xfc, 0xee, \n    0xb1, 0x9c, 0x82, 0x13, 0xaf, 0x2b, 0x4a, 0x2d, 0x36, 0xcc, 0x02, 0x1d, 0xe8, 0xd8, 0x6d, 0x85, \n    0xef, 0x1d, 0x5e, 0x5d, 0x8f, 0x70, 0xcc, 0xf5, 0xa2, 0x91, 0xd3, 0x62, 0xbd, 0x6a, 0x48, 0x03, \n    0x13, 0x1d, 0xd6, 0x28, 0x59, 0xd0, 0x2c, 0x2a, 0x44, 0xe2, 0x1b, 0x52, 0xa8, 0x69, 0x79, 0xd0, \n    0x20, 0xcc, 0xb7, 0x37, 0xc8, 0x22, 0xa3, 0xb3, 0x51, 0xe3, 0xbb, 0x06, 0x91, 0x2d, 0xe6, 0xf1, \n    0x5b, 0xc4, 0x83, 0x1a, 0x62, 0x4f, 0x5d, 0xb9, 0xd6, 0x08, 0xd1, 0xfb, 0x7b, 0x0e, 0x69, 0x4a, \n    0x09, 0x4a, 0xde, 0x94, 0xfc, 0x79, 0x96, 0x67, 0x42, 0x10, 0x62, 0x5e, 0xe9, 0xf3, 0xa8, 0x66, \n    0x03, 0xc4, 0x32, 0x1b, 0x0a, 0x46, 0x8a, 0x0e, 0x84, 0xb4, 0x0b, 0x7e, 0xc5, 0x2a, 0xf2, 0xd2, \n    0x40, 0x1a, 0x45, 0x3a, 0x9f, 0xc7, 0xf4, 0x58, 0xf4, 0xe0, 0xbc, 0xa6, 0xea, 0xc6, 0x21, 0x85, \n    0xcd, 0x16, 0x08, 0x74, 0x82, 0x04, 0x37, 0xf9, 0x38, 0x7e, 0x77, 0x7c, 0xf7, 0xb0, 0x3b, 0x01, \n    0x89, 0x70, 0x94, 0xd6, 0xc4, 0xd5, 0x35, 0xda, 0x8a, 0x05, 0x5e, 0xcf, 0x6d, 0xcb, 0x05, 0x96, \n    0x58, 0x5c, 0x79, 0x56, 0x19, 0x3d, 0x43, 0xaa, 0x90, 0xd0, 0xf1, 0xed, 0xb8, 0x38, 0xd8, 0x40, \n    0xda, 0xf5, 0xd8, 0x12, 0x54, 0x0c, 0x85, 0x6d, 0xbe, 0x11, 0xdb, 0xcd, 0x18, 0x15, 0x72, 0x8d, \n    0x40, 0xab, 0x1c, 0x4c, 0x4a, 0xb5, 0x30, 0xa4, 0xdf, 0xb0, 0x60, 0xa9, 0x5b, 0xa7, 0x4c, 0x0c, \n    0x51, 0x02, 0x86, 0x52, 0x3c, 0x85, 0x90, 0xea, 0x18, 0x65, 0x0b, 0x5f, 0x34, 0x53, 0xdd, 0x23, \n    0x1d, 0x63, 0x79, 0x90, 0xae, 0x1b, 0x0e, 0x00, 0x7c, 0xdf, 0x0c, 0xc6, 0x32, 0x0d, 0xc7, 0x22, \n    0x0c, 0x0a, 0x7a, 0x56, 0x4b, 0x37, 0xc0, 0x94, 0xf0, 0x37, 0x7a, 0x35, 0xf2, 0x14, 0xee, 0x0f, \n    0x0f, 0xbb, 0x6c, 0x74, 0x3d, 0x32, 0x0e, 0x28, 0xcd, 0xc5, 0x40, 0xa6, 0x4c, 0x86, 0x45, 0xf8, \n    0x7a, 0x82, 0x00, 0xcb, 0x2d, 0xdf, 0x27, 0xaf, 0x56, 0x68, 0x58, 0x39, 0x84, 0xfe, 0x38, 0x26, \n    0x13, 0x4a, 0x20, 0xa9, 0x81, 0xda, 0x3e, 0xbc, 0x80, 0xda, 0x33, 0x82, 0x4a, 0x26, 0xbe, 0x20, \n    0xbe, 0x5f, 0x4f, 0x47, 0x97, 0x4f, 0x5a, 0x27, 0x24, 0x87, 0x1a, 0x7c, 0x15, 0x01, 0xca, 0xe2, \n    0x98, 0xeb, 0xe3, 0x3b, 0x71, 0x75, 0xbd, 0xd8, 0xd8, 0x06, 0x23, 0xd1, 0x36, 0x23, 0x95, 0x93, \n    0x55, 0xd8, 0xc4, 0xee, 0xb0, 0x79, 0x0f, 0x52, 0x90, 0x29, 0x5d, 0xa4, 0x31, 0x44, 0x98, 0x51, \n    0x43, 0x4e, 0x57, 0xab, 0x1a, 0x7f, 0x17, 0xd7, 0xbc, 0xb7, 0xc1, 0x79, 0x7e, 0xc3, 0x3e, 0x7f, \n    0x2e, 0xc7, 0x1a, 0x26, 0xc1, 0xaf, 0x7e, 0xc7, 0xe0, 0x96, 0x4f, 0x44, 0x92, 0xf5, 0x72, 0x42, \n    0xb3, 0xcf, 0x61, 0xf9, 0x7a, 0x41, 0x88, 0x9c, 0x75, 0x83, 0x10, 0x4c, 0xd7, 0x24, 0x34, 0x9b, \n    0x71, 0x42, 0x3f, 0xe1, 0x1e, 0x3e, 0x97, 0xc9, 0x13, 0xf6, 0xb9, 0xd6, 0xc1, 0x5d, 0x47, 0x6a, \n    0xdd, 0x13, 0xf7, 0x7d, 0xd7, 0x5d, 0xfd, 0x5e, 0xbd, 0xbb, 0xb3, 0x5b, 0xb2, 0x35, 0x3e, 0xaf, \n    0x8c, 0xdc, 0x7a, 0x53, 0xd5, 0x25, 0xa4, 0xea, 0x70, 0x9e, 0x0c, 0x5d, 0xef, 0xe5, 0xca, 0x8e, \n    0x99, 0xd4, 0xa8, 0x0d, 0xe2, 0xb1, 0xe0, 0xd6, 0xca, 0x0c, 0xa4, 0x41, 0x6c, 0x0f, 0x5b, 0x04, \n    0xb0, 0x5c, 0x27, 0xc1, 0x9c, 0x1c, 0xa7, 0xe1, 0x4d, 0xc0, 0x64, 0xdb, 0x0d, 0x80, 0xe5, 0xc7, \n    0x9b, 0xad, 0x9a, 0x95, 0x66, 0xb0, 0xce, 0x63, 0xc3, 0x94, 0x1d, 0xe6, 0xf2, 0x35, 0x92, 0x43, \n    0x7f, 0xf9, 0x26, 0x3d, 0xbf, 0xb1, 0xb3, 0xfc, 0x3c, 0x0d, 0x72, 0xdf, 0x71, 0xa6, 0x4c, 0x46, \n    0x3e, 0x68, 0xe5, 0x4c, 0xbc, 0x98, 0xb0, 0xe7, 0xf9, 0x3a, 0xa9, 0x12, 0xce, 0xbd, 0x08, 0x92, \n    0x10, 0xc4, 0xc6, 0x48, 0xd8, 0x26, 0x79, 0xc2, 0x81, 0x90, 0x3c, 0x95, 0x79, 0xa4, 0x99, 0x45, \n    0xb1, 0xc7, 0x37, 0xce, 0xa2, 0xbe, 0x20, 0x9d, 0xc0, 0x09, 0x6f, 0x92, 0x4e, 0x7c, 0x83, 0xf9, \n    0x84, 0x61, 0x4e, 0x5b, 0x24, 0x0f, 0x50, 0xda, 0x55, 0xea, 0x6b, 0xbd, 0x73, 0xb8, 0x31, 0x69, \n    0x70, 0x47, 0x14, 0x24, 0x41, 0x84, 0x12, 0xf6, 0xd1, 0x88, 0x21, 0x92, 0xb4, 0x8d, 0x78, 0x5d, \n    0x1a, 0xc9, 0x89, 0xda, 0x04, 0x56, 0x2a, 0x28, 0x52, 0x01, 0xc5, 0x11, 0xf3, 0x2d, 0xce, 0x24, \n    0x96, 0x95, 0x92, 0x3c, 0xa0, 0xf1, 0x22, 0x12, 0x37, 0x17, 0xa6, 0x8c, 0x2c, 0xc8, 0x68, 0x7f, \n    0xdc, 0x98, 0x1c, 0x6b, 0xd3, 0x6d, 0x08, 0x75, 0x2c, 0x21, 0xc2, 0xba, 0xf9, 0x1f, 0x4e, 0x0c, \n    0xb8, 0x1b, 0x7e, 0xf9, 0x05, 0x29, 0x10, 0xc3, 0x20, 0xd6, 0x8f, 0x7f, 0x36, 0x16, 0xf0, 0xe5, \n    0xff, 0x45, 0xba, 0xc3, 0xb6, 0x65, 0xa0, 0x5a, 0xc6, 0x5f, 0x9f, 0xcb, 0x17, 0xc7, 0x21, 0x18, \n    0x13, 0x17, 0x06, 0x67, 0xcf, 0xf9, 0x24, 0x5f, 0x2b, 0x57, 0xf8, 0x07, 0x8d, 0xbe, 0x1a, 0x7f, \n    0xbf, 0x34, 0xff, 0xb5, 0x73, 0xdf, 0x6f, 0x25, 0xef, 0x55, 0x47, 0x04, 0xc7, 0xaf, 0xc5, 0xa7, \n    0x9b, 0x71, 0xaa, 0xe0, 0x79, 0x55, 0xac, 0xae, 0x38, 0xb7, 0xe5, 0xb5, 0xc1, 0x6d, 0x39, 0xe9, \n    0x3f, 0xcc, 0x2d, 0x3f, 0xf8, 0x38, 0x7e, 0x82, 0xbf, 0x6e, 0xc6, 0x27, 0x87, 0xe4, 0x69, 0x31, \n    0xff, 0xc8, 0x39, 0x14, 0x17, 0x06, 0x7b, 0x4f, 0xf8, 0x2c, 0x5f, 0x4f, 0xef, 0xaf, 0xb3, 0x85, \n    0x6b, 0x92, 0xfc, 0x0d, 0x29, 0x3e, 0x24, 0x59, 0x3c, 0x48, 0x8f, 0x9f, 0xb1, 0x80, 0x7b, 0x5d, \n    0x82, 0x5f, 0x8f, 0x29, 0x84, 0x62, 0xb5, 0x90, 0x11, 0x5f, 0xe5, 0x1f, 0x8f, 0xd9, 0xdd, 0x8d, \n    0x58, 0xbf, 0x6e, 0x69, 0xe0, 0x16, 0xd9, 0x37, 0x5c, 0x22, 0x7c, 0x49, 0xaa, 0x5f, 0xc6, 0xa6, \n    0xed, 0x61, 0x74, 0x13, 0xf8, 0xdb, 0x8b, 0x0a, 0x11, 0x66, 0xde, 0xa4, 0x71, 0x2d, 0xd0, 0xb5, \n    0xb5, 0x84, 0x6c, 0xd3, 0xfe, 0xbb, 0x15, 0x13, 0xd6, 0x76, 0x85, 0xb3, 0xc3, 0x0f, 0x9f, 0xc5, \n    0x61, 0x72, 0xe5, 0xd1, 0xf0, 0xca, 0xdd, 0x46, 0x5f, 0x04, 0xb9, 0xcf, 0x3a, 0xc8, 0x7c, 0x3b, \n    0x96, 0x1a, 0xee, 0xf3, 0x70, 0x35, 0x56, 0xdd, 0x7b, 0x72, 0x7b, 0x9a, 0xae, 0x2e, 0x0e, 0xc8, \n    0xa0, 0x37, 0xd8, 0xed, 0x90, 0x47, 0x20, 0x01, 0xb6, 0x97, 0x95, 0x13, 0x50, 0x7d, 0x9a, 0x9d, \n    0xd1, 0xb0, 0x83, 0xdb, 0x29, 0x1a, 0x2c, 0xeb, 0x5d, 0xf3, 0x63, 0x76, 0xa2, 0x0b, 0xfe, 0xfb, \n    0x24, 0x0e, 0x92, 0x53, 0x00, 0x88, 0x47, 0x49, 0x9a, 0xae, 0x28, 0xee, 0xeb, 0xf1, 0x2e, 0xf5, \n    0xa2, 0x28, 0x56, 0xf9, 0xb0, 0xdb, 0x9d, 0x47, 0xc5, 0x62, 0x3d, 0xe9, 0x4c, 0xd3, 0x65, 0x77, \n    0x0a, 0x34, 0x43, 0xbd, 0x58, 0x24, 0x45, 0x37, 0xc8, 0x2f, 0x92, 0xa9, 0x0f, 0x16, 0xe6, 0xcf, \n    0x72, 0x3c, 0x12, 0xc1, 0xe6, 0x03, 0x7e, 0x8e, 0xa1, 0x12, 0xc6, 0x73, 0x2d, 0xe7, 0x00, 0x45, \n    0x3e, 0x07, 0x45, 0xfd, 0x6e, 0x09, 0x17, 0x19, 0xbb, 0x94, 0x0f, 0xd8, 0xe7, 0x7c, 0x9a, 0x45, \n    0x2b, 0xd9, 0x3d, 0x3b, 0x0b, 0x32, 0x82, 0x8a, 0xc4, 0x52, 0x12, 0x32, 0x22, 0x3d, 0xb1, 0xa3, \n    0x0a, 0xde, 0x09, 0x77, 0xe3, 0xf1, 0x88, 0xda, 0x71, 0x7e, 0xf6, 0x38, 0x28, 0x02, 0x78, 0xf8, \n    0xfe, 0x43, 0xf5, 0xe9, 0xeb, 0x60, 0x4e, 0x35, 0x38, 0x70, 0x08, 0x79, 0x01, 0xe2, 0x99, 0x42, \n    0x14, 0xcb, 0x5f, 0xd3, 0x4c, 0x3c, 0xee, 0xef, 0x6a, 0x90, 0x45, 0x5a, 0x04, 0xf1, 0x53, 0xb6, \n    0x1d, 0x4f, 0xc3, 0x37, 0x7c, 0xa8, 0x86, 0x42, 0xff, 0xd9, 0xed, 0x92, 0x63, 0xd0, 0x4a, 0x3c, \n    0x41, 0x40, 0x66, 0xeb, 0x84, 0xd7, 0x96, 0xa8, 0xa7, 0x20, 0x32, 0xf8, 0xc4, 0x37, 0x9a, 0x26, \n    0xe9, 0x27, 0xc2, 0x36, 0x96, 0x4a, 0x96, 0xd8, 0xe5, 0xf1, 0x04, 0xb0, 0x4a, 0x28, 0xaf, 0x75, \n    0x79, 0xc5, 0x26, 0x50, 0x88, 0x41, 0x27, 0xc4, 0x21, 0x47, 0x71, 0x1a, 0x3c, 0xcd, 0x48, 0xbe, \n    0x48, 0xb3, 0x02, 0x8b, 0xcb, 0xbc, 0xc4, 0xb5, 0xa3, 0x63, 0xa1, 0xb1, 0xb6, 0xc5, 0x4b, 0x80, \n    0xd1, 0x62, 0x9d, 0x25, 0x24, 0x4c, 0xa7, 0x6b, 0x44, 0xd4, 0x01, 0x15, 0x11, 0x38, 0x8f, 0x2e, \n    0x9e, 0x85, 0x38, 0x5a, 0xee, 0x50, 0x1f, 0x58, 0x6c, 0xbd, 0x85, 0x15, 0x9f, 0x2e, 0x88, 0x38, \n    0xe8, 0xb9, 0x02, 0x31, 0xf1, 0x47, 0x8a, 0x4b, 0xb0, 0xe5, 0x63, 0xac, 0x6a, 0x3d, 0x6d, 0x42, \n    0x2e, 0x5d, 0x78, 0x82, 0xf2, 0x52, 0x93, 0xfe, 0xcf, 0x9a, 0x66, 0x17, 0x6f, 0x05, 0x0b, 0xa0, \n    0xd5, 0x5e, 0x13, 0x77, 0x8d, 0x9b, 0xe5, 0x51, 0x19, 0x0e, 0x26, 0x4a, 0xf3, 0xeb, 0x40, 0xc5, \n    0x30, 0x0d, 0x1c, 0xe7, 0xc3, 0x4d, 0xe1, 0x27, 0xc1, 0x74, 0xe1, 0x15, 0x64, 0x34, 0x26, 0x45, \n    0x87, 0x99, 0xde, 0xf3, 0x28, 0x2f, 0x3a, 0x19, 0x5d, 0xa6, 0x67, 0x50, 0x6b, 0x73, 0x4e, 0x9a, \n    0xad, 0x12, 0x4e, 0x4e, 0xa8, 0x60, 0x73, 0x84, 0xcd, 0x35, 0x58, 0xf0, 0x3e, 0x5e, 0x13, 0x5b, \n    0x05, 0x3a, 0x98, 0x64, 0x12, 0xed, 0x0d, 0x68, 0x2d, 0x16, 0x51, 0xde, 0xc1, 0x8d, 0xa8, 0x9c, \n    0x16, 0x1d, 0x7e, 0x57, 0x0d, 0xdd, 0xf1, 0xf8, 0x8d, 0x96, 0x83, 0x1e, 0x8e, 0xb6, 0x64, 0x02, \n    0xd1, 0xdc, 0x74, 0x14, 0xa3, 0x4f, 0x32, 0xe6, 0x3c, 0x69, 0xd9, 0x25, 0x27, 0xac, 0x85, 0x00, \n    0x90, 0x94, 0x68, 0x4d, 0x00, 0x66, 0x54, 0x52, 0x02, 0xc4, 0x5e, 0x5a, 0xb3, 0x4d, 0x11, 0x85, \n    0x6d, 0x72, 0x4a, 0xe9, 0x4a, 0x5b, 0xe8, 0x68, 0x46, 0x3c, 0x76, 0xab, 0x54, 0xb5, 0x1d, 0x18, \n    0xd7, 0xb2, 0x69, 0xc3, 0xa3, 0xd9, 0x1a, 0xf9, 0x34, 0x96, 0x46, 0xe0, 0x82, 0xe0, 0xd3, 0x56, \n    0x80, 0xca, 0x9c, 0x80, 0x4b, 0x1e, 0xac, 0x8b, 0x85, 0x88, 0x6b, 0x35, 0x8c, 0x9f, 0x4b, 0xd0, \n    0x30, 0x09, 0x48, 0xb5, 0xde, 0x78, 0x26, 0x00, 0x56, 0x1c, 0x0f, 0xf0, 0x55, 0x25, 0x2f, 0x8f, \n    0xf4, 0x55, 0x97, 0x1e, 0xd0, 0x3c, 0x63, 0x19, 0xe6, 0x08, 0x58, 0x68, 0x96, 0x0d, 0x8f, 0x66, \n    0xcb, 0x41, 0x08, 0xcb, 0x45, 0x4d, 0x1a, 0x18, 0xb0, 0x22, 0x02, 0x06, 0x20, 0x11, 0xf0, 0xab, \n    0x73, 0x16, 0xe0, 0x89, 0xd6, 0x11, 0x69, 0x6a, 0xe3, 0x61, 0x06, 0x99, 0xeb, 0x01, 0xfe, 0x28, \n    0x01, 0x87, 0xfe, 0xf3, 0xc9, 0x8b, 0xe7, 0x38, 0x8a, 0x67, 0x7e, 0x4d, 0x7d, 0xa8, 0x96, 0xcc, \n    0xc1, 0x68, 0x99, 0xcd, 0xa1, 0x96, 0x66, 0x6b, 0x5a, 0x83, 0xb3, 0x6e, 0x14, 0x67, 0x76, 0x52, \n    0x24, 0xcf, 0xf0, 0x51, 0x14, 0x76, 0xf2, 0x55, 0x1c, 0x15, 0x5e, 0xd3, 0x6f, 0xb6, 0xde, 0xf7, \n    0x3f, 0xe8, 0xc8, 0x44, 0x97, 0xaa, 0x49, 0xee, 0xf2, 0xe1, 0x26, 0x9d, 0xf6, 0x32, 0x8b, 0x9c, \n    0x2c, 0x97, 0x0b, 0x4d, 0x7e, 0x20, 0x80, 0x93, 0x0c, 0x49, 0xf3, 0x6e, 0xb3, 0x46, 0x87, 0xdf, \n    0xe2, 0x09, 0xff, 0x80, 0x88, 0x93, 0x05, 0x6d, 0x54, 0xbf, 0x59, 0x42, 0x6e, 0x8d, 0xc8, 0x3a, \n    0x09, 0xe9, 0x0c, 0x4f, 0xae, 0x85, 0x24, 0x5b, 0x27, 0x24, 0xc8, 0x99, 0xf3, 0x65, 0xee, 0x18, \n    0xf4, 0xf8, 0xd5, 0xbf, 0xc4, 0xc9, 0x0e, 0xb2, 0x82, 0x08, 0x9a, 0x5b, 0x5a, 0x8e, 0xd1, 0x51, \n    0x1c, 0x20, 0xc0, 0x26, 0x48, 0x9b, 0x2c, 0xf3, 0x79, 0x1b, 0xf0, 0x6a, 0x6a, 0x0e, 0xbc, 0x19, \n    0x07, 0x22, 0xac, 0x15, 0x60, 0xf7, 0x0e, 0x1c, 0x83, 0x31, 0xc3, 0xb1, 0xc6, 0x02, 0x72, 0x63, \n    0xa4, 0x7e, 0xcc, 0x03, 0x86, 0x22, 0x31, 0x8e, 0x65, 0x92, 0xc7, 0x04, 0x60, 0x04, 0xeb, 0x4d, \n    0x75, 0xf8, 0x29, 0x31, 0x18, 0xd9, 0x98, 0xc4, 0xeb, 0xcc, 0xbb, 0xb7, 0xfa, 0xd4, 0x6a, 0x1c, \n    0xe8, 0x56, 0x89, 0x55, 0x4f, 0x2a, 0xc5, 0xd3, 0x14, 0xf2, 0x41, 0x2d, 0xd6, 0xc3, 0x82, 0x16, \n    0x7e, 0x92, 0x03, 0xdd, 0x20, 0x9b, 0xf2, 0xe0, 0x45, 0x73, 0x0b, 0x97, 0x75, 0x55, 0x67, 0xd6, \n    0x6e, 0x2c, 0x9a, 0x33, 0xad, 0x59, 0x66, 0x76, 0xca, 0xa2, 0x0c, 0x9a, 0xd6, 0x7a, 0x69, 0x67, \n    0x33, 0xc2, 0xf4, 0xf7, 0xe9, 0xc4, 0x5a, 0x27, 0xb7, 0x40, 0xd9, 0x29, 0x8e, 0x2d, 0x25, 0xea, \n    0x14, 0xa4, 0x14, 0x95, 0x29, 0x4d, 0x72, 0xfb, 0x36, 0xe1, 0x44, 0x54, 0x84, 0xea, 0xd5, 0x71, \n    0xf7, 0x33, 0x8d, 0x57, 0x30, 0x51, 0x91, 0x62, 0x49, 0xbb, 0x0c, 0x20, 0x84, 0xc8, 0x76, 0xbd, \n    0xc5, 0x28, 0x7f, 0xac, 0x9a, 0xf9, 0x9e, 0x1a, 0x57, 0x89, 0xb4, 0x10, 0x76, 0xd0, 0x5f, 0x24, \n    0xf4, 0x9c, 0x3c, 0xc6, 0xd3, 0x61, 0xe5, 0x48, 0xc5, 0x8a, 0xc8, 0x00, 0xa2, 0xfc, 0x65, 0xf0, \n    0xd2, 0xc3, 0xf1, 0x98, 0x03, 0x20, 0x6e, 0xaf, 0x85, 0x96, 0x57, 0x1e, 0xda, 0x1d, 0x32, 0x6c, \n    0xe0, 0x84, 0x9f, 0xa7, 0xf8, 0x2a, 0xc7, 0xdb, 0x22, 0x8b, 0x92, 0xb9, 0x9b, 0x19, 0xc9, 0xd2, \n    0xdf, 0xfd, 0x67, 0x9b, 0x79, 0x60, 0x00, 0x3b, 0xea, 0x41, 0xd8, 0x09, 0x0e, 0xc2, 0x1a, 0xef, \n    0xd8, 0x25, 0xfd, 0xda, 0xf3, 0x7c, 0x8d, 0x3f, 0xce, 0xfc, 0xf1, 0x2d, 0x4d, 0x42, 0x58, 0xce, \n    0xe5, 0x12, 0x28, 0x47, 0xe5, 0x78, 0x71, 0xfc, 0x0e, 0x0a, 0x1f, 0xd0, 0x01, 0x1e, 0xb3, 0x71, \n    0x27, 0x0a, 0xf8, 0xc9, 0x21, 0x40, 0xf0, 0x73, 0x9a, 0x38, 0x86, 0x6f, 0x2f, 0x80, 0x57, 0x03, \n    0x2d, 0xcf, 0xa0, 0x26, 0xc2, 0x6d, 0x1b, 0x91, 0xd4, 0x5a, 0xba, 0x84, 0xe0, 0x50, 0xfe, 0x61, \n    0xf5, 0xa7, 0x27, 0x6a, 0x33, 0x0a, 0xc9, 0x9d, 0xd7, 0xec, 0x9e, 0x07, 0x51, 0x81, 0xcf, 0x9a, \n    0x4a, 0x8f, 0x3b, 0x30, 0x69, 0xe2, 0x89, 0x5a, 0x9b, 0x62, 0x64, 0xd2, 0xfc, 0x46, 0xe9, 0x32, \n    0x9b, 0xac, 0x63, 0x8c, 0x8a, 0x77, 0xf2, 0xe8, 0xa7, 0x66, 0x9b, 0x34, 0x0e, 0x27, 0xd9, 0xf8, \n    0x75, 0x8c, 0xef, 0xd9, 0x11, 0x6c, 0xa7, 0x90, 0x8b, 0x74, 0x9d, 0xf1, 0x66, 0x32, 0x33, 0x6a, \n    0x20, 0x1a, 0xb9, 0x61, 0x95, 0x8e, 0xd8, 0x5c, 0x6b, 0x13, 0xd4, 0x44, 0x4e, 0x9b, 0xe6, 0x54, \n    0x5a, 0x6e, 0xeb, 0x61, 0xf3, 0x29, 0x14, 0xd8, 0xa1, 0x46, 0x3b, 0x91, 0x59, 0x3e, 0x4b, 0x67, \n    0x2c, 0xd6, 0x4b, 0xec, 0x2e, 0xce, 0xe1, 0xe9, 0x0d, 0x18, 0x47, 0x87, 0x70, 0x4b, 0x3e, 0xeb, \n    0xa4, 0xa7, 0x86, 0x37, 0xc5, 0xb4, 0x0c, 0x53, 0x09, 0x94, 0xc6, 0x93, 0x2c, 0x4b, 0x33, 0x94, \n    0x0e, 0x64, 0x02, 0x60, 0xa0, 0x14, 0x2f, 0x8d, 0x5c, 0xe6, 0xaa, 0xfc, 0x28, 0x4c, 0x53, 0xa1, \n    0xfd, 0x23, 0xc7, 0x32, 0xa0, 0x22, 0x09, 0x49, 0x5a, 0xc8, 0x8a, 0x1c, 0x83, 0x2c, 0x8c, 0xbd, \n    0xc1, 0x1c, 0x5c, 0x99, 0xcc, 0x1c, 0x70, 0x0c, 0xa4, 0x3e, 0x73, 0xd3, 0xa9, 0xbb, 0x83, 0xbd, \n    0xe9, 0x1a, 0xb5, 0xd9, 0xa6, 0x01, 0x8a, 0x88, 0x91, 0x6e, 0xcd, 0x07, 0x7e, 0x21, 0x83, 0x0c, \n    0x80, 0x31, 0x39, 0xc4, 0x70, 0xcf, 0x06, 0xe9, 0x24, 0xd7, 0xf8, 0xbe, 0x9f, 0x20, 0x55, 0x8e, \n    0x21, 0x08, 0x10, 0xf0, 0xa8, 0x19, 0x9d, 0xc3, 0x27, 0xac, 0xad, 0xd8, 0xaa, 0xd9, 0x61, 0x19, \n    0x16, 0x86, 0x19, 0x74, 0xb5, 0xae, 0x28, 0x1b, 0x0a, 0x3c, 0xf3, 0x2a, 0xaf, 0x35, 0x09, 0xcb, \n    0xf5, 0x65, 0x0f, 0xff, 0xbd, 0x57, 0xb7, 0xe4, 0xcf, 0xcc, 0xfb, 0x9a, 0xda, 0x84, 0x6c, 0xc1, \n    0x65, 0x2a, 0xe9, 0x21, 0x04, 0xbe, 0x31, 0x1e, 0xd2, 0x4f, 0x2d, 0x0b, 0x99, 0x2e, 0xc4, 0x27, \n    0x49, 0x91, 0x5d, 0xe8, 0xf9, 0xf3, 0x94, 0x55, 0xf9, 0xa2, 0x32, 0x84, 0x2c, 0x32, 0x3a, 0x33, \n    0xb8, 0x22, 0x25, 0x14, 0x8f, 0xe7, 0xd8, 0xb9, 0x42, 0x4a, 0xca, 0x24, 0xbb, 0x66, 0xb4, 0x4e, \n    0xf7, 0x47, 0x7d, 0xc8, 0xa6, 0x7e, 0xda, 0xce, 0x25, 0x7e, 0xee, 0xc8, 0x1b, 0x57, 0x5b, 0xec, \n    0x6d, 0xfa, 0x06, 0xe4, 0xd6, 0x50, 0xa2, 0xa7, 0x26, 0xc0, 0xd8, 0xd5, 0x56, 0x70, 0xac, 0xab, \n    0x26, 0xa0, 0xe0, 0xf3, 0x56, 0x30, 0xa2, 0xa5, 0x26, 0xa0, 0xd8, 0x95, 0x80, 0xfb, 0x58, 0x11, \n    0x9e, 0x58, 0xf5, 0x60, 0x05, 0xfe, 0x36, 0x3c, 0x66, 0x6f, 0x23, 0x29, 0x99, 0x9a, 0x0b, 0xe3, \n    0x16, 0x3b, 0x18, 0xfc, 0x93, 0x33, 0x58, 0x4a, 0xcc, 0xbb, 0xb0, 0x05, 0xe4, 0x35, 0xd9, 0x06, \n    0x34, 0xf8, 0xe9, 0xb2, 0x51, 0x70, 0x66, 0x69, 0xf9, 0x97, 0x97, 0x57, 0xfc, 0xcf, 0x17, 0x17, \n    0x59, 0x55, 0xc6, 0xd0, 0x42, 0xbd, 0xb2, 0x0b, 0x34, 0x1e, 0x91, 0xdd, 0x0a, 0xf1, 0xc4, 0xae, \n    0x5a, 0xad, 0xcd, 0xf5, 0x36, 0xcb, 0xad, 0xed, 0x59, 0xdc, 0x15, 0x75, 0x49, 0x55, 0x65, 0x38, \n    0x97, 0x11, 0xa0, 0x46, 0x01, 0x3d, 0xca, 0xb2, 0xe0, 0xa2, 0x33, 0xcb, 0xd2, 0xa5, 0xc7, 0xd0, \n    0x54, 0xe5, 0xc4, 0x2c, 0xa9, 0xd5, 0x59, 0x06, 0x2b, 0x0f, 0xdb, 0x57, 0xe3, 0x2a, 0xd5, 0x12, \n    0x65, 0x84, 0x2e, 0x99, 0xc6, 0xa5, 0x7d, 0x81, 0x8c, 0xd8, 0xe6, 0x04, 0x63, 0xc4, 0x07, 0xfa, \n    0x9b, 0x0e, 0x72, 0x94, 0x9b, 0x71, 0xe0, 0x05, 0xa9, 0x85, 0x43, 0xf8, 0xd7, 0x76, 0x3d, 0x62, \n    0x01, 0x63, 0x88, 0x13, 0x2a, 0x13, 0xad, 0x0e, 0xbb, 0xaa, 0xcc, 0x77, 0x55, 0x25, 0xa1, 0x2a, \n    0xa1, 0xb8, 0x5c, 0x7c, 0x7c, 0x79, 0xba, 0x86, 0x6d, 0x74, 0xba, 0x3b, 0x6c, 0x40, 0x07, 0xea, \n    0x43, 0xc7, 0x7a, 0xe2, 0x9f, 0x72, 0x80, 0x0a, 0x71, 0xec, 0x06, 0xbb, 0x38, 0x70, 0xee, 0xaf, \n    0x68, 0x20, 0x55, 0x33, 0xe0, 0xd5, 0xb8, 0x66, 0x06, 0x35, 0xf3, 0x6e, 0x1d, 0x36, 0xaf, 0x11, \n    0x8e, 0x11, 0x11, 0x6a, 0x47, 0xd9, 0xd7, 0x9b, 0x0a, 0x79, 0xf7, 0xf4, 0x1b, 0xba, 0x04, 0xef, \n    0x56, 0x98, 0xd7, 0x37, 0x2d, 0x10, 0x83, 0x2c, 0x83, 0xa4, 0x2b, 0x57, 0xe7, 0x85, 0xcd, 0xc0, \n    0x5f, 0x5e, 0x50, 0x85, 0x93, 0x38, 0x25, 0x81, 0x95, 0x13, 0x1e, 0x87, 0x6e, 0xb8, 0x42, 0x5b, \n    0x7d, 0x2a, 0x81, 0x5a, 0x9f, 0x02, 0x16, 0xca, 0x43, 0xaa, 0x48, 0x29, 0xda, 0xd7, 0x65, 0x14, \n    0x25, 0x51, 0x7f, 0x7b, 0xea, 0xcc, 0x97, 0x00, 0x73, 0x11, 0xcf, 0x2c, 0x35, 0xb7, 0x6b, 0xb2, \n    0xe4, 0x40, 0x1c, 0x02, 0x63, 0x67, 0x08, 0xf2, 0x11, 0x00, 0x7a, 0xc7, 0x00, 0x6a, 0x32, 0x24, \n    0xde, 0xcc, 0x01, 0x3a, 0xc9, 0x9a, 0x2d, 0x18, 0x09, 0x58, 0xc6, 0xc1, 0xda, 0x74, 0x4e, 0xf2, \n    0x0c, 0xfc, 0xeb, 0x4c, 0xef, 0xfb, 0x62, 0x57, 0x18, 0x8b, 0x48, 0xd1, 0x16, 0x47, 0x3c, 0x4f, \n    0xc5, 0xa5, 0x96, 0x81, 0xc8, 0x11, 0x22, 0xcc, 0x78, 0xe5, 0x16, 0x77, 0x5b, 0x66, 0x57, 0x78, \n    0x25, 0xb3, 0xcb, 0x0d, 0x80, 0x6a, 0x5b, 0x98, 0x01, 0xca, 0xab, 0x2d, 0x00, 0xcb, 0xd9, 0xb6, \n    0x9c, 0x89, 0x07, 0x6c, 0x06, 0xc1, 0x3e, 0x96, 0x20, 0xba, 0xb6, 0x56, 0xc0, 0x30, 0x5e, 0xb7, \n    0xad, 0x6c, 0x79, 0xc3, 0x2c, 0x3c, 0x54, 0x33, 0x00, 0xf6, 0x71, 0x0b, 0x10, 0xec, 0x16, 0x70, \n    0x08, 0x97, 0x1d, 0xda, 0x1d, 0xc4, 0x94, 0x9d, 0x2f, 0x86, 0xa5, 0xd1, 0x0c, 0x62, 0x49, 0x8b, \n    0x45, 0x0a, 0x0e, 0xbb, 0xf9, 0xfa, 0xd5, 0xdb, 0x93, 0xa6, 0xe6, 0xb4, 0xb1, 0x99, 0x34, 0x54, \n    0x33, 0x2a, 0xc3, 0xb0, 0x32, 0x5e, 0xd0, 0x80, 0xb6, 0xc0, 0xab, 0xf5, 0x24, 0x36, 0xa6, 0xbd, \n    0xd7, 0x26, 0xbe, 0x37, 0x4a, 0x7d, 0x2d, 0x9f, 0x62, 0xa7, 0xbf, 0xb8, 0x01, 0xe6, 0x99, 0x7e, \n    0xc6, 0x41, 0xe6, 0x3a, 0x2e, 0x2a, 0x44, 0x6a, 0x45, 0x27, 0x2b, 0x0c, 0x64, 0xb5, 0xf9, 0x12, \n    0xa8, 0xe2, 0x96, 0x41, 0x78, 0x0f, 0x16, 0xf7, 0x5c, 0xa4, 0xf9, 0x84, 0x0d, 0x93, 0xb6, 0xb2, \n    0xaa, 0xa8, 0x23, 0xa1, 0xde, 0x55, 0x19, 0x24, 0x30, 0x39, 0x54, 0xfd, 0x94, 0xe9, 0xa9, 0xbe, \n    0xb1, 0x06, 0x09, 0x39, 0x7e, 0xfb, 0x0b, 0x77, 0x74, 0x9f, 0xd5, 0x1e, 0xf9, 0xa7, 0x1b, 0x24, \n    0xca, 0xc5, 0x61, 0xc5, 0xf8, 0x3c, 0x9d, 0xe3, 0xe9, 0x7f, 0x57, 0x39, 0x98, 0xe1, 0x86, 0x60, \n    0xb3, 0x8b, 0xa3, 0x7e, 0x08, 0xa3, 0x6c, 0xd4, 0x65, 0x2f, 0x9b, 0xe8, 0xcd, 0xbe, 0xfa, 0x88, \n    0xc5, 0xdf, 0x9c, 0x53, 0x63, 0x2b, 0xb6, 0xf4, 0xb9, 0x85, 0xa3, 0x6d, 0x30, 0x1f, 0x7f, 0x3e, \n    0x39, 0x79, 0xcd, 0x95, 0xe5, 0x16, 0xc1, 0x8d, 0x52, 0xfc, 0x46, 0xa8, 0x9d, 0x4b, 0x05, 0xc3, \n    0x6f, 0x5d, 0x7d, 0xd4, 0x35, 0xe9, 0x86, 0x95, 0x23, 0xbe, 0xe7, 0x90, 0x3b, 0xa2, 0xab, 0xdc, \n    0x36, 0xe4, 0xc5, 0xb2, 0xf5, 0x46, 0x84, 0x61, 0xba, 0xe2, 0xcb, 0xa6, 0x6a, 0x8b, 0x4c, 0x8b, \n    0x61, 0x3e, 0xe1, 0x5f, 0x7f, 0x11, 0xf6, 0x01, 0x4a, 0x99, 0x64, 0x5e, 0x2c, 0xc8, 0x68, 0x34, \n    0x22, 0x3d, 0xcb, 0x81, 0xb8, 0x10, 0x1f, 0x0a, 0xe7, 0xc7, 0x3c, 0xea, 0xa8, 0xe1, 0xf7, 0x1b, \n    0xe3, 0x97, 0x29, 0x6a, 0x27, 0x47, 0x07, 0x8e, 0x6e, 0x9d, 0x84, 0x87, 0x5d, 0x3e, 0x6a, 0x6c, \n    0xe6, 0x2d, 0x37, 0x48, 0x42, 0x4a, 0x41, 0x56, 0xcb, 0x73, 0x5b, 0x4e, 0xd3, 0xfc, 0xec, 0x29, \n    0x17, 0x22, 0xa7, 0x41, 0xf3, 0x0a, 0xbc, 0x3b, 0xcc, 0x64, 0x8c, 0x22, 0xc6, 0xdf, 0xac, 0xbc, \n    0xec, 0x80, 0xd7, 0xcf, 0xff, 0x2b, 0x2a, 0x16, 0x50, 0x0b, 0x01, 0x38, 0x24, 0xf9, 0x1a, 0x50, \n    0x9e, 0x66, 0x85, 0xe7, 0x05, 0x6d, 0x32, 0xa9, 0xab, 0xc2, 0xd1, 0x45, 0x3d, 0x82, 0xe9, 0x02, \n    0x8e, 0x4c, 0xec, 0xac, 0x74, 0x9a, 0xad, 0xf7, 0xbd, 0x0f, 0xf2, 0xea, 0x77, 0xcb, 0xbd, 0x96, \n    0x90, 0x47, 0x00, 0x39, 0xf9, 0x2c, 0xc8, 0x57, 0x93, 0x3f, 0x1e, 0xe9, 0x0d, 0x63, 0x46, 0x07, \n    0x40, 0xb6, 0x39, 0x45, 0xef, 0xfb, 0x1f, 0x88, 0x4f, 0xfa, 0xf2, 0x6a, 0xf0, 0x61, 0x03, 0x9e, \n    0x23, 0x1b, 0xcf, 0x91, 0xc2, 0x73, 0x64, 0xe0, 0x39, 0xaa, 0xe0, 0x91, 0xbb, 0xd2, 0x12, 0x91, \n    0xaf, 0x68, 0xbb, 0x3e, 0x0f, 0x95, 0x6b, 0xa5, 0x4a, 0x0c, 0xb9, 0x34, 0x35, 0x52, 0x7e, 0x1d, \n    0x64, 0x85, 0x5c, 0xd8, 0x1b, 0x8b, 0x8c, 0x77, 0xe2, 0x21, 0x9a, 0x3c, 0xe6, 0x8d, 0xf6, 0x8f, \n    0x3b, 0x97, 0x0a, 0x27, 0x70, 0x75, 0xd5, 0xd5, 0xaf, 0xfb, 0xd6, 0x75, 0xef, 0xc3, 0xd5, 0x47, \n    0x07, 0x4e, 0x15, 0xfb, 0x51, 0x78, 0xfc, 0x45, 0x23, 0xcf, 0x0c, 0xba, 0xc6, 0xac, 0x56, 0xf5, \n    0xa6, 0xd8, 0xb0, 0xef, 0x63, 0x59, 0xe0, 0xba, 0xa7, 0xdf, 0x32, 0x59, 0x14, 0xc6, 0x89, 0x15, \n    0xaf, 0xc8, 0x1b, 0x5c, 0xd2, 0x37, 0x6d, 0x5f, 0x49, 0x5f, 0x18, 0xfd, 0xb8, 0xce, 0xe4, 0x65, \n    0xb5, 0x26, 0x01, 0x50, 0xd4, 0x48, 0xb6, 0x41, 0x01, 0xda, 0xf2, 0x31, 0x1f, 0xe0, 0xd9, 0x03, \n    0x9d, 0x5d, 0xb5, 0xcf, 0xae, 0x2c, 0x58, 0x28, 0xc1, 0xb8, 0xc7, 0xec, 0xdb, 0x51, 0x67, 0x38, \n    0x63, 0xfc, 0xc7, 0xa7, 0x90, 0x62, 0x52, 0x56, 0x5a, 0xb0, 0xe6, 0xa5, 0x72, 0x52, 0xe8, 0xc2, \n    0x19, 0x82, 0x8e, 0xd8, 0x70, 0xd2, 0x1d, 0xb8, 0x46, 0xe4, 0x2c, 0x4a, 0xf0, 0xcc, 0x92, 0xe7, \n    0xd9, 0x7e, 0xe0, 0x46, 0xd5, 0x94, 0xb5, 0x12, 0x2e, 0x67, 0xee, 0xea, 0xf8, 0x2c, 0xf0, 0x3b, \n    0x66, 0x6a, 0x6b, 0x5d, 0x3d, 0x42, 0xd0, 0xf0, 0x29, 0x33, 0x22, 0xde, 0x0c, 0xe1, 0xb5, 0xb5, \n    0xb9, 0xee, 0xc6, 0xb8, 0xdb, 0xb7, 0x4d, 0xb8, 0x5b, 0xe0, 0xf9, 0x9b, 0x7e, 0xbf, 0x69, 0x69, \n    0xc2, 0x4d, 0xe2, 0xaf, 0xad, 0x0d, 0x3a, 0xfe, 0x96, 0x3e, 0x68, 0x63, 0x9a, 0x56, 0xb7, 0xf6, \n    0x29, 0xab, 0x03, 0xd9, 0xd2, 0x39, 0x97, 0xbe, 0x66, 0xf9, 0x9b, 0xda, 0xf2, 0x03, 0x0a, 0xc5, \n    0x34, 0x43, 0x64, 0xe7, 0xbe, 0x26, 0x95, 0xb5, 0x0b, 0x7f, 0xc3, 0xc5, 0x37, 0x14, 0x80, 0x5d, \n    0xd4, 0x15, 0xc6, 0xfa, 0xcf, 0x32, 0x81, 0xd2, 0x44, 0x8a, 0x44, 0x33, 0xc3, 0x32, 0x6a, 0xd9, \n    0xad, 0x57, 0xc8, 0x75, 0xd8, 0x8a, 0xef, 0xcb, 0xe4, 0x94, 0x79, 0x5a, 0x55, 0x99, 0x98, 0xb9, \n    0xd4, 0x47, 0x96, 0x95, 0x81, 0x63, 0xa4, 0x09, 0x6e, 0xda, 0xbc, 0x7b, 0xf3, 0xec, 0x38, 0x5d, \n    0x42, 0x5e, 0x83, 0x8d, 0x66, 0x45, 0x12, 0x98, 0xcf, 0xa6, 0x9a, 0xc5, 0x2a, 0x22, 0x2a, 0x63, \n    0xc1, 0x77, 0x9c, 0xc0, 0x13, 0x87, 0xa8, 0x71, 0x53, 0x39, 0x4d, 0xce, 0xb0, 0xb2, 0xc6, 0x1c, \n    0x18, 0x56, 0x32, 0xc0, 0x9e, 0x1d, 0x6e, 0x43, 0xb0, 0x0f, 0xb9, 0xa5, 0x3e, 0x36, 0x93, 0x02, \n    0xb3, 0x8c, 0x12, 0xbf, 0x26, 0xcd, 0x96, 0x09, 0x21, 0x56, 0x9b, 0x25, 0x09, 0xec, 0x9b, 0xde, \n    0x80, 0x06, 0xfc, 0xdd, 0x29, 0xb2, 0x68, 0x69, 0x90, 0xaa, 0x46, 0x63, 0x33, 0xd0, 0x18, 0x2a, \n    0x90, 0xb7, 0x9b, 0xbc, 0x51, 0x38, 0xa5, 0x31, 0x6b, 0x15, 0xe2, 0x6f, 0xd5, 0x04, 0xec, 0x36, \n    0xba, 0x73, 0xd6, 0x02, 0x94, 0x88, 0x37, 0xf7, 0x4b, 0xa1, 0x2c, 0x8d, 0x2f, 0xf8, 0x11, 0x36, \n    0x2c, 0x79, 0x50, 0x0e, 0x8f, 0xf0, 0x16, 0xe1, 0xa4, 0xe6, 0x24, 0x5a, 0xb2, 0x2f, 0x08, 0x29, \n    0x28, 0xdc, 0x0b, 0x66, 0xb8, 0x01, 0x2e, 0xac, 0xe4, 0x33, 0xaa, 0x23, 0xdb, 0xec, 0x1a, 0x0e, \n    0xb3, 0x6b, 0xd4, 0x98, 0x1d, 0x77, 0x66, 0x72, 0x17, 0xc7, 0xdc, 0x2f, 0xa8, 0x9e, 0xcf, 0xc5, \n    0xdc, 0x9a, 0xa1, 0x69, 0x8c, 0xab, 0x73, 0x54, 0x9b, 0xe9, 0x06, 0x07, 0x5f, 0xcf, 0x24, 0xdd, \n    0xa6, 0xa7, 0x8c, 0x8e, 0x17, 0xa0, 0xaf, 0x83, 0x39, 0x4c, 0x88, 0x37, 0x8e, 0xd8, 0x11, 0x94, \n    0xdc, 0x6a, 0x22, 0xc9, 0xf7, 0xd0, 0xcd, 0x0e, 0x92, 0x71, 0x68, 0x71, 0x54, 0x9e, 0x39, 0x64, \n    0x20, 0xf2, 0x65, 0x73, 0x13, 0xc4, 0xd3, 0x61, 0xee, 0x92, 0x7e, 0x8b, 0xdc, 0xb1, 0xcf, 0x37, \n    0x8e, 0x47, 0xce, 0x33, 0x8d, 0x1b, 0xb9, 0x30, 0x35, 0xc8, 0x3c, 0xf9, 0x65, 0x9a, 0x89, 0x55, \n    0x00, 0x58, 0x49, 0x77, 0xb9, 0xeb, 0x84, 0x96, 0xfe, 0x54, 0x1e, 0xb6, 0xd0, 0x1a, 0x4c, 0x82, \n    0x25, 0x16, 0x76, 0xd8, 0xf1, 0x83, 0x73, 0x9a, 0x1d, 0x07, 0x39, 0xf5, 0xec, 0xae, 0x09, 0xdf, \n    0x37, 0xd6, 0x51, 0x94, 0x2f, 0xe8, 0x5e, 0x83, 0x40, 0xfe, 0x06, 0x3b, 0x10, 0xf0, 0x6c, 0xbf, \n    0xcd, 0xcb, 0x4f, 0xa3, 0x95, 0xf8, 0x7a, 0x68, 0xdc, 0xa1, 0x50, 0xfa, 0x82, 0xc7, 0x40, 0x67, \n    0x42, 0x5a, 0xd2, 0x15, 0x98, 0x4c, 0xe7, 0x71, 0x04, 0x66, 0xd9, 0x6f, 0x49, 0xe3, 0x17, 0xdb, \n    0x1c, 0x86, 0x5e, 0x89, 0x3c, 0xd7, 0xbb, 0x65, 0xb1, 0x0f, 0xa5, 0x13, 0x0c, 0x87, 0x84, 0xd1, \n    0xa4, 0x16, 0xb4, 0x7f, 0x1a, 0xaf, 0x43, 0xa8, 0x75, 0xcd, 0xf1, 0xad, 0x16, 0x84, 0xdc, 0xaa, \n    0x23, 0x61, 0xa5, 0xa7, 0x26, 0x11, 0x81, 0xb5, 0x57, 0x8b, 0x55, 0x1f, 0xdd, 0x6a, 0x55, 0x13, \n    0x8c, 0x52, 0x4c, 0x35, 0x27, 0x60, 0x75, 0x89, 0x88, 0x55, 0xb7, 0xe3, 0x83, 0x3a, 0x6e, 0x5b, \n    0xc6, 0x06, 0x3c, 0xf0, 0x12, 0x65, 0xb0, 0x7e, 0x78, 0x88, 0x94, 0x9c, 0x83, 0xc3, 0x56, 0x5e, \n    0x88, 0xe7, 0x28, 0x8e, 0x45, 0xfa, 0x49, 0xc4, 0x14, 0x63, 0x8f, 0xbf, 0x3c, 0x84, 0x2a, 0x35, \n    0x02, 0xef, 0xc8, 0xf5, 0x31, 0x88, 0xe3, 0xab, 0xa3, 0x65, 0x3b, 0x1a, 0x75, 0xb6, 0x79, 0x68, \n    0x59, 0xf3, 0xf5, 0xb6, 0x24, 0xc7, 0x3a, 0x55, 0x8b, 0xb7, 0xda, 0x09, 0xf3, 0x55, 0xf2, 0x36, \n    0xf7, 0x06, 0xcc, 0xbf, 0x79, 0x8a, 0xdc, 0x12, 0xba, 0xd6, 0x59, 0xa8, 0x11, 0x42, 0x89, 0x74, \n    0xf6, 0xb6, 0x70, 0x3f, 0x7c, 0x42, 0x14, 0xa1, 0xeb, 0x78, 0x6e, 0x4c, 0x8f, 0xf0, 0xbb, 0xd6, \n    0x47, 0x96, 0xfb, 0x55, 0x73, 0xaa, 0x21, 0x75, 0x3d, 0x01, 0xdd, 0x1b, 0x84, 0x55, 0x1f, 0xa0, \n    0x59, 0x41, 0x0d, 0x2a, 0xa7, 0x6f, 0x6f, 0x8c, 0xab, 0x1b, 0xb4, 0xab, 0xe2, 0x82, 0x35, 0x08, \n    0x96, 0x18, 0x80, 0xd0, 0xd5, 0x8b, 0xf5, 0xe0, 0xde, 0x9e, 0xff, 0xd4, 0x9b, 0x04, 0x96, 0xfb, \n    0xb9, 0xb2, 0x29, 0x36, 0x36, 0xcc, 0xab, 0x46, 0x2b, 0x3c, 0x4d, 0x7a, 0x2e, 0x8f, 0x5e, 0x6f, \n    0xbf, 0x4b, 0x5e, 0x02, 0x6d, 0xb1, 0x49, 0xae, 0x0d, 0xae, 0xdd, 0x23, 0xaf, 0x7b, 0x29, 0x7d, \n    0xe7, 0x92, 0xdb, 0xb9, 0x63, 0xdb, 0x79, 0xd3, 0xa6, 0x3a, 0x77, 0x39, 0x5b, 0x00, 0x89, 0xdd, \n    0x6d, 0x1c, 0x3f, 0xd8, 0x6a, 0x7c, 0xf9, 0x06, 0xfa, 0xce, 0xa5, 0x7d, 0x78, 0x0d, 0xb1, 0xdc, \n    0xfb, 0xd0, 0xaa, 0xa0, 0xd1, 0xe3, 0x74, 0xa9, 0x23, 0xfa, 0x8e, 0x77, 0x29, 0x22, 0x87, 0xaf, \n    0xd2, 0xac, 0xee, 0x45, 0x9a, 0x17, 0x59, 0x80, 0xdf, 0xdc, 0x86, 0x53, 0xff, 0x09, 0x36, 0x10, \n    0x91, 0x7c, 0x1d, 0xc7, 0x01, 0xba, 0x07, 0x30, 0x2b, 0xbc, 0x43, 0x09, 0xd4, 0xb8, 0xf8, 0x21, \n    0xd0, 0xea, 0x09, 0x51, 0x02, 0xe1, 0x57, 0x6d, 0x73, 0x1f, 0x67, 0x05, 0xe0, 0x4a, 0x1c, 0x05, \n    0x8f, 0x60, 0x85, 0x24, 0x20, 0x56, 0x81, 0xbe, 0x08, 0x8a, 0x45, 0x67, 0x19, 0x25, 0xde, 0xf5, \n    0x5e, 0xa4, 0xed, 0xf4, 0xb0, 0x66, 0xbc, 0xc3, 0x7c, 0x0a, 0x6c, 0xd3, 0xfb, 0x88, 0x87, 0x5f, \n    0x35, 0xc5, 0x87, 0xd2, 0x53, 0x23, 0xf9, 0xca, 0xc7, 0xcc, 0x5a, 0x60, 0xb8, 0xc2, 0xbc, 0x76, \n    0xe7, 0xd2, 0x85, 0xbb, 0x2c, 0x50, 0x2b, 0x67, 0x6b, 0xd9, 0xb9, 0x39, 0xfc, 0xb2, 0x53, 0x7e, \n    0x44, 0x07, 0xab, 0x47, 0xfb, 0x4c, 0x0e, 0x3b, 0x5b, 0xe7, 0x48, 0x06, 0xc4, 0xd6, 0x35, 0x6e, \n    0xaa, 0x62, 0x7c, 0x78, 0x5f, 0x89, 0xea, 0x6d, 0x3b, 0x4a, 0x97, 0xe7, 0x86, 0x39, 0x94, 0x7e, \n    0x0c, 0x7a, 0x6d, 0x67, 0xf0, 0xec, 0xde, 0x86, 0x5d, 0x5c, 0x47, 0x2a, 0xe7, 0x48, 0x7c, 0xdf, \n    0xd0, 0xc0, 0x48, 0x7d, 0x81, 0x1f, 0x0a, 0x33, 0x5a, 0xc1, 0xc7, 0x88, 0x83, 0xd7, 0x64, 0x78, \n    0xf3, 0xf4, 0x24, 0x7d, 0x0d, 0xf9, 0x1b, 0x2e, 0x65, 0x4d, 0x6e, 0xc4, 0xf3, 0x2e, 0xcb, 0x17, \n    0x6a, 0xcf, 0x7c, 0x5f, 0xef, 0xbf, 0xc2, 0x0a, 0x1f, 0x97, 0xcf, 0xf4, 0x5e, 0xef, 0x75, 0x64, \n    0xbc, 0x84, 0x9c, 0xd0, 0x45, 0xc6, 0x16, 0x59, 0xe1, 0xa1, 0x5b, 0x09, 0x6b, 0x08, 0xbe, 0x7b, \n    0xf7, 0xcb, 0x09, 0xae, 0x80, 0xb9, 0x4e, 0x78, 0x7d, 0x0b, 0x69, 0xe2, 0xff, 0xf3, 0xf4, 0xef, \n    0xdb, 0x4b, 0xae, 0xbe, 0x30, 0x8b, 0xd2, 0xb6, 0xe4, 0x08, 0xff, 0x42, 0x1c, 0x82, 0x47, 0x06, \n    0xc2, 0xf4, 0x3c, 0x61, 0x9d, 0x1d, 0xc9, 0x9e, 0xd6, 0xc7, 0x50, 0x4a, 0xcb, 0xbf, 0x4c, 0xe7, \n    0xf8, 0xed, 0x2f, 0xba, 0xba, 0xb2, 0x17, 0xcc, 0xf2, 0x33, 0x7e, 0x48, 0x1a, 0x83, 0x7c, 0x26, \n    0xbe, 0x7c, 0x5b, 0x2e, 0x5d, 0x1b, 0xcf, 0xbe, 0xb6, 0xcb, 0xe3, 0xd5, 0xbf, 0x26, 0xcd, 0x0d, \n    0xca, 0x65, 0x79, 0x2d, 0xd5, 0x41, 0xd1, 0xa5, 0x2e, 0x1d, 0x25, 0x71, 0x24, 0x2d, 0x8a, 0x92, \n    0xbb, 0x23, 0x76, 0xd0, 0xea, 0x8f, 0x14, 0xc2, 0x10, 0x76, 0xb0, 0x5a, 0x20, 0x68, 0xec, 0x5a, \n    0x6c, 0x5a, 0x6d, 0x90, 0xc9, 0x63, 0x29, 0x88, 0x20, 0x67, 0x7d, 0x12, 0x2c, 0xa7, 0x4d, 0x65, \n    0x98, 0xc4, 0xe9, 0x44, 0xf4, 0xac, 0x8f, 0xe0, 0xa3, 0xf7, 0x5e, 0xcd, 0xf9, 0xa1, 0x4d, 0x2e, \n    0xd9, 0xbb, 0xd6, 0x43, 0x48, 0x75, 0xc0, 0x1b, 0x75, 0x71, 0x3b, 0x44, 0x9f, 0x47, 0xdf, 0xad, \n    0x83, 0x40, 0x06, 0x42, 0xef, 0xbc, 0x7b, 0xf3, 0x5c, 0x64, 0x53, 0xaf, 0x26, 0x7f, 0xd0, 0x69, \n    0x01, 0xd7, 0x1e, 0xce, 0x50, 0x79, 0xd3, 0x66, 0x43, 0xfa, 0x15, 0xe8, 0x2f, 0xd5, 0x74, 0xf0, \n    0x1d, 0x47, 0x18, 0x0c, 0xb3, 0x68, 0x37, 0xd5, 0xfa, 0xc2, 0x02, 0xb1, 0xbc, 0x0f, 0x49, 0x53, \n    0xcf, 0x15, 0xe2, 0x89, 0x9d, 0x79, 0x04, 0x3a, 0xe6, 0x29, 0x7f, 0xc9, 0xad, 0x06, 0x8c, 0x1f, \n    0x64, 0xab, 0x80, 0x69, 0x7c, 0x42, 0x9c, 0x48, 0x4f, 0x35, 0x3e, 0x71, 0x73, 0xb1, 0x26, 0x14, \n    0x3f, 0xe3, 0xff, 0x5b, 0xa5, 0xe8, 0x4f, 0xa6, 0xc4, 0xfc, 0xb6, 0x9a, 0xae, 0x1a, 0x06, 0x1f, \n    0xbf, 0x7a, 0x21, 0xbe, 0xde, 0xe6, 0x39, 0xf6, 0x2e, 0x42, 0xab, 0xd7, 0x4b, 0xac, 0x96, 0xf1, \n    0x0d, 0xb7, 0x42, 0xcd, 0x98, 0xaf, 0x1e, 0x9b, 0x7b, 0xb2, 0xea, 0xb6, 0x63, 0x7f, 0x5d, 0xd3, \n    0xaf, 0x27, 0xae, 0xec, 0x42, 0x6f, 0xfe, 0xb8, 0x3b, 0xd8, 0xe2, 0xcc, 0xa2, 0x7c, 0xd3, 0xd0, \n    0x78, 0x29, 0x4a, 0x1e, 0x93, 0xbc, 0x39, 0xa4, 0xf8, 0x3e, 0x94, 0x8d, 0x80, 0xda, 0xb1, 0x79, \n    0x03, 0xb6, 0xfc, 0x66, 0xad, 0x8d, 0xe0, 0xca, 0x67, 0x18, 0xc0, 0x5a, 0xcb, 0xa7, 0x1e, 0x54, \n    0xcf, 0x2d, 0x0c, 0x68, 0xad, 0xfb, 0xb3, 0x19, 0x5a, 0xa6, 0x04, 0xd5, 0x95, 0x30, 0x4f, 0xa7, \n    0x6c, 0x75, 0x48, 0x14, 0xb3, 0x6f, 0xdd, 0xd5, 0xb0, 0x3b, 0x1d, 0xe4, 0x04, 0x7e, 0x3f, 0xa6, \n    0xb3, 0x60, 0x1d, 0xe3, 0xb1, 0x0e, 0xf3, 0xa0, 0xd8, 0x16, 0x2f, 0x97, 0xdd, 0xe0, 0x20, 0x55, \n    0xe5, 0x28, 0x15, 0xa0, 0x37, 0x8e, 0x52, 0x39, 0xbd, 0x5a, 0x05, 0xfd, 0xdf, 0xc4, 0xae, 0xb6, \n    0x6f, 0xc0, 0xbf, 0x33, 0x81, 0x88, 0xe3, 0xa6, 0x8d, 0xc7, 0x29, 0xbe, 0x04, 0x81, 0xaa, 0x84, \n    0xaf, 0xad, 0x9f, 0x07, 0x09, 0x6b, 0x85, 0x84, 0x59, 0xba, 0x32, 0xde, 0x5a, 0x10, 0x31, 0xf0, \n    0x87, 0x46, 0x5b, 0x3b, 0x71, 0x66, 0xb1, 0xa6, 0x99, 0xd2, 0xf1, 0x82, 0x4e, 0x4f, 0x31, 0xc5, \n    0x63, 0xb0, 0x0b, 0x70, 0xd8, 0x41, 0x08, 0x45, 0x07, 0xe1, 0xdf, 0xd7, 0xef, 0xb1, 0xb3, 0xb1, \n    0x72, 0xb8, 0x7c, 0x79, 0x1a, 0x03, 0xd2, 0x2f, 0xf2, 0x0d, 0x01, 0xe5, 0x4e, 0xd3, 0xf4, 0x34, \n    0x2a, 0x4f, 0x99, 0x76, 0xbd, 0x1f, 0x86, 0xf0, 0xdf, 0x6f, 0x7f, 0x75, 0xee, 0x1c, 0xfc, 0x9a, \n    0xdf, 0x69, 0x49, 0x38, 0xf8, 0x3c, 0x82, 0x7f, 0xde, 0xfb, 0xdf, 0x0e, 0x3e, 0xdc, 0x69, 0x75, \n    0xee, 0xec, 0xb4, 0xfe, 0xfa, 0x0d, 0x7e, 0x76, 0x81, 0xc1, 0x9d, 0xbe, 0x76, 0xc8, 0x46, 0x7f, \n    0x49, 0xdb, 0x98, 0x53, 0x6b, 0x6f, 0xeb, 0xaf, 0x0d, 0x6e, 0x3e, 0xd1, 0xab, 0x17, 0x40, 0xb5, \n    0xd8, 0x70, 0xbb, 0xb9, 0x11, 0x09, 0x01, 0x18, 0xe7, 0x7d, 0x60, 0xf1, 0xf5, 0x6f, 0xe0, 0xda, \n    0x7c, 0x82, 0x72, 0x07, 0x0f, 0xfc, 0x82, 0xaf, 0x83, 0x51, 0x22, 0x8a, 0x34, 0xbb, 0xfc, 0x46, \n    0x25, 0xab, 0xe5, 0x73, 0x1c, 0x76, 0xd5, 0x2b, 0xea, 0x87, 0x5d, 0xf1, 0x95, 0xcf, 0x87, 0x5d, \n    0xf6, 0xff, 0xe0, 0xfb, 0x5f, 0x76, 0x39, 0xf3, 0x7b, 0x93, 0x6f, 0x00, 0x00\n};\n\n\n"
  },
  {
    "path": "examples/localRFID/data/login",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Login</title>\n    <style>\n    #about {color:lightgray};\n    body {background-color: #f5f5f5; font-family: Arial, sans-serif;}\n    header, footer {padding: 10px; background-color: #024c5b; color: #fff; width: 50%; text-align: center;}\n    header {margin-top: 40px;}\n    label {display: block; margin: 5px;}\n    input[type=\"text\"], input[type=\"password\"], input[type=\"email\"] {width: 80%; padding: 8px; border: 1px solid #ddd; border-radius: 3px;}\n    button[type=\"submit\"] {margin-top: 20px; width: 50%; min-width: 60px; padding: 10px; background-color: #607D8B; color: white; border: none; cursor: pointer;}\n    button[type=\"submit\"]:hover, .nav {background-color: #16729F;}\n    .container {display: flex; flex-direction: column; align-items: center; min-height: 100vh;}\n    .custom-container {padding: 10px; width: 50%; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); background-color: #ffffff;}  \n    .content {padding: 20px; display: flex; flex-direction: column;}\n    .center {text-align: center};\n  </style>\n</head>\n<body>\n  <div class=\"container\">\n    <header>\n      <h1>ESP32 RFID Logs - Login</h1> \n    </header>\n    <div class=\"custom-container\">\n      <div class=\"content\">\n        <form id=\"loginForm\" class=\"center\">\n          <div>\n            <label for=\"username\">Username:</label>\n            <input type=\"text\" id=\"username\" name=\"username\" required>\n          </div>\n          <div>\n            <label for=\"password\">Password:</label>\n            <input type=\"password\" id=\"password\" name=\"password\" required>\n          </div>\n          <button type=\"submit\">Login</button>\n        </form>\n      </div>\n    </div>\n    <footer class=\"footer\">\n      <div class=\"has-text-centered\">\n        <p> RFID Log &copy; 2025. All rights reserved.</p>\n        <a id=about target=_blank rel=noopener href=\"https://github.com/cotestatnt/async-esp-fs-webserver\">Created with https://github.com/cotestatnt/async-esp-fs-webserver</a>\n      </div>\n    </footer>\n  </div>\n\n  <script>\n    // To avoid sharing plain text between client and server, \n    // send SHA256 of password input text (based on https://geraintluff.github.io/sha256/)\n    function sha256(ascii) {\n      var rightRotate = (value, amount) => (value >>> amount) | (value << (32 - amount));\n      var mathPow = Math.pow, maxWord = mathPow(2, 32), lengthProperty = 'length', result = '', words = [], asciiBitLength = ascii[lengthProperty] * 8;\n      var hash = sha256.h = sha256.h || [], k = sha256.k = sha256.k || [], primeCounter = k[lengthProperty];\n      var isComposite = {};\n      for (var candidate = 2; primeCounter < 64; candidate++) {\n        if (!isComposite[candidate]) {\n          for (i = 0; i < 313; i += candidate) isComposite[i] = candidate;\n          hash[primeCounter] = (mathPow(candidate, .5) * maxWord) | 0;\n          k[primeCounter++] = (mathPow(candidate, 1 / 3) * maxWord) | 0;\n        }\n      }\n      ascii += '\\x80';\n      while (ascii[lengthProperty] % 64 - 56) ascii += '\\x00';\n      for (i = 0; i < ascii[lengthProperty]; i++) {\n        j = ascii.charCodeAt(i);\n        if (j >> 8) return;\n        words[i >> 2] |= j << ((3 - i) % 4) * 8;\n      }\n      words[words[lengthProperty]] = ((asciiBitLength / maxWord) | 0);\n      words[words[lengthProperty]] = (asciiBitLength);\n      for (j = 0; j < words[lengthProperty];) {\n        var w = words.slice(j, j += 16);\n        var oldHash = hash.slice(0, 8);\n        for (i = 0; i < 64; i++) {\n          var i2 = i + j, w15 = w[i - 15], w2 = w[i - 2];\n          var a = hash[0], e = hash[4];\n          var temp1 = hash[7] + (rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25)) + ((e & hash[5]) ^ ((~e) & hash[6])) + k[i] +\n              (w[i] = (i < 16) ? w[i] : (w[i - 16] + (rightRotate(w15, 7) ^ rightRotate(w15, 18) ^ (w15 >>> 3)) + w[i - 7] +\n                  (rightRotate(w2, 17) ^ rightRotate(w2, 19) ^ (w2 >>> 10))) | 0);\n\n          var temp2 = (rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22)) + ((a & hash[1]) ^ (a & hash[2]) ^ (hash[1] & hash[2]));\n          hash = [(temp1 + temp2) | 0].concat(hash);\n          hash[4] = (hash[4] + temp1) | 0;\n        }\n        for (i = 0; i < 8; i++) hash[i] = (hash[i] + oldHash[i]) | 0;\n      }\n      for (i = 0; i < 8; i++)\n        for (j = 3; j + 1; j--)\n          result += ((hash[i] >> (j * 8)) & 255).toString(16).padStart(2, '0');\n      return result;\n    };\n\n   document.getElementById('loginForm').addEventListener('submit', async function(event) {\n    event.preventDefault();\n    const username = document.getElementById('username').value;\n    const password = document.getElementById('password').value;\n    const hash = sha256(password);\n  \n    // 1. Effettua il login con POST\n    const postResponse = await fetch('/rfid', {\n      method: 'POST',\n      body: new URLSearchParams({ username, hash }),\n    });\n  \n    if (!postResponse.ok) {\n      alert(\"Login fallito\");\n      return;\n    }\n  \n    // 2. Se il POST ha successo, invia una richiesta PUT\n    const putResponse = await fetch('/rfid', {\n      method: 'PUT',\n      body: new URLSearchParams({ username, hash }),\n    });\n  \n    if (putResponse.ok) {\n      // 3. Estrai l'HTML dalla risposta e sostituisci l'intero documento\n      const html = await putResponse.text();\n      \n      // Apri un nuovo documento e scrivi l'HTML ricevuto\n      document.open();\n      document.write(html);\n      document.close(); // Forza l'esecuzione degli script\n      // history.pushState({}, \"\", \"/rfid\"); \n    } else {\n      alert(\"Errore durante l'operazione PUT\");\n    }\n  });\n  </script>\n</body>\n</html>"
  },
  {
    "path": "examples/localRFID/data/rfid",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>RFID Log</title>\n  <style>\n    /* Base Styles */\n    body {background:#f5f5f5; margin:0; font-family:Arial,sans-serif; margin-top:20px;}\n    header, footer {padding:10px; background:#024c5b; color:#fff; width:90%; text-align:center;}\n    label {display:block; margin-bottom:5px;}\n    input[type=\"text\"], input[type=\"password\"], input[type=\"email\"] {width:100%; padding:8px; border:1px solid #ddd; border-radius:3px;}\n    button.submit {margin:0 20px -10px 20px; width:80%; min-width:60px; padding:10px; background:#607D8B; color:white; border:none; cursor:pointer;}\n    button.submit:hover {background:#16729F;}\n    button[disabled]:hover, button[disabled] {background:#ccc; color:#666;}\n    select.submit {width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 3px; background-color: white; cursor: pointer;}\n    select.submit:hover {border-color: #16729F;}\n    \n    /* Layout */\n    .container {display:flex; flex-direction:column; align-items:center; min-height:100vh;}\n    .custom-container {padding:10px; width:90%; box-shadow:0 0 10px rgba(0,0,0,0.1); background:#fff;}\n    .sidebar {background:#f8f9fa; flex:0 0 10%; transition:all 0.3s ease;}\n    .tab {padding:10px 20px; cursor:pointer;}\n    .tab.active {background:#e9ecef;}\n    .content {flex-grow:1; display:flex; justify-content:center; align-items:flex-start;}\n    .main-content {width:100%; padding:0 0 20px 20px; min-height:70vh;}\n    .section {position:relative;}\n    .collapse-container {margin-bottom:20px;}\n    .collapse-content {display:none; padding:10px; border:1px solid #ddd; border-radius:5px; margin-bottom:20px;}\n    .collapse-content.show {display:block;}\n    .form-row {display:flex; flex-wrap:wrap; margin-bottom:10px; align-items:flex-end;}\n    .form-column {flex:1; margin:0 20px 0 20px;}\n    .form-column:last-child {margin-right:40px;}\n    .but-row {display:flex; margin:20px 0 10px 0;}\n    \n    /* Interactive Elements */\n    .button:hover {cursor:pointer; background:#16729F;}\n    .button:active {transform:scale(0.8);}\n    #about {color:lightgray;}\n    details, main, summary {display:block;}\n    \n    /* Floating Elements */\n    .floating {position:absolute; float:right; z-index:1; right:-4px;}\n    .floating.top {top:-8px;}\n    .floating.bottom {bottom:-8px;}\n    .floating.inline {position:sticky;}\n    .floating .button {\n      width:24px; height:24px; margin-top:2px; border-radius:50%; \n      box-shadow:0 2px 4px rgba(0,0,0,0.8); border:1px solid transparent; \n      font-family:monospace; font-size:larger; color:cornsilk;\n    }\n    \n    /* Color Classes */\n    .green {background:rgba(0,128,0,0.73);}\n    .blue {background:#607D8B;}\n    \n    /* Tables */\n    .responsive-table {width:100%; overflow-x:auto; -webkit-overflow-scrolling:touch; margin-bottom:1rem;}\n    .table-container {min-width:600px; border:1px solid #ddd; border-radius:5px; overflow:hidden;}\n    .table-header, .table-row {display:flex; flex-wrap:nowrap;}\n    .table-header {background:#f2f2f2; font-weight:bold; margin-top:10px;}\n    .table-header>div, .table-row>div {padding:10px 5px; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}\n    .table-row {border-bottom:1px solid #ddd;}\n    .table-row:nth-child(even) {background:#f9f9f9;}\n    .table-row.selected {background:#d3d3d3; border:1px solid orange; color:blue;}\n    \n    /* Table Columns */\n    .col-tag {flex:1 1 25%; text-align:center;}\n    .col-reader {flex:1 1 10%; text-align:center;}\n    .col-level {flex:0 0 5%; text-align:center;}\n    .col-timestamp {flex:1 1 30%;}\n    .col-name {flex:1 1 25%;}\n    .col-email {flex:1 1 25%;}\n    .col-username {flex:1 1 20%;}\n    \n    \n    /* Modal */\n    .d-modal {\n      width:-webkit-fill-available; border-radius:10px; border:1px solid rgba(51,51,51,0.43);\n      box-shadow:0 3px 8px rgba(0,0,0,0.24); left:50%; position:absolute; top:60%; \n      transform:translate(-50%,-50%); flex-direction:column; background:hsla(200,18%,46%,0.9); z-index:999;\n    }\n    .d-modal-title {color:#111827; padding:1.5em; position:relative; width:calc(100% - 4.5em);}\n    .d-modal-content {border-top:1px solid #e0e0e0; padding:1.5em; overflow:auto;}\n    .btn {display:inline-flex; padding:10px 15px; background:#2E8BC0; color:#fff; border:0; cursor:pointer; min-width:40%; border-radius:5px; justify-content:center;}\n    .btn:hover {filter:brightness(85%);}\n    .btn-bar {display:flex; padding:20px; justify-content:center; flex-wrap:wrap; gap:30px 20px; order:998;}\n    \n    /* Utility Classes */\n    .hide {display:none;}\n    .loader {\n      width:120px; height:120px; border:24px solid rgba(222,219,219,0.66); \n      border-top:24px solid rgba(52,152,219,0.89); border-radius:50%;\n      animation:spin 2s linear infinite; position:fixed; top:50%; left:50%; \n      margin:-60px; display:none;\n    }\n    @keyframes spin {0%{transform:rotate(0deg);} 100%{transform:rotate(360deg);}}\n    \n    /* Mobile */\n    @media (max-width:768px) {\n      .custom-container {width:95%; padding:5px;}\n      .table-header>div, .table-row>div {padding:8px 10px; font-size:0.9em;}\n      .form-column {flex:1 1 100%; margin:0 0 10px 0;}\n      .form-column:last-child {margin-right:0;}\n      .but-row {flex-wrap:wrap;}\n      button.submit {margin:5px 0; width:100%;}\n    }\n  </style>\n</head>\n<body>\n  <div class=\"loader\" id=\"loader\"></div>\n  \n  <details id=modal-message>\n    <summary style=\"display: block\"></summary>\n    <div class=d-modal>\n      <div class=d-modal-title><h2 id=message-title>t</h2></div>\n      <div class=d-modal-content><p id=message-body></p></div>\n      <div class=btn-bar>\n          <a id=ok-modal class=\"btn hide\" onclick=closeModal(true)><span>OK</span></a>\n          <a id=close-modal class=\"btn\" onclick=closeModal(false)><span>Close</span></a>\n      </div>\n    </div>\n  </details>\n          \n  <div id=\"main-box\" class=\"container\">\n    <header>\n      <h1 class=\"title is-3\">ESP32 RFID Logs</h1> \n    </header>\n    <div class=\"custom-container\">\n      <div class=\"content\">\n        <div class=\"sidebar\">\n          <div class=\"tab\" id=\"logsTab\" data-target=\"logsContent\">Logs</div>\n          <div class=\"tab\" id=\"usersTab\" data-target=\"usersContent\">Users</div>\n          <div class=\"tab\"><a id=\"setup\" style=\"color: inherit; text-decoration: none;\" href=\"#\" disabled>Setup</a></div>\n        </div>\n        <div class=\"main-content\">\n          <div id=\"logsContent\" class=\"section\">\n            \n            <div class=\"floating top\">\n              <button class=\"button green\" onclick=\"toggleCollapse('collapse-logs')\"><b id='toggle-logs'>+</b></button>\n            </div>\n            \n            <div class=\"floating bottom\">\n              <button class=\"button blue\" id=\"prev-log\"><b>&lt;</b></button>\n              <button class=\"button blue\" id=\"next-log\"><b>&gt;</b></button>\n            </div>\n            \n            <div class=\"collapsible\">\n              <div id=\"collapse-logs\" class=\"collapse-content\">\n               <div id=\"insertForm\">\n                  <div class=\"form-row\">\n                    <div class=\"form-column\">\n                      <label for=\"log-file-select\">Select Log File:</label>\n                      <select id=\"log-file-select\" class=\"submit\">\n                        <!-- Options will be added dynamically -->\n                      </select>\n                    </div>\n                    <div class=\"form-column\">\n                      <label for=\"username-log\">Username:</label>\n                      <input type=\"text\" id=\"username-log\" name=\"username-log\" placeholder=\"Username\">\n                    </div>\n                    <div class=\"form-column\">\n                      <label for=\"reader-log\">Reader:</label>\n                      <input type=\"text\" id=\"reader-log\" name=\"reader-log\" placeholder=\"Reader number\">\n                    </div>\n                    \n                    <div class=\"but-row\">\n                      <button class=\"submit\" id=\"export-log\">Export</button>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </div>\n    \n            <div class=\"responsive-table\">\n              <div class=\"table-container\">\n                <div class=\"table-header\">\n                  <div class=\"col-reader\">Reader</div>\n                  <div class=\"col-username\">Username</div>\n                  <div class=\"col-tag\">Tag Code</div>\n                  <div class=\"col-timestamp\">Timestamp</div>\n                </div>\n                <div id=\"logsTable\" class=\"table-body\">\n                  <!-- Rows will be added dynamically -->\n                </div>\n              </div>\n            </div>\n          </div>\n        \n          <div id=\"usersContent\" class=\"section hide\">\n            <div class=\"floating top\">\n              <button class=\"button green\" id=\"handle-users\" onclick=\"toggleCollapse('collapse-user')\" disabled><b id='toggle-user'>+</b></button>\n            </div>\n            <div class=\"collapsible\">\n              <div id=\"collapse-user\" class=\"collapse-content\">\n                <div id=\"insertForm\">\n                  <div class=\"form-row\">\n                    <div class=\"form-column\">\n                      <label for=\"tag\">Tag Code:</label>\n                      <span style=\"display: inline-flex;\">\n                        <input type=\"text\" id=\"tag\" name=\"tag\" placeholder=\"Tag Code\">\n                        <div class=\"floating inline\">\n                          <button id=\"get-tag\" class=\"button blue\" title=\"Read RFID tag code\"><b>@</b></button>\n                        </div>\n                      </span>\n                    </div>\n                    <div class=\"form-column\">\n                      <label for=\"name\">Name:</label>\n                      <input type=\"text\" id=\"name\" name=\"name\" placeholder=\"Name\">\n                    </div>\n                    <div class=\"form-column\">\n                      <label for=\"level\">Level:</label>\n                      <input type=\"text\" id=\"level\" name=\"level\" placeholder=\"Level\">\n                    </div>\n                  </div>\n                  <div class=\"form-row\">\n                    <div class=\"form-column\">\n                      <label for=\"username\">Username:</label>\n                      <input type=\"text\" id=\"username\" name=\"username\" placeholder=\"Username\">\n                    </div>\n                    <div class=\"form-column\">\n                      <label for=\"password\">Password:</label>\n                      <input type=\"password\" id=\"password\" name=\"password\" placeholder=\"password\">\n                    </div>\n                    <div class=\"form-column\">\n                      <label for=\"email\">Email:</label>\n                      <input type=\"email\" id=\"email\" name=\"email\" placeholder=\"Email\">\n                    </div>\n                  </div>\n                  \n                  <div class=\"but-row\">\n                    <button class=\"submit\" id=\"add-user\">Insert</button>\n                    <button class=\"submit\" id=\"delete-user\" disabled>Delete</button>\n                  </div>\n                </div>\n              </div>\n            </div>\n            \n            <div class=\"responsive-table\">\n              <div class=\"table-container\">\n                <div class=\"table-header\">\n                  <div class=\"col-username\">Username</div>\n                  <div class=\"col-name\">Name</div>\n                  <div class=\"col-email\">Email</div>\n                  <div class=\"col-tag\">Tag Code</div>\n                  <div class=\"col-level\">Role</div>\n                </div>\n                <div id=\"usersTable\" class=\"table-body\">\n                  <!-- Rows will be added dynamically -->\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <footer class=\"footer\">\n      <div class=\"has-text-centered\">\n        <p> RFID Log &copy; 2025. All rights reserved.</p>\n        <a id=about target=_blank rel=noopener href=\"https://github.com/cotestatnt/async-esp-fs-webserver\">Created with https://github.com/cotestatnt/async-esp-fs-webserver</a>\n      </div>\n    </footer>\n  </div>\n\n  <script>\n    var userLevel = 0;\n    let currentCsvData = [];\n    let currentPage = 0;\n    const recordsPerPage = 15;\n    let totalFilteredRecords = 0;\n    \n    \n    // Callback function called on modal box close\n    var closeCb = function(){};\n  \n    // ID Element selector shorthands\n    var $ = function(el) {\n        return document.getElementById(el);\n    };\n    \n    // Switch active page\n    function tabClick() {\n      const tabs = document.querySelectorAll('.tab');\n      const sections = document.querySelectorAll('.section');\n      tabs.forEach(t => t.classList.remove('active'));\n      sections.forEach(s => s.classList.add('hide'));\n      const target = this.dataset.target;\n      $(target).classList.remove('hide');\n      this.classList.remove('hide');\n      this.classList.add('active');\n    }\n    \n    // Toggle the collapsible user section \n    function toggleCollapse(id, keep) {\n      if (keep)\n        $(id).classList.add('show');\n      else\n        $(id).classList.toggle('show');\n        \n      const allRows = document.querySelectorAll('.table-row');\n      allRows.forEach(row => row.classList.remove('selected'));\n      const allInput = $('insertForm').querySelectorAll('input');\n      allInput.forEach(inp => inp.value = '');\n      $('add-user').innerHTML = 'Insert';\n      $('delete-user').disabled = true;\n      $('add-user').disabled = true;\n      const btnId = id.split('-')[1];\n      $('toggle-' + btnId).innerHTML = $(id).classList.contains('show') ? '-' : '+';\n    }\n    \n    // Show a message, if fn != undefinded run as callback on OK button press\n    function openModal(title, msg, fn) {\n      $('message-title').innerHTML = title;\n      $('message-body').innerHTML = msg;\n      $('modal-message').open = true;\n      $('main-box').style.filter = \"blur(3px)\";\n      if (typeof fn != 'undefined') {\n        closeCb = fn;\n        $('ok-modal').classList.remove('hide');\n      }\n      else\n        $('ok-modal').classList.add('hide');\n    }\n    \n    // Close modal box\n    function closeModal(do_cb) {\n      $('modal-message').open = false;\n      $('main-box').style.filter = \"\";\n      if (typeof closeCb != 'undefined' && do_cb)\n        closeCb();\n    }\n    \n    // Helper to format timestamp\n    function formatTimestamp(timestamp) {\n      const date = new Date(timestamp);\n      return isNaN(date.getTime()) ? timestamp : date.toLocaleString();\n    }\n    \n    \n    //////////////////////////////////////////////////////////////////////////////////////////////////\n    //////////////////////////////   Users setup handling ////////////////////////////////////////////\n    //////////////////////////////////////////////////////////////////////////////////////////////////\n    \n    \n    // Send command to MCU before the readings in order to handle properly logs record\n    function readTagCode() {\n      fetch('/waitCode')\n      .then(response => {\n        openModal('Read new TAG', \"<br>Please hold your tag close to the RFID reader\", getTagCode);\n      })\n    }\n    \n    // Read the RFID code for current user\n    function getTagCode() {\n      fetch('/getCode')\n      .then(response => {\n        if (!response.ok) {\n          throw new Error('Request error');\n        }\n        return response.json();\n      })\n      .then(data => {\n        $('tag').value = data.tag;\n        $('add-user').disabled = false;\n      })\n      .catch(error => {\n        alert('Error:' + error);\n      });\n    }\n    \n    // Get list of registered users\n    function getUsers() {\n      const usersTable = $('usersTable');\n      fetch('/users')\n      .then(response => {\n        if (!response.ok) {\n          throw new Error('Request error');\n        }\n        return response.json();\n      })\n      .then(data => {\n        usersTable.innerHTML = '';\n        data.forEach((user, index) => {\n          const userEntry = document.createElement('div');\n          userEntry.className = 'table-row';\n          userEntry.innerHTML = `\n            <div class=\"col-username\">${user.username}</div>\n            <div class=\"col-name\">${user.name}</div>\n            <div class=\"col-email\">${user.email}</div>\n            <div class=\"col-tag\">${user.tag}</div>\n            <div class=\"col-level\">${user.level}</div>`;\n          usersTable.appendChild(userEntry);\n          \n          userEntry.addEventListener('click', function(ev) {\n            const allRows = document.querySelectorAll('.table-row');\n            allRows.forEach(row => row.classList.remove('selected'));\n            \n            if(userLevel >= 5) {\n              toggleCollapse('collapse-user', true);\n              this.classList.add('selected');\n              const cols = Array.from(this.querySelectorAll('div')).map(el => {\n                const id = el.className.replace('col-', '');\n                return {\n                  id: id,\n                  value: el.innerHTML\n                };\n              });\n              \n              cols.forEach(item => {\n                if ($(item.id)) {\n                  $(item.id).value = item.value; \n                  $(item.id).addEventListener('input', function() {\n                    $('add-user').disabled = false;\n                  });\n                }\n              });\n              \n              $('delete-user').disabled = false;\n              $('add-user').innerHTML = 'Update';\n            }\n          });\n        });\n        \n        $('loader').style.display = \"none\";\n      })\n      .catch(error => {\n        console.error('Error:', error);\n      });\n    }\n        \n    // Send command to MCU before the readings in order to handle properly logs record\n    function deleteUser() {\n      $('delete-user').disabled = true;\n      sendUserForm('/delUser');\n    }\n    \n    // Insert or update a new user record\n    function sendUserForm(url) {\n      var formData = new FormData();\n      formData.append(\"username\", $('username').value);\n      formData.append(\"password\", $('password').value);\n      formData.append(\"name\", $('name').value);\n      formData.append(\"email\", $('email').value);    \n      formData.append(\"tag\", $('tag').value);\n      formData.append(\"level\", $('level').value);\n      formData.append(\"type\", $('add-user').innerHTML);\n      const option = {\n        method: 'POST',\n        body: formData\n      };\n      fetch(url, option)\n        .then(response => {\n          if (!response.ok) {\n            throw new Error('Request error');\n          }\n          return response.text();\n        })\n        .then(result => {\n          openModal('Users', \"<br>New record inserted or updated\");\n          getUsers();\n        })\n        .catch(error => {\n          openModal('Error', error);\n        });\n    }\n\n    //////////////////////////////////////////////////////////////////////////////////////////////////\n    //////////////////////////////    CSV logs handling //////////////////////////////////////////////\n    //////////////////////////////////////////////////////////////////////////////////////////////////\n    function listLogFiles() {\n      const url = '/list?dir=/logs';\n      $('loader').style.display = \"block\";\n      \n      fetch(url)\n      .then(response => {\n        if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);\n        return response.json();\n      })\n      .then(files => {\n        const select = $('log-file-select');\n        select.innerHTML = '';\n        \n        if (!files || files.length === 0) {\n          select.innerHTML = '<option value=\"-1\">No log files found</option>';\n          $('loader').style.display = \"none\";\n          return;\n        }\n    \n        const csvFiles = files\n        .filter(file => file.name.endsWith('.csv'))\n        .sort((a, b) => {\n          const dateA = a.name.split('.')[0].split('_');\n          const dateB = b.name.split('.')[0].split('_');\n          const dateObjA = new Date(dateA[0], dateA[1] - 1, dateA[2]);\n          const dateObjB = new Date(dateB[0], dateB[1] - 1, dateB[2]);\n          return dateObjB - dateObjA;\n        });\n        \n        csvFiles.forEach(file => {\n          const dateParts = file.name.split('.')[0].split('_');\n          const formattedDate = `${dateParts[2]}/${dateParts[1]}/${dateParts[0]}`;\n          const option = new Option(\n            formattedDate,\n            file.name,\n            false,\n            false\n          );\n          select.add(option);\n        });\n    \n        if (csvFiles.length > 0) {\n          select.value = csvFiles[0].name;\n          loadCsvFile(csvFiles[0].name);\n        }\n      })\n      .catch(error => {\n        console.error('Error listing files:', error);\n        openModal('Error', `Failed to list log files: ${error.message}`);\n      })\n      .finally(() => {\n        $('loader').style.display = \"none\";\n      });\n    \n      $('log-file-select').addEventListener('change', function() {\n        const selectedFile = this.value;\n        if (selectedFile && selectedFile !== '-1') {\n          $('loader').style.display = \"block\";\n          loadCsvFile(selectedFile)\n          .catch(error => {\n            console.error('Error loading file:', error);\n            openModal('Error', 'Failed to load selected file');\n          })\n          .finally(() => {\n            $('loader').style.display = \"none\";\n          });\n        }\n      });\n    }\n    \n    \n    function loadCsvFile(filename) {\n      $('loader').style.display = \"block\";\n      currentCsvData = []; // Reset data\n      \n      fetch(`/logs/${encodeURIComponent(filename)}`)\n        .then(response => response.text())\n        .then(csvText => {\n            // Convert CSV to array of arrays\n            currentCsvData = csvText.split('\\n')\n                .filter(line => line.trim())\n                .map(line => line.split(',').map(cell => cell.replace(/\"/g, '').trim()));\n            \n            applyFilters(); // Apply filters immediately after loading\n        })\n        .catch(error => {\n            console.error(\"Error loading file:\", error);\n            $('logsTable').innerHTML = `<div class=\"table-row error\">Error loading file</div>`;\n        })\n        .finally(() => {\n            $('loader').style.display = \"none\";\n        });\n    }\n    \n    function updatePaginationButtons() {\n      $('prev-log').disabled = currentPage === 0;\n      $('next-log').disabled = (currentPage + 1) * recordsPerPage >= totalFilteredRecords;\n    }\n    \n    function applyFilters() {\n      if (currentCsvData.length === 0) return;\n      const usernameFilter = $('username-log').value.toLowerCase();\n      const readerFilter = $('reader-log').value.toLowerCase();\n      \n      // Filter data (skip header row)\n      let filteredData = currentCsvData.slice(1).filter(row => {\n          return (!usernameFilter || row[1].toLowerCase().includes(usernameFilter)) &&\n                 (!readerFilter || row[0].toLowerCase().includes(readerFilter));\n      });\n      \n      totalFilteredRecords = filteredData.length;\n      currentPage = 0; // Reset to first page when filters change\n      \n      // Get data for current page\n      const pagedData = filteredData.slice(\n        currentPage * recordsPerPage,\n        (currentPage + 1) * recordsPerPage\n      );\n      \n      // Update table\n      updateTable(pagedData);\n      updatePaginationButtons();\n      return filteredData;\n    }\n    \n    function updateTable(data) {\n      const tableBody = $('logsTable');\n      tableBody.innerHTML = '';\n      \n      if (data.length === 0) {\n        tableBody.innerHTML = '<div class=\"table-row\"><div class=\"col-empty\">No matching records</div></div>';\n        return;\n      }\n      \n      data.forEach(row => {\n        const rowElement = document.createElement('div');\n        rowElement.className = 'table-row';\n        rowElement.innerHTML = `\n          <div class=\"col-reader\">${row[0]}</div>\n          <div class=\"col-username\">${row[1]}</div>\n          <div class=\"col-tag\">${row[2]}</div>\n          <div class=\"col-timestamp\">${formatTimestamp(row[3])}</div>\n        `;\n        tableBody.appendChild(rowElement);\n      });\n    \n      // Mostra informazioni sulla paginazione (opzionale)\n      const startRecord = currentPage * recordsPerPage + 1;\n      const endRecord = Math.min((currentPage + 1) * recordsPerPage, totalFilteredRecords);\n      console.log(`Showing records ${startRecord}-${endRecord} of ${totalFilteredRecords}`);\n    }\n    \n    // Setup event listeners\n    function setupFilters() {\n      const inputs = [$('username-log'), $('reader-log')];\n      inputs.forEach(input => {\n        input.addEventListener('input', () => {\n          applyFilters(); // Reapply filters on each change\n        });\n      });\n    }\n    \n    function goToPrevPage() {\n      if (currentPage > 0) {\n        currentPage--;\n        showCurrentPage();\n      }\n    }\n    \n    function goToNextPage() {\n      if ((currentPage + 1) * recordsPerPage < totalFilteredRecords) {\n        currentPage++;\n        showCurrentPage();\n      }\n    }\n    \n    function showCurrentPage() {\n      const usernameFilter = $('username-log').value.toLowerCase();\n      const readerFilter = $('reader-log').value.toLowerCase();\n      \n      let filteredData = currentCsvData.slice(1).filter(row => {\n          return (!usernameFilter || row[1].toLowerCase().includes(usernameFilter)) &&\n                 (!readerFilter || row[0].toLowerCase().includes(readerFilter));\n      });\n      \n      const pagedData = filteredData.slice(\n        currentPage * recordsPerPage,\n        (currentPage + 1) * recordsPerPage\n      );\n      \n      updateTable(pagedData);\n      updatePaginationButtons();\n    }\n\n    // Export and download filtered data\n    function exportCSV() {\n      let csvString = 'reader, username, tag, timestamp\\n'\n      let filteredData = applyFilters();\n      filteredData.forEach( row => {\n        csvString += row.join(', ') + '\\n';\n      });\n      \n      // Download as CSV file\n      const blob = new Blob([csvString], { type: 'text/csv' });\n      const url = window.URL.createObjectURL(blob);\n      const a = document.createElement('a');\n      a.href = url;\n      a.download = 'data.csv';\n      document.body.appendChild(a);\n      a.click();\n      document.body.removeChild(a);\n      window.URL.revokeObjectURL(url);\n    }\n    \n    // Initialization\n    document.addEventListener('DOMContentLoaded', function() {   \n      $('loader').style.display = \"block\";\n      \n      setupFilters();\n      listLogFiles();\n      getUsers();\n      \n      // Event listeners\n      $('logsTab').addEventListener('click', tabClick);\n      $('usersTab').addEventListener('click', tabClick);\n      $('get-tag').addEventListener('click', readTagCode);\n      $('export-log').addEventListener('click', exportCSV);\n      $('prev-log').addEventListener('click', goToPrevPage);\n      $('next-log').addEventListener('click', goToNextPage);\n      \n      $('add-user').addEventListener('click', function(event) {\n        event.preventDefault(); \n        $('add-user').disabled = true;\n        $('delete-user').disabled = true;\n        sendUserForm('/addUser');\n      });\n      \n      $('delete-user').addEventListener('click', function(event) {\n        event.preventDefault(); \n        openModal('Delete user', \"Do you really want to drop current user record?\", deleteUser);\n      });\n\n      // Check if user has admin level (>= 5)\n      var usernameValue = document.cookie.replace(/(?:(?:^|.*;\\s*)username\\s*=\\s*([^;]*).*$)|^.*$/, \"$1\");\n      userLevel = usernameValue.split(',')[1];\n      if(userLevel >= 5) {\n        console.log(usernameValue.split(',')[0], \"is admin\");\n        $('handle-users').disabled = false;\n        $('setup').href = '/setup';\n      }\n    });\n  </script>\n</body>\n</html>"
  },
  {
    "path": "examples/localRFID/localRFID.ino",
    "content": "#include <Arduino.h>\n#include <FS.h>\n#include <LittleFS.h>\n#include <ArduinoJson.h>\n#include <FSWebServer.h>       // https://github.com/cotestatnt/esp-fs-webserver\n#include <MFRC522v2.h>         // https://github.com/OSSLibraries/Arduino_MFRC522v2\n#include <MFRC522DriverSPI.h>\n#include <MFRC522DriverPinSimple.h>\n#include <MFRC522Debug.h>\n#include <time.h>\n\n#define MYTZ \"CET-1CEST,M3.5.0,M10.5.0/3\"  // Timezone definition to get properly time from NTP server\n\n/*\n* To customize the appearance of the web pages, upload the login and rfid files \n* (without extensions) to the microcontroller's flash memory using built-in functionality (/setup webpage). \n* Set the following #define to false for send your pages instead of those embedded in the firmware. \n* You can find both files in the \"/data\" folder.\n*/\n#define USE_EMBEDDED_HTM true\n\n#if USE_EMBEDDED_HTM\n#include \"data/html_login.h\"\n#include \"data/html_rfid.h\"\n#endif\n\n#define PIN_MISO    15\n#define PIN_MOSI    16\n#define PIN_SCLK    17\n#define PIN_CS      18\n\nuint32_t chipId = 0;                   // Unique ID of the ESP chip\nbool addLogRecord = true;              // Flag to enable/disable log recording\n\nMFRC522DriverPinSimple ss_pin(PIN_CS); // Configurable, see typical pin layout above.\nMFRC522DriverSPI driver{ss_pin};       // Create SPI driver.\nMFRC522 mfrc522{driver};               // Create MFRC522 instance.\nstruct tm ntp;                         // Time structure holding current time from NTP\n\n#include \"JsonDB.hpp\"\n#include \"webserver.hpp\"\n\nTableManager usersTable(\"/users.json\");\n\nstruct User_t{\n  const char* username;\n  const char* password;\n  const char* name;\n  const char* email;\n  const char* tag;\n  uint8_t level;\n} ;\n\n// Some test users\nUser_t users[] = {\n  {\"admin\", \"admin\", \"DB adminitrator\", \"\", \"\", 5},\n  {\"user1\", \"\", \"John Doe\", \"jhon.doe@email.com\", \"12345\", 1},\n  {\"user2\", \"\", \"Mark twain\", \"mark65@email.com\", \"67890\", 1}\n};\n\nconst char* uniqueKeys[] = {\"tag\", \"username\"};\n\n//////////////////////////// Append a row to csv file ///////////////////////////////////\nbool appendRow(const char* username, uint64_t tag) {\n  getLocalTime(&ntp, 10);\n\n  char filename[32];\n  snprintf(filename, sizeof(filename),\"/logs/%04d_%02d_%02d.csv\", ntp.tm_year + 1900, ntp.tm_mon + 1, ntp.tm_mday);  \n\n  File file = LittleFS.open(\"/logs\");\n  if (!file) {    \n    LittleFS.mkdir(\"/logs\");\n    Serial.println(\"Created /logs directory\");\n  }\n  \n  if (LittleFS.exists(filename)) {\n    file = LittleFS.open(filename, \"a\");   // Append to existing file\n  }\n  else {\n    file = LittleFS.open(filename, \"w\");   // Create a new file    \n    file.println(\"reader, username, tag, timestamp\");\n    Serial.printf(\"Created file %s\\n\", filename);\n  }\n\n  if (file) {\n    char timestamp[25];\n    strftime(timestamp, sizeof(timestamp), \"%c\", &ntp);\n    char row[64];\n    snprintf(row, sizeof(row), \"%d, %s, %llu, %s\", chipId, username, tag, timestamp);        \n    Serial.println(row);\n    file.println(row);\n    file.close();\n    return true;\n  }\n  return false;\n}\n\nvoid setup() {\n  SPI.begin(PIN_SCLK, PIN_MISO, PIN_MOSI, PIN_CS);\n  Serial.begin(115200);\n  Serial.println(\"\\n\\n\\n\\nStarting ESP32 RFID gateway...\");\n\n  mfrc522.PCD_Init();                                       // Init MFRC522 board.\n  MFRC522Debug::PCD_DumpVersionToSerial(mfrc522, Serial);\t  // Show details of PCD - MFRC522 Card Reader details.\n  Serial.println(F(\"Scan PICC to see UID, SAK, type, and data blocks...\"));\n  \n  if (!LittleFS.begin()) {         \n    Serial.println(\"ERROR on mounting filesystem. It will be reformatted!\");\n    LittleFS.format();\n    ESP.restart();\n  }\n\n  // LittleFS.remove(\"/users.json\");\n\n  // Load the users table\n  usersTable.loadTable();\n\n  for (User_t user: users){\n    JsonDocument userDoc;\n    userDoc[\"username\"] = user.username;\n    userDoc[\"password\"] = getSHA256(user.password);\n    userDoc[\"name\"] = user.name;\n    userDoc[\"email\"] = user.email;\n    userDoc[\"tag\"] = user.tag;\n    userDoc[\"level\"] = user.level;\n\n    if (usersTable.addRecord(userDoc.as<JsonObject>(), uniqueKeys, 2)) \n      Serial.println(\"Record added to table\");\n    else\n      Serial.println(\"Error or record alreay present.\");\n  }\n\n  // Start webserver\n  startWebServer();\n  // Set NTP servers\n  #ifdef ESP8266\n  configTime(MYTZ, \"time.google.com\", \"time.windows.com\", \"pool.ntp.org\");\n  #elif defined(ESP32)\n  configTzTime(MYTZ, \"time.google.com\", \"time.windows.com\", \"pool.ntp.org\");\n  #endif\n  // Wait for NTP sync (with timeout)\n  getLocalTime(&ntp, 5000);\n\n  #if defined(ESP32)\n  for(byte i=0; i<17; i=i+8) {\n    chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;\n  }\n  #elif defined(ESP8266)\n  chipId = ESP.getChipId();\n  #endif\n  Serial.print(\"ESP Chip ID: \");\n  Serial.println(chipId);\n}\n\nvoid loop() {\n  myWebServer.run();  // Handle webserver events\n\n  delay(10);\n\n\tif (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {\n    static uint32_t readTime = millis();\n    static uint64_t oldCode = 0;\n\n    // The UID of an RFID tag can be up to 64bit long\n    uint64_t tagCode = 0;   \n\n    // tagCode is swapped, but it doesn't matter We need only it's a unique number\n    for(byte i = 0; i < mfrc522.uid.size; i++) {\n      tagCode |= mfrc522.uid.uidByte[i] << (8*i);\n    }\n\n    if ((tagCode != oldCode) || (millis() - readTime > 5000)) {\n      readTime = millis();\n      oldCode = tagCode;\n\n      // Serial.printf(\"\\nTag code: %llu\\n\", tagCode);\n      if (addLogRecord) {                \n        JsonObject user = usersTable.findRecord(\"tag\", String(tagCode).c_str());\n        if (user) {        \n          const char* username = user[\"username\"].as<const char*>();\n          appendRow(username, tagCode);\n        }\n        else {\n          appendRow(\"not allowed\", tagCode);\n        }\n      }\n    }\n\t}\n}\n\n\n"
  },
  {
    "path": "examples/localRFID/partitions.csv",
    "content": "# Name,   Type, SubType, Offset,  Size, Flags\nnvs,      data, nvs,     0x9000,  0x5000,\notadata,  data, ota,     0xe000,  0x2000,\napp0,     app,  ota_0,   0x10000, 0x140000,\napp1,     app,  ota_1,   0x150000,0x140000,\nspiffs,   data, spiffs,  0x290000,0x160000,\ncoredump, data, coredump,0x3F0000,0x10000,\n"
  },
  {
    "path": "examples/localRFID/platformio.ini",
    "content": "; PlatformIO Project Configuration File\n;\n;   Build options: build flags, source filter\n;   Upload options: custom upload port, speed and extra flags\n;   Library options: dependencies, extra library storages\n;   Advanced options: extra scripting\n;\n; Please visit documentation for the other options and examples\n; https://docs.platformio.org/page/projectconf.html\n\n[platformio]\nsrc_dir = .\n\n[env:esp32-s3-devkitc1-n4r2]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32-s3-devkitc1-n4r2\nframework = arduino\nupload_speed = 921600\nboard_build.partitions = partitions.csv\n\nlib_extra_dirs = \n    ../../\n    C:/Users/cotes/OneDrive/Documenti/Arduino/libraries/RFID_MFRC522v2\n    C:/Users/cotes/OneDrive/Documenti/Arduino/libraries/ArduinoJson\n\n\n[env:esp32dev]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32dev\nframework = arduino\nupload_speed = 921600\nboard_build.partitions = partitions.csv\nlib_extra_dirs = ../../\n\n[env:esp8266-nodemcuv2]\nplatform = espressif8266\nboard = nodemcuv2\nframework = arduino\nupload_speed = 921600\nboard_build.partitions = partitions.csv\nlib_extra_dirs = ../../\n"
  },
  {
    "path": "examples/localRFID/readme.md",
    "content": "In this example, a simple access control system has been implemented using RFID tags and the [Arduino_MFRC522v2](https://github.com/OSSLibraries/Arduino_MFRC522v2) library. \n\nThe system allows enabling or disabling any type of user, each of whom can be assigned a different role. \n\n![image](https://github.com/user-attachments/assets/5ad7afe1-2e95-4df9-8549-698a9442e01a)\n\nFor instance, modifying or deleting user accounts requires logging in with a role level of at least 5 (such as the admin user). \nUsers with lower levels can still log in, but only to view data.\n\n# Logs\n![image](https://github.com/user-attachments/assets/4aab4a8a-3ce7-473c-a96b-ee79a70b7ad1)\n\n# User management\n![image](https://github.com/user-attachments/assets/b9663a4c-50f8-4a00-b26f-ab2369839ffb)\n\n# Customization\nBy default, this example includes web pages embedded in the firmware as C arrays, compressed using Gzip.\n\nTo customize the appearance of the web pages, \nupload the [`login`](https://github.com/cotestatnt/async-esp-fs-webserver/blob/master/examples/localRFID/data/login) \nand [`rfid`](https://github.com/cotestatnt/async-esp-fs-webserver/blob/master/examples/localRFID/data/rfid) files (without extensions) to the microcontroller's flash memory using built-in functionality (/setup webpage). \nYou can find both files in the `\"/data\"` folder.\n\nThen set `#define USE_EMBEDDED_HTM false` in order to serve files webpage instead embedded.\n\nIn the [mysqlRFID.ino](https://github.com/cotestatnt/async-esp-fs-webserver/blob/master/examples/mysqlRFID/mysqlRFID.ino) example, a MySQL database is used through the [Arduino-MySQL](https://github.com/cotestatnt/Arduino-MySQL) library. \nSince the database is centralized, it supports multiple RFID tag readers, logging each access point for tracking purposes. \nThe web pages in this example are embedded as `const char*` strings, making them easy to edit directly within the project.\n"
  },
  {
    "path": "examples/localRFID/webserver.hpp",
    "content": "#pragma once\n#include <Arduino.h>\n\n#include <LittleFS.h>\n#include <FSWebServer.h>  // https://github.com/cotestatnt/esp-fs-webserver\n#include \"mbedtls/md.h\"\n\n\n// Webserver class\nFSWebServer myWebServer(LittleFS, 80, \"esp32rfid\");\n\nextern TableManager usersTable;\nextern const char* uniqueKeys[];\n\n\nString getSHA256(const char* payload) {\n  String hashed = \"\";\n  byte shaResult[32];\n  mbedtls_md_context_t ctx;\n  const size_t payloadLength = strlen(payload);         \n  mbedtls_md_init(&ctx);\n  mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 0);\n  mbedtls_md_starts(&ctx);\n  mbedtls_md_update(&ctx, (const unsigned char *) payload, payloadLength);\n  mbedtls_md_finish(&ctx, shaResult);\n  mbedtls_md_free(&ctx);\n  for(int i= 0; i< sizeof(shaResult); i++){\n    char str[3];\n    sprintf(str, \"%02x\", (int)shaResult[i]);\n    hashed += str;\n  }\n  return hashed;\n}\n\nint getUserLevel(const String& username, const String&  hash) {\n  JsonObject user = usersTable.findRecord(\"username\", username.c_str());\n  if (user) {\n    if (hash.equals(user[\"password\"].as<const char*>())) {\n      return user[\"level\"].as<int>();\n    }\n  }\n  return 0;\n}\n\nvoid handleGetUsers() {\n  JsonArray users = usersTable.getUsers();\n  String json;\n  serializeJsonPretty(users, json);\n  myWebServer.send(200, \"application/json\", json);\n}\n\nvoid handleNewUser() {\n  String username = myWebServer.arg(\"username\");\n  String name = myWebServer.arg(\"name\");\n  String email = myWebServer.arg(\"email\");\n  String tag = myWebServer.arg(\"tag\");\n  String level = myWebServer.arg(\"level\");\n  String hashedPassword = getSHA256(myWebServer.arg(\"password\").c_str());\n  bool update = myWebServer.arg(\"type\").equals(\"Update\");\n\n  JsonDocument newUser;\n  newUser[\"username\"] = username;\n  newUser[\"password\"] = hashedPassword;\n  newUser[\"name\"] = name;\n  newUser[\"email\"] = email;\n  newUser[\"tag\"] = tag;\n  newUser[\"level\"] = level;\n\n  usersTable.loadTable();\n\n  if (update){\n    if (!usersTable.deleteRecord(\"username\", username.c_str()))\n      myWebServer.send(500, \"text/plain\", \"Error\");\n  }\n  \n  if (usersTable.addRecord(newUser.as<JsonObject>(),uniqueKeys, 2))  {\n    myWebServer.send(200, \"text/plain\", \"OK\");\n    return;\n  } \n  myWebServer.send(500, \"text/plain\", \"Error\");\n}\n\nvoid handleRemoveUser() {\n  String username = myWebServer.arg(\"username\");\n  if (usersTable.deleteRecord(\"username\", username.c_str()))\n    myWebServer.send(200, \"text/plain\", \"OK\");\n  else\n    myWebServer.send(500, \"text/plain\", \"Error\");\n}\n\nvoid handleGetCode() {\n  uint32_t timeout = millis();\n\n  while (true) {\n    if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {\n      uint64_t tagCode = 0;\n\n      // tagCode is swapped, but it doesn't matter We need only it's a unique number\n      for(byte i = 0; i < mfrc522.uid.size; i++) {\n        tagCode |= mfrc522.uid.uidByte[i] << (8*i);\n      }\n      \n      // With 8 byte TAG code, the result integer could be too large since JavaScript \n      // uses 64-bit floating-point numbers (IEEE 754), which have a maximum precision of 2^53 - 1 \n      String result = \"{\\\"tag\\\": \\\"\";\n      result += String(tagCode);  \n      result += \"\\\"}\";\n\n      Serial.printf(\"Tag code: 0x%llX\", tagCode);\n      myWebServer.send(200, \"application/json\", result);\n      addLogRecord = true;\n      return;\n    }\n\n    if (millis() - timeout > 5000) {\n      myWebServer.send(500, \"application/json\", \"{\\\"error\\\": \\\"timeout\\\"}\");\n      addLogRecord = true;\n      return;\n    }\n  }\n}\n\n// This handler will be called from login page to check password\nvoid handleCheckHash() {\n\n  // Even if user con login, only user with level >= 5 can edit users table\n  if (getUserLevel(myWebServer.arg(\"username\"), myWebServer.arg(\"hash\"))) {\n    myWebServer.send(200, \"text/plain\", \"OK\");\n  }\n  else {\n    myWebServer.send(401, \"text/plain\", \"Wrong password\");\n  }\n}\n\n\n// This handler will be called from login page on login succesfull\nvoid handleMainPage() {\n  // Check again user and password to avoid direct page loading\n  int level = getUserLevel(myWebServer.arg(\"username\"), myWebServer.arg(\"hash\"));\n  if (level) {\n    // Even if any user con login succesfully, only user with level >= 5 can edit users table\n    // Username and user level is set here using cookie.\n    String cookie = \"username=\" ;\n    cookie += myWebServer.arg(\"username\");\n    cookie += \",\";  cookie += level;  cookie += \"; Path=/\";\n#if USE_EMBEDDED_HTM\n    myWebServer.sendHeader(\"Content-Encoding\", \"gzip\");\n    myWebServer.sendHeader(\"Set-Cookie\", cookie);\n    myWebServer.send_P(200, \"text/html\", (const char*)rfid, sizeof(rfid));\n#else\n    myWebServer.sendHeader(\"Set-Cookie\", cookie);\n    myWebServer.send(200, \"text/html\", \"\");  // Send file from filesystem if not embedded\n#endif\n  } \n  else {\n    myWebServer.send(401, \"text/plain\", \"Wrong password\");\n  }\n}\n\n\n// Configure and start webserver\nbool startWebServer(bool clear = false) {\n  bool connected = false;\n  // FILESYSTEM INIT\n  if (!LittleFS.begin()) {\n    Serial.println(\"ERROR on mounting filesystem. It will be formmatted!\");\n    LittleFS.format();\n    ESP.restart();\n  }\n\n  if (clear) {\n    LittleFS.remove(myWebServer.getConfiFileName());\n  }\n\n  // Try to connect to WiFi (will start AP if not connected after timeout)\n  if (!myWebServer.startWiFi(15000)) {\n    Serial.println(\"\\nWiFi not connected! Starting AP mode...\");\n    myWebServer.startCaptivePortal(\"ESP32_RFID\", \"\", \"/setup\");\n  }\n  else \n    connected = true;\n    \n  // Add endpoints request handlers\n  myWebServer.on(\"/users\", HTTP_GET, handleGetUsers);\n  myWebServer.on(\"/addUser\", HTTP_POST, handleNewUser);\n  myWebServer.on(\"/delUser\", HTTP_POST, handleRemoveUser);\n  myWebServer.on(\"/getCode\", HTTP_GET, handleGetCode);\n  myWebServer.on(\"/waitCode\", HTTP_GET, [](){\n    addLogRecord = false; \n    myWebServer.send(200, \"text/plain\", \"OK\");\n  });\n\n  /* \n  * To avoid ugly and basic login prompt avalaible with \"stardard\" DIGEST_AUTH\n  * let's use a custom login web page (from flash literal string). This web page\n  * will send a POST request to /rfid enpoint passing username and password SHA256 hash\n  */\n  myWebServer.on(\"/\", HTTP_ANY, [](){    \n    // Redirect to login page\n    myWebServer.sendHeader(\"Location\", \"/login\");\n    myWebServer.send(302, \"text/plain\", \"\");\n  });\n\n  #if USE_EMBEDDED_HTM\n  myWebServer.on(\"/login\", HTTP_ANY, [](){\n    myWebServer.sendHeader(\"Content-Encoding\", \"gzip\");\n    myWebServer.send_P(200, \"text/html\", (const char*)rfid, sizeof(rfid));\n  });\n  #endif\n  \n  /*\n  * If client calculated password SHA256 hash string match with the user stored,\n  * we can serve the /rfid web page (from flash literal string, same as login page)\n  */\n  myWebServer.on(\"/rfid\", HTTP_POST, handleCheckHash);\n\n  /*\n  * Although it is not the conventional way, handle this page with a PUT request\n  * otherwise it will be impossible to edit file if needed.\n  * The embedded /edit webpage uses the GET method to retrieve the file content.\n  */\n  myWebServer.on(\"/rfid\", HTTP_PUT, handleMainPage);\n\n  // Enable ACE FS file web editor and add FS info callback function\n  myWebServer.enableFsCodeEditor();\n\n  // Start the webserver\n  myWebServer.begin();\n  Serial.print(\"\\n\\nESP Web Server started on IP Address: \");\n  Serial.println(myWebServer.getServerIP());\n  Serial.println(\n    \"Open /setup page to configure optional parameters.\\n\"\n    \"Open /edit page to view, edit or upload example or your custom webserver source files.\"\n  );\n\n  return connected;\n}"
  },
  {
    "path": "examples/mqtt_webserver/.gitignore",
    "content": ".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",
    "content": "/*\n Basic ESP32 MQTT example:\n It connects to an MQTT server then:\n  - publishes \"hello world\" to the topic \"output\" every two seconds\n  - subscribes to the topic \"input\", printing out any (string) messages it receives.\n  - If the first character of the topic \"input\" is an 1, switch ON the ESP Led, else switch it off\n  It will reconnect to the server using a NON blocking reconnect function. \n*/\n#include <FS.h>\n#include <LittleFS.h>\n#include <WiFi.h>\n#include <WiFiClient.h>\n\n#include <PubSubClient.h>      // https://github.com/knolleary/pubsubclient/\n#include <FSWebServer.h>       // https://github.com/cotestatnt/esp-fs-webserver\n\n#ifndef BUILTIN_LED\n#define BUILTIN_LED 2  // Pin number for the built-in LED on ESP32 boards\n#endif\n\nFSWebServer myWebServer(LittleFS, 80, \"hostname\");\n\n// Update these with values suitable for your network.\nconst char* mqtt_server = \"broker.mqtt-dashboard.com\";\n\n////////////////////////////////  Filesystem  /////////////////////////////////////////\nvoid startFilesystem() {\n  if ( !LittleFS.begin()) {\n    Serial.println(\"ERROR on mounting filesystem. It will be formmatted!\");\n    LittleFS.format();\n    ESP.restart();\n  }\n  myWebServer.printFileList(LittleFS, \"/\", 1, Serial);\n}\n\n///////////////////////////  MQTT callback function  ///////////////////////////////////\nvoid mqttCallback(char* topic, byte* payload, unsigned int length) {\n  Serial.printf(\"Message arrived [%s] \", topic);\n  for (int i = 0; i < length; i++) {\n    Serial.print((char)payload[i]);\n  }\n  Serial.println();\n\n  // Switch on the LED if an 1 was received as first character (LED on with LOW signal on most boards)\n  digitalWrite(BUILTIN_LED, (char)payload[0] == '1' ? LOW : HIGH);\n}\n\n\n// Declare task handle\nTaskHandle_t mqttTaskHandle = NULL;\n\nvoid mqttTask(void *parameter) {\n  // Create WiFiClient and PubSubClient locally in the task\n  WiFiClient espClient;\n  PubSubClient mqttClient(espClient);\n\n  uint32_t lastMsgTime = millis();\n  uint32_t lastConnectionTime = 0;\n  uint16_t value = 0;\n\n  // Set MQTT server and callback function\n  mqttClient.setServer(mqtt_server, 1883);\n  mqttClient.setCallback(mqttCallback);\n\n  // Create a unique mqttClient ID and in/out topics\n  char clientId[16];\n  char inTopic[24];\n  char outTopic[24];\n  snprintf(clientId, sizeof(clientId), \"ESP-%llX\", ESP.getEfuseMac());\n  snprintf(inTopic, sizeof(inTopic), \"%s/input\", clientId);\n  snprintf(outTopic, sizeof(outTopic), \"%s/output\", clientId);\n\n  Serial.print(\"MQTT CLiend ID: \"); Serial.println(clientId);\n  Serial.print(\"Publish output topic: \"); Serial.println(outTopic);\n  Serial.print(\"Subscribe input topic: \"); Serial.println(inTopic);\n\n  // Task infinite loop\n  for (;;) { \n    // Yield to other tasks\n    vTaskDelay(100 / portTICK_PERIOD_MS);  \n\n    // Debug: print stack info every 30 seconds\n    static uint32_t lastStackCheck = 0;\n    if (millis() - lastStackCheck > 30000) {\n      lastStackCheck = millis();\n      UBaseType_t stackWatermark = uxTaskGetStackHighWaterMark(NULL);\n      Serial.printf(\"[MQTT Task] Stack high water mark: %u bytes Free\\n\",  stackWatermark * 4);\n    }\n\n    // Handle MQTT connection\n    if (!mqttClient.connected()) {\n      // Try to reconnect every 5 seconds if WiFi is connected\n      if (WiFi.status() == WL_CONNECTED && millis() - lastConnectionTime > 5000) {\n        lastConnectionTime = millis();\n\n        Serial.print(\"Attempting MQTT connection...\");\n        if (mqttClient.connect(clientId)) {\n          Serial.println(\"connected\");\n          char payload[64];\n          snprintf(payload, sizeof(payload), \"Hello World from %s\", clientId);\n          mqttClient.publish(outTopic, payload);\n          mqttClient.subscribe(inTopic);\n        } \n        else {\n          Serial.printf(\"failed, rc=%d\\n\", mqttClient.state());\n        }\n      }\n    } \n    else {\n      // Client connected, handle MQTT messages\n      mqttClient.loop();\n\n      // Publish a new message every 5 seconds\n      if (millis() - lastMsgTime > 5000) {\n        lastMsgTime = millis();\n\n        char payload[64];\n        snprintf(payload, sizeof(payload), \"Hello World from %s #%d\", clientId, ++value);\n        mqttClient.publish(outTopic, payload);\n        Serial.print(\"Publish message: \");\n        Serial.println(payload);\n      }\n    }\n  }\n}\n\nvoid setup() {\n  pinMode(BUILTIN_LED, OUTPUT);  // Initialize the BUILTIN_LED pin as an output\n  Serial.begin(115200);\n  delay(1000);\n  Serial.println(\"\\n\\nESP32 MQTT & WebServer Example\");\n\n  // LittleFS filesystem init\n  startFilesystem();\n\n  // Try to connect to stored SSID, start AP if fails after timeout\n  if (!myWebServer.startWiFi(15000)) {\n    Serial.println(\"\\nWiFi not connected! Starting AP mode...\");\n    myWebServer.startCaptivePortal(\"ESP32_RFID\", \"123456789\", \"/setup\");\n  }\n\n  // Enable ACE FS file web editor and add FS info callback function\n  myWebServer.enableFsCodeEditor();\n\n  // Start webserver\n  myWebServer.begin();\n  Serial.print(F(\"ESP Web Server started on IP Address: \"));\n  Serial.println(myWebServer.getServerIP());\n  Serial.println(F(\"Open /setup page to configure optional parameters\"));\n  Serial.println(F(\"Open /edit page to view and edit files\"));\n\n  // Create and start MQTT task\n  xTaskCreate(\n    mqttTask,         // Task function\n    \"mqttTask\",       // Task name\n    10000,            // Stack size (bytes)\n    NULL,             // Parameters\n    10,               // Priority\n    &mqttTaskHandle   // Task handle\n  );\n  Serial.printf(\"[DEBUG] MQTT Task created with stack size: 10000 bytes (~2500 words)\\n\");\n  \n}\n\nvoid loop() {\n  // Nothing to do here for MQTT, all handled in mqttTask\n  delay(10);\n  \n  myWebServer.run();  // Handle web server requests\n}\n"
  },
  {
    "path": "examples/mqtt_webserver/partitions.csv",
    "content": "# Name,   Type, SubType, Offset,  Size, Flags\nnvs,      data, nvs,     0x9000,  0x5000,\notadata,  data, ota,     0xe000,  0x2000,\napp0,     app,  ota_0,   0x10000, 0x140000,\napp1,     app,  ota_1,   0x150000,0x140000,\nspiffs,   data, spiffs,  0x290000,0x160000,\ncoredump, data, coredump,0x3F0000,0x10000,\n"
  },
  {
    "path": "examples/mqtt_webserver/platformio.ini",
    "content": "; PlatformIO Project Configuration File\n;\n;   Build options: build flags, source filter\n;   Upload options: custom upload port, speed and extra flags\n;   Library options: dependencies, extra library storages\n;   Advanced options: extra scripting\n;\n; Please visit documentation for the other options and examples\n; https://docs.platformio.org/page/projectconf.html\n\n[platformio]\nsrc_dir = .\n\n[env:esp32-s3-devkitc1-n4r2]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32-s3-devkitc1-n4r2\nframework = arduino\nupload_speed = 921600\nboard_build.partitions = partitions.csv\n\nlib_extra_dirs = \n    ../../\n    C:/Users/cotes/OneDrive/Documenti/Arduino/libraries/PubSubClient    \n\n\n[env:esp32dev]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32dev\nframework = arduino\nupload_speed = 921600\nboard_build.partitions = partitions.csv\nlib_extra_dirs = ../../\n\n[env:esp8266-nodemcuv2]\nplatform = espressif8266\nboard = nodemcuv2\nframework = arduino\nupload_speed = 921600\nboard_build.partitions = partitions.csv\nlib_extra_dirs = ../../\n"
  },
  {
    "path": "examples/mqtt_webserver/readme.md",
    "content": "![MQTTX app](https://github.com/user-attachments/assets/8dd72a75-94b6-44d6-aa7b-0e35d0bc9347)\n"
  },
  {
    "path": "examples/mysqlRFID/.gitignore",
    "content": ".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",
    "content": "#pragma once\n#include <Arduino.h>\n\ninline const char login_htm[] PROGMEM = R\"string_literal(\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Login</title>\n    <style>\n    #about {color:lightgray};\n    body {background-color: #f5f5f5; font-family: Arial, sans-serif;}\n    header, footer {padding: 10px; background-color: #024c5b; color: #fff; width: 50%; text-align: center;}\n    header {margin-top: 40px;}\n    label {display: block; margin: 5px;}\n    input[type=\"text\"], input[type=\"password\"], input[type=\"email\"] {width: 80%; padding: 8px; border: 1px solid #ddd; border-radius: 3px;}\n    button[type=\"submit\"] {margin-top: 20px; width: 50%; min-width: 60px; padding: 10px; background-color: #607D8B; color: white; border: none; cursor: pointer;}\n    button[type=\"submit\"]:hover, .nav {background-color: #16729F;}\n    .container {display: flex; flex-direction: column; align-items: center; min-height: 100vh;}\n    .custom-container {padding: 10px; width: 50%; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); background-color: #ffffff;}  \n    .content {padding: 20px; display: flex; flex-direction: column;}\n    .center {text-align: center};\n  </style>\n</head>\n<body>\n  <div class=\"container\">\n    <header>\n      <h1>ESP32 RFID Logs - Login</h1> \n    </header>\n    <div class=\"custom-container\">\n      <div class=\"content\">\n        <form id=\"loginForm\" class=\"center\">\n          <div>\n            <label for=\"username\">Username:</label>\n            <input type=\"text\" id=\"username\" name=\"username\" required>\n          </div>\n          <div>\n            <label for=\"password\">Password:</label>\n            <input type=\"password\" id=\"password\" name=\"password\" required>\n          </div>\n          <button type=\"submit\">Login</button>\n        </form>\n      </div>\n    </div>\n    <footer class=\"footer\">\n      <div class=\"has-text-centered\">\n        <p> RFID Log &copy; 2024. All rights reserved.</p>\n        <a id=about target=_blank rel=noopener href=\"https://github.com/cotestatnt/esp-fs-webserver/\">Created with https://github.com/cotestatnt/esp-fs-webserver/</a>\n      </div>\n    </footer>\n  </div>\n\n  <script>\n    // To avoid sharing plain text between client and server, \n    // send SHA256 of password input text (based on https://geraintluff.github.io/sha256/)\n    function sha256(ascii) {\n      var rightRotate = (value, amount) => (value >>> amount) | (value << (32 - amount));\n      var mathPow = Math.pow, maxWord = mathPow(2, 32), lengthProperty = 'length', result = '', words = [], asciiBitLength = ascii[lengthProperty] * 8;\n      var hash = sha256.h = sha256.h || [], k = sha256.k = sha256.k || [], primeCounter = k[lengthProperty];\n      var isComposite = {};\n      for (var candidate = 2; primeCounter < 64; candidate++) {\n        if (!isComposite[candidate]) {\n          for (i = 0; i < 313; i += candidate) isComposite[i] = candidate;\n          hash[primeCounter] = (mathPow(candidate, .5) * maxWord) | 0;\n          k[primeCounter++] = (mathPow(candidate, 1 / 3) * maxWord) | 0;\n        }\n      }\n      ascii += '\\x80';\n      while (ascii[lengthProperty] % 64 - 56) ascii += '\\x00';\n      for (i = 0; i < ascii[lengthProperty]; i++) {\n        j = ascii.charCodeAt(i);\n        if (j >> 8) return;\n        words[i >> 2] |= j << ((3 - i) % 4) * 8;\n      }\n      words[words[lengthProperty]] = ((asciiBitLength / maxWord) | 0);\n      words[words[lengthProperty]] = (asciiBitLength);\n      for (j = 0; j < words[lengthProperty];) {\n        var w = words.slice(j, j += 16);\n        var oldHash = hash.slice(0, 8);\n        for (i = 0; i < 64; i++) {\n          var i2 = i + j, w15 = w[i - 15], w2 = w[i - 2];\n          var a = hash[0], e = hash[4];\n          var temp1 = hash[7] + (rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25)) + ((e & hash[5]) ^ ((~e) & hash[6])) + k[i] +\n              (w[i] = (i < 16) ? w[i] : (w[i - 16] + (rightRotate(w15, 7) ^ rightRotate(w15, 18) ^ (w15 >>> 3)) + w[i - 7] +\n                  (rightRotate(w2, 17) ^ rightRotate(w2, 19) ^ (w2 >>> 10))) | 0);\n\n          var temp2 = (rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22)) + ((a & hash[1]) ^ (a & hash[2]) ^ (hash[1] & hash[2]));\n          hash = [(temp1 + temp2) | 0].concat(hash);\n          hash[4] = (hash[4] + temp1) | 0;\n        }\n        for (i = 0; i < 8; i++) hash[i] = (hash[i] + oldHash[i]) | 0;\n      }\n      for (i = 0; i < 8; i++)\n        for (j = 3; j + 1; j--)\n          result += ((hash[i] >> (j * 8)) & 255).toString(16).padStart(2, '0');\n      return result;\n   };\n\n    document.getElementById('loginForm').addEventListener('submit', function(event) {\n      event.preventDefault();\n      const hash = sha256(document.getElementById('password').value);\n      const username = document.getElementById('username').value;\n      var formData = new FormData();\n      formData.append(\"username\", document.getElementById('username').value);\n      formData.append(\"hash\", hash);\n      \n      fetch('/rfid', {\n        method: 'POST',\n        body: formData\n      })\n      .then(response => {\n        if (response.ok) {\n            var url = '/rfid?username=' + username + '&hash=' + hash;\n            window.location.href = url;\n        } else {\n            alert(\"Wrong password\");\n        };\n      });\n    });\n  </script>\n</body>\n</html>\n)string_literal\";\n\n\n\n\n\nstatic const char index_htm[] PROGMEM = R\"string_literal(\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>RFID Log</title>\n  <style>\n    #about {color:lightgray};\n    details,main,summary{display: block;}\n    body {background-color: #f5f5f5; margin: 0; font-family: Arial, sans-serif; margin-top: 20px;}\n    header, footer {padding: 10px; background-color: #024c5b; color: #fff; width: 90%; text-align: center;}\n    label {display: block; margin-bottom: 5px;}\n    input[type=\"text\"], input[type=\"password\"], input[type=\"email\"] {width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 3px;}\n    button.submit {margin: 0 20px -10px 20px; width: 80%; min-width: 60px; padding: 10px; background-color: #607D8B; color: white; border: none; cursor: pointer;}\n    button.submit:hover {background-color: #16729F;}\n    button[disabled]:hover, button[disabled] {background-color: #ccc; color: #666;}\n  \n    .container {display: flex; flex-direction: column; align-items: center; min-height: 100vh;}\n    .custom-container {padding: 10px; width: 90%; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); background-color: #ffffff;}\n    .sidebar {background-color: #f8f9fa; flex: 0 0 10%; transition: all 0.3s ease;}\n    .tab {padding: 10px 20px; cursor: pointer;}\n    .tab.active {background-color: #e9ecef;}\n    .content {flex-grow: 1; display: flex; justify-content: center; align-items: flex-start;}\n    .main-content {width: 100%; padding: 0 0 20px 20px; min-height: 70vh; }\n    .table-container {display: flex; flex-direction: column; border: 1px solid #ddd; border-radius: 5px; overflow: hidden;}\n    .table-header {display: flex; background-color: #f2f2f2; padding: 10px 15px; font-weight: bold; justify-content: space-around;}\n    .table-body {display: flex; flex-direction: column;}\n    .table-body > div {display: flex; padding: 4px 10px; border-bottom: 1px solid #ddd; justify-content: space-around;}\n    .table-body > div:nth-child(even) {background-color: #f9f9f9;}\n    .table-body > div.selected {background-color: #d3d3d3; border: 1px solid orange; color: blue;}\n    .section {position: relative;}\n    .collapse-container {margin-bottom: 20px;}\n    .collapse-content {display: none; padding: 10px; border: 1px solid #ddd; border-radius: 5px; margin-bottom: 20px;}\n    .collapse-content.show {display: block;}\n    .form-row {display: flex; flex-wrap: wrap; margin-bottom: 10px; align-items: flex-end;}\n    .form-column {flex: 1; margin: 0 20px 0 20px;}\n    .form-column:last-child { margin-right: 40px; }\n    .but-row {display: flex; margin: 20px 0 10px 0;}\n    .button:hover {cursor: pointer; background-color: #16729F;}\n    .button:active {transform: scale(0.8);}\n    \n    .floating {position: absolute; float: right; z-index: 1; right: -4px;}\n    .floating.top {top: -8px; }\n    .floating.bottom {bottom: -8px;}\n    .floating.inline {position: sticky;}\n    .floating .button {width: 24px; height: 24px; border-radius: 50%; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.8);\n                      border: 1px solid transparent; font-family: monospace; font-size: larger; color: cornsilk;}\n\n    .green {background-color: #008000ba;}\n    .blue {background-color: #607D8B;}\n    .w05 {width: 5%;} .w10 {width: 10%;} .w20 {width: 20%;} .w30 {width: 30%;}\n    \n    .d-modal{\n      width: -webkit-fill-available; border-radius: 10px; border-style: solid; border-width: 1px; border-color: #3333336e;\n      box-shadow: rgba(0, 0, 0, 0.24) 0 3px 8px;left: 50%; position: absolute; top: 60%; transform: translate(-50%, -50%);\n      flex-direction: column; background-color: hsl(199.53deg 18.3% 46.08% / 90%); z-index: 999;\n    }\n    .d-modal-title{color:#111827;padding:1.5em;position:relative;width:calc(100% - 4.5em);}\n    .d-modal-content{border-top:1px solid #e0e0e0;padding:1.5em;overflow:auto;}\n    .btn{display:inline-flex ;padding:10px 15px; background-color:#2E8BC0;color:#fff;border:0;cursor:pointer;min-width:40%;border-radius:5px;justify-content:center;}\n    .btn:hover{ filter: brightness(85%);}\n    .btn-bar{display:flex;padding:20px;justify-content:center;flex-wrap:wrap;grid-column-gap:30px;grid-row-gap:20px; order:998;}\n    \n    /*Must be the last rule*/\n    .hide {display: none; }\n    .loader { width: 120px; height: 120px; border: 24px solid #dedbdba8; border-top: 24px solid #3498dbe3;  border-radius: 50%;\n       animation: spin 2s linear infinite;  position: fixed; top: 50%; left: 50%; margin: -60px; display: none;}\n    @keyframes spin { 0% { transform: rotate(0deg); }  100% { transform: rotate(360deg); } }\n\n  </style>\n</head>\n<body>\n  <div class=\"loader\" id=\"loader\"></div>\n  \n  <details id=modal-message>\n    <summary style=\"display: block\"></summary>\n    <div class=d-modal>\n      <div class=d-modal-title><h2 id=message-title>t</h2></div>\n      <div class=d-modal-content><p id=message-body></p></div>\n      <div class=btn-bar>\n          <a id=ok-modal class=\"btn hide\" onclick=closeModal(true)><span>OK</span></a>\n          <a id=close-modal class=\"btn\" onclick=closeModal(false)><span>Close</span></a>\n      </div>\n    </div>\n  </details>\n          \n  <div id=\"main-box\" class=\"container\">\n    <header>\n      <h1 class=\"title is-3\">ESP32 RFID Logs</h1> \n    </header>\n    <div class=\"custom-container\">\n      <div class=\"content\">\n        <div class=\"sidebar\">\n          <div class=\"tab\" id=\"logsTab\" data-target=\"logsContent\">Logs</div>\n          <div class=\"tab\" id=\"usersTab\" data-target=\"usersContent\">Users</div>\n          <div class=\"tab\"><a id=\"setup\" style=\"color: inherit; text-decoration: none;\" href=\"#\" disabled>Setup</a></div>\n        </div>\n        <div class=\"main-content\">\n          <div id=\"logsContent\" class=\"section\">\n            \n            <div class=\"floating top\">\n              <button class=\"button green\" onclick=\"toggleCollapse('collapse-logs')\"><b id='toggle-logs'>+</b></button>\n            </div>\n            \n            <div class=\"floating bottom\">\n              <button class=\"button blue\" id=\"prev-log\"><b>&lt;</b></button>\n              <button class=\"button blue\" id=\"next-log\"><b>&gt;</b></button>\n            </div>\n            \n            <div class=\"collapsible\">\n              <div id=\"collapse-logs\" class=\"collapse-content\">\n               <div id=\"insertForm\">\n                  <div class=\"form-row\">\n                    <div class=\"form-column\">\n                      <input type=\"datetime-local\" id=\"start\">\n                      <input type=\"datetime-local\" id=\"end\">\n                    </div>\n                    <div class=\"form-column\">\n                      <label for=\"username-log\">Username:</label>\n                      <input type=\"text\" id=\"username-log\" name=\"username-log\" placeholder=\"Username\">\n                    </div>\n                    <div class=\"form-column\">\n                      <label for=\"reader-log\">Reader:</label>\n                      <input type=\"text\" id=\"reader-log\" name=\"reader-log\" placeholder=\"Reader number\">\n                    </div>\n                    \n                    <div class=\"but-row\">\n                      <button class=\"submit\" id=\"filter-log\">Filter</button>\n                      <button class=\"submit\" id=\"export-log\">Export</button>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </div>\n    \n            <div class=\"table-container\">\n              <div class=\"table-header\">\n                <div class=\"w05\">ID</div>\n                <div class=\"w30\">Timestamp</div>\n                <div class=\"w30\">Username</div>\n                <div class=\"w10\">Tag Code</div>\n                <div class=\"w10\">Reader</div>\n              </div>\n              <div id=\"logsTable\" class=\"table-body\">\n                <!-- //// -->\n              </div>\n            </div>\n          </div>\n        \n          <div id=\"usersContent\" class=\"section hide\">\n            <div class=\"floating top\">\n              <button class=\"button green\" id=\"handle-users\" onclick=\"toggleCollapse('collapse-user')\" disabled><b id='toggle-user'>+</b></button>\n            </div>\n            <div class=\"collapsible\">\n              <div id=\"collapse-user\" class=\"collapse-content\">\n                <!-- Form per l'inserimento di un record nella tabella -->\n                <div id=\"insertForm\">\n                  <div class=\"form-row\">\n                    <div class=\"form-column\">\n                      <label for=\"tagCode\">Tag Code:</label>\n                      <span style=\"display: inline-flex;\">\n                        <input type=\"text\" id=\"tagCode\" name=\"tagCode\" placeholder=\"Tag Code\">\n                        \n                        <div class=\"floating inline\">\n                          <button id=\"get-tag\" class=\"button blue\" title=\"Read RFID tag code\"><b>@</b></button>\n                        \n                        </div>\n                      </span>\n                    </div>\n                    <div class=\"form-column\">\n                      <label for=\"name\">Name:</label>\n                      <input type=\"text\" id=\"name\" name=\"name\" placeholder=\"Name\">\n                    </div>\n                    <div class=\"form-column\">\n                      <label for=\"level\">Level:</label>\n                      <input type=\"text\" id=\"level\" name=\"level\" placeholder=\"Level\">\n                    </div>\n                   \n                  </div>\n                  <div class=\"form-row\">\n                    <div class=\"form-column\">\n                      <label for=\"username\">Username:</label>\n                      <input type=\"text\" id=\"username\" name=\"username\" placeholder=\"Username\">\n                    </div>\n                    <div class=\"form-column\">\n                      <label for=\"password\">Password:</label>\n                      <input type=\"password\" id=\"password\" name=\"password\" placeholder=\"password\">\n                    </div>\n                    <div class=\"form-column\">\n                      <label for=\"email\">Email:</label>\n                      <input type=\"email\" id=\"email\" name=\"email\" placeholder=\"Email\">\n                    </div>\n                  </div>\n                  \n                  <div class=\"but-row\">\n                    <button class=\"submit\" id=\"add-user\">Insert</button>\n                    <button class=\"submit\" id=\"delete-user\" disabled>Delete</button>\n                  </div>\n                </div>\n              </div>\n            </div>\n            \n            <div class=\"table-container\">\n              <div class=\"table-header\">\n                <div class=\"w05\">ID</div>\n                <div class=\"w20\">Username</div>\n                <div class=\"w05\">Role</div>\n                <div class=\"w30\">Name</div>\n                <div class=\"w30\">Email</div>\n                <div class=\"w10\">Tag Code</div>\n              </div>\n              <div id=\"usersTable\" class=\"table-body\">\n                 <!-- //// -->\n              </div>\n            </div>\n          </div>\n          \n        </div>\n      </div>\n    </div>\n    <footer class=\"footer\">\n      <div class=\"has-text-centered\">\n        <p> RFID Log &copy; 2024. All rights reserved.</p>\n        <a id=about target=_blank rel=noopener href=\"https://github.com/cotestatnt/esp-fs-webserver/\">Created with https://github.com/cotestatnt/esp-fs-webserver/</a>\n      </div>\n    </footer>\n  </div>\n\n  <script>\n    var userLevel = 0;\n   \n    // Callback function called on modal box close\n    var closeCb = function(){};\n  \n    // ID Element selector shorthands\n    var $ = function(el) {\n    \treturn document.getElementById(el);\n    };\n    \n    // Switch active page\n    function tabClick(el) {\n      const tabs = document.querySelectorAll('.tab');\n      const sections = document.querySelectorAll('.section');\n      tabs.forEach(t => t.classList.remove('active'));\n      sections.forEach(s => s.classList.add('hide'));\n      const target = this.dataset.target;\n      $(target).classList.remove('hide');\n      this.classList.remove('hide');\n      this.classList.add('active');\n    }\n    \n    // Toggle the collapsible user section \n    function toggleCollapse(id, keep) {\n      if (keep)\n        $(id).classList.add('show');\n      else\n        $(id).classList.toggle('show');\n        \n      const allRows = document.querySelectorAll('.table-body > div');\n      allRows.forEach(row => row.classList.remove('selected'));\n      const allInput = $('insertForm').querySelectorAll('input');\n      allInput.forEach(inp => inp.value = '');\n      $('add-user').innerHTML = 'Insert';\n      $('delete-user').disabled = true;\n      $('add-user').disabled = true;\n      const btnId = id.split('-')[1];\n      $('toggle-' + btnId).innerHTML = $(id).classList.contains('show') ? '-' : '+';\n    }\n      \n    // Get logs records\n    function getLogs(filter) {\n      var formData = new FormData();\n      if (filter != undefined) \n        formData.append(\"filter\", filter);\n      else\n        formData.append(\"filter\", \"\");\n      const option = {\n        method: 'POST',\n        body: formData\n      };\n      \n      const logs = $('logsTable');\n      fetch('/logs', option)\n      .then(response => {\n        if (!response.ok) {\n          throw new Error('Requesr error');\n        }\n        return response.json();\n      })\n      .then(data => {\n        logs.innerHTML = '';\n        data.forEach(log => {\n          const logEntry = document.createElement('div');\n          logEntry.innerHTML = \n           `<div class=\"w05\">${log.id}</div>\n            <div class=\"w30\">${new Date(log.epochTime * 1000).toLocaleString()}</div>\n            <div class=\"w30\">${log.username}</div>\n            <div class=\"w10\">${log.tagCode}</div>\n            <div class=\"w10\">${log.readerID}</div>`;\n          logs.appendChild(logEntry);\n        });\n        $('loader').style.display = \"none\";\n      })\n      .catch(error => {\n        console.error('Error:', error);\n        logs.innerHTML = '';\n        $('loader').style.display = \"none\";\n      });\n    }\n    \n    // Read the RFID code for current user\n    function getTagCode() {\n      fetch('/getCode')\n      .then(response => {\n        if (!response.ok) {\n          throw new Error('Request error');\n        }\n        return response.json();\n      })\n      .then(data => {\n        $('tagCode').value = data.tagCode;\n        $('add-user').disabled = false;\n      })\n      .catch(error => {\n        alert('Error:' + error);\n      });\n    }\n    \n    // Get list of registered users\n    function getUsers() {\n      const usersTable = $('usersTable');\n      fetch('/users')\n      .then(response => {\n        if (!response.ok) {\n          throw new Error('Requesr error');\n        }\n        return response.json();\n      })\n      .then(data => {\n        data.forEach(user => {\n          const userEntry = document.createElement('div');\n          userEntry.innerHTML = \n             `<div class=\"w05\" id=\"user\">${user.id}</div>\n              <div class=\"w20\" id=\"username\">${user.username}</div>\n              <div class=\"w05\" id=\"level\">${user.level}</div>\n              <div class=\"w30\" id=\"name\">${user.name}</div>\n              <div class=\"w30\" id=\"email\">${user.email}</div>\n              <div class=\"w10\" id=\"tagCode\">${user.tagCode}</div>`;\n          usersTable.appendChild(userEntry);\n          \n          userEntry.addEventListener('click', function(ev) {\n            const allRows = document.querySelectorAll('.table-body > div');\n            allRows.forEach(row => row.classList.remove('selected'));\n            \n            if(userLevel >= 5) {\n              toggleCollapse('collapse-user', true);\n              this.classList.add('selected');\n              const cols = Array.from(ev.target.parentNode.querySelectorAll('div')).map(el => {\n                return {\n                  id: el.id,\n                  value: el.innerHTML\n                };\n              });\n              if (cols.length === 6) {\n                cols.forEach(item => {\n                  $(item.id).value = item.value; \n                  $(item.id).addEventListener('input', function() {\n                    $('add-user').disabled = false;\n                  });\n                });\n              }\n              $('delete-user').disabled = false;\n              $('add-user').innerHTML = 'Update';\n            }\n          });\n        });\n      })\n      .catch(error => {\n        console.error('Error:', error);\n      });\n    }\n    \n    // Send command to MCU before the readings in order to handle properly logs record\n    function readTagCode() {\n      fetch('/waitCode')\n      .then(response => {\n        openModal('Read new TAG', \"<br>Please hold your tag close to the RFID reader\", getTagCode);\n      })\n    }\n    \n    // Insert or update a new user record\n    function sendUserForm(url) {\n      var formData = new FormData();\n      formData.append(\"username\", $('username').value);\n      formData.append(\"password\", $('password').value);\n      formData.append(\"name\", $('name').value);\n      formData.append(\"email\", $('email').value);    \n      formData.append(\"tagCode\", $('tagCode').value);\n      formData.append(\"level\", $('level').value);\n      const option = {\n        method: 'POST',\n        body: formData\n      };\n      fetch(url, option)\n        .then(response => {\n          if (!response.ok) {\n            throw new Error('Request error');\n          }\n          return response.text();\n        })\n        .then(result => {\n          openModal('Users', \"<br>New record inserted or updated\");\n        })\n        .catch(error => {\n          openModal('Error', error);\n        });\n    }\n    \n        // Send command to MCU before the readings in order to handle properly logs record\n    function deleteUser() {\n      $('delete-user').disabled = true;\n      sendUserForm('/delUser');\n    }\n    \n    // Show a message, if fn != undefinded run as callback on OK button press\n    function openModal(title, msg, fn) {\n      $('message-title').innerHTML = title;\n      $('message-body').innerHTML = msg;\n      $('modal-message').open = true;\n      $('main-box').style.filter = \"blur(3px)\";\n      if (typeof fn != 'undefined') {\n        closeCb = fn;\n        $('ok-modal').classList.remove('hide');\n      }\n      else\n        $('ok-modal').classList.add('hide');\n    }\n    \n    // Clode modal box\n    function closeModal(do_cb) {\n      $('modal-message').open = false;\n      $('main-box').style.filter = \"\";\n      if (typeof closeCb != 'undefined' && do_cb)\n        closeCb();\n    }\n    \n    function getRowTimestamp(id) {\n      // get last row\n      var divs = Array.from($(id).querySelectorAll('div:not([class^=\"w\"])'));\n      //Get timestamp value\n      const [day, month, year, hour, minute, second] = Array.from(divs[divs.length - 1].querySelectorAll('div'))[1].innerHTML.split(/[\\s,:\\/]+/);\n      return (new Date(year, month - 1, day, hour, minute, second)).getTime()/1000;\n    }\n    \n    function customFilter() {\n      let filter = ' WHERE ';\n      function includeAND(rule) {\n        return (filter != ' WHERE ') ? (' AND ' + rule) : rule;\n      }\n      if ( $('start').value)\n        filter += includeAND('epoch >= ' + (new Date($('start').value)) / 1000);\n      if ( $('end').value)\n        filter += includeAND('epoch <= ' + (new Date($('end').value)) / 1000);\n      if ( $('reader-log').value)\n        filter += includeAND('reader = ' + $('reader-log').value);\n      if ( $('username-log').value)\n        filter += includeAND(\"username = '\" + $('username-log').value + \"'\");\n      getLogs(filter);\n    }\n    \n    function exportCSV() {\n      // Create CSV data\n      const divs = Array.from($('logsTable').querySelectorAll('div:not([class^=\"w\"])'));\n      let table = [];\n      \n      divs.forEach(item => {\n        const cols = Array.from(item.querySelectorAll('div'));\n        let row = [];\n        cols.forEach(field => {\n          row.push(field.innerHTML)\n        });\n        table.push(row);\n      })\n  \n      let csvString = 'id, timestamp, username, tagCode, reader\\n'\n      for (let i = 1; i < table.length; i++) {\n        csvString += table[i].join(', ') + '\\n';\n      }\n      \n      // Download as CSV file\n      const blob = new Blob([csvString], { type: 'text/csv' });\n      const url = window.URL.createObjectURL(blob);\n      const a = document.createElement('a');\n      a.href = url;\n      a.download = 'data.csv';\n      document.body.appendChild(a);\n      a.click();\n      document.body.removeChild(a);\n      window.URL.revokeObjectURL(url);\n    }\n    \n    function getUserLevel() {\n      fetch('/userLevel?username=cotestatnt', {\n        method: 'GET'\n      })\n      .then(response => {\n        console.log(response);\n      })\n    }\n    \n    // Event listeners\n    $('add-user').addEventListener('click', function(event) {\n      event.preventDefault(); \n      $('add-user').disabled = true;\n      $('delete-user').disabled = true;\n      sendUserForm('/addUser');\n    });\n    \n    $('delete-user').addEventListener('click', function(event) {\n      event.preventDefault(); \n      openModal('Delete user', \"Do you really want to drop current user record?\", deleteUser);\n    });\n      \n    document.addEventListener('DOMContentLoaded', function() {   \n      $('loader').style.display = \"block\";\n      getUsers();\n      getLogs();\n      \n      $('logsTab').addEventListener('click', tabClick);\n      $('usersTab').addEventListener('click', tabClick);\n      $('get-tag').addEventListener('click', readTagCode);\n      $('filter-log').addEventListener('click', customFilter);\n      $('export-log').addEventListener('click', exportCSV);\n        \n      $('next-log').addEventListener('click', function(){\n        $('loader').style.display = \"block\";\n        var ts = getRowTimestamp('logsTable');\n        var filter = ` WHERE epoch <= ${ts}`;\n        getLogs(filter);\n      });\n      \n      $('prev-log').addEventListener('click', function(){\n        $('loader').style.display = \"block\";\n        var ts = getRowTimestamp('logsTable');\n        var filter = ` WHERE epoch >= ${ts}`;\n        getLogs(filter);\n      });\n      \n      // Check if user has admin level (>= 5)\n      var usernameValue = document.cookie.replace(/(?:(?:^|.*;\\s*)username\\s*=\\s*([^;]*).*$)|^.*$/, \"$1\");\n      userLevel = usernameValue.split(',')[1];\n      if(userLevel >= 5) {\n        console.log(usernameValue.split(',')[0], \"is admin\");\n        $('handle-users').disabled = false;\n        $('setup').href = '/setup';\n      }\n\n    });\n\n  </script>\n</body>\n</html>\n)string_literal\";\n"
  },
  {
    "path": "examples/mysqlRFID/mysqlRFID.ino",
    "content": "#include <FS.h>\n#include <LittleFS.h>\n\n#include <ArduinoJson.h>       // https://github.com/bblanchon/ArduinoJson\n#include <MySQL.h>             // https://github.com/cotestatnt/Arduino-MySQL\n#include <FSWebServer.h>  // https://github.com/cotestatnt/esp-fs-webserver\n#include <MFRC522v2.h>         // https://github.com/OSSLibraries/Arduino_MFRC522v2\n#include <MFRC522DriverSPI.h>\n#include <MFRC522DriverPinSimple.h>\n#include <MFRC522Debug.h>\n\n#define PIN_MISO    15\n#define PIN_MOSI    16\n#define PIN_SCLK    17\n#define PIN_CS      18\n\n// This set of variables can be updated using webpage http://<esp-ip-address>/setup\nString user = \"xxxxxxxxxxx\";                   // MySQL user login username\nString password = \"xxxxxxxxxxxx\";              // MySQL user login password\nString dbHost = \"192.168.1.1\";                 // MySQL hostname/URL\nString database = \"xxxxxxxxxxx\";               // Database name\nuint16_t dbPort = 3306;                        // MySQL host port\n\n// Var labels (in /setup webpage)\n#define MY_SQL_HOST \"MySQL Hostname\"\n#define MY_SQL_PORT \"MySQL Port\"\n#define MY_SQL_DB   \"MySQL Database name\"\n#define MY_SQL_USER \"MySQL Username\"\n#define MY_SQL_PASS \"MySQL Password\"\n#define RFID_READER_ID \"RFID Reader ID\"\n\nMFRC522DriverPinSimple ss_pin(PIN_CS); // Configurable, see typical pin layout above.\nMFRC522DriverSPI driver{ss_pin};       // Create SPI driver.\nMFRC522 mfrc522{driver};               // Create MFRC522 instance.\n\nuint32_t chipId = 0;\nbool addLogRecord = true;\n\n#include \"mysql_impl.h\"\n#include \"webserver_impl.h\"\n\nvoid setup() {\n  SPI.begin(PIN_SCLK, PIN_MISO, PIN_MOSI, PIN_CS);\n  Serial.begin(115200);\n  Serial.println(\"\\n\\n\\n\\nStarting ESP32 RFID gateway...\");\n  \n  mfrc522.PCD_Init();  // Init MFRC522 board.\n  MFRC522Debug::PCD_DumpVersionToSerial(mfrc522, Serial);\t// Show details of PCD - MFRC522 Card Reader details.\n  Serial.println(F(\"Scan PICC to see UID, SAK, type, and data blocks...\"));\n  \n  /* Init and start configuration webserver */\n  if (startWebServer()) {\n  \n    /* Init and start MySQL task */\n    if (connectToDatabase()) {\n      /*\n      * Check if working table exists and create if not exists.\n      * If not already present, also a default admin user will be created (admin, admin);\n      */ \n      if (!checkAndCreateTables()) {\n        Serial.println(\"Error! Tables not created properly\");\n      }\n    }\n    else {\n      Serial.println(\"\\nDatabase connection failed.\\nCheck your connection\");\n    }\n  }\n  \n  #if defined(ESP32)\n  for(byte i=0; i<17; i=i+8) {\n    chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;\n  }\n  #elif defined(ESP8266)\n  chipId = ESP.getChipId();\n  #endif\n  Serial.print(\"ESP Chip ID: \");\n  Serial.println(chipId);\n}\n\nvoid loop() {\n  myWebServer.run();   \n\n  delay(10);\n  \n  if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {\n    static uint32_t readTime = millis();\n    static uint64_t oldCode = 0;\n  \n    // The UID of an RFID tag can be up to 64bit long\n    uint64_t tagCode = 0;   \n  \n    // tagCode is swapped, but it doesn't matter We need only it's a unique number\n    for(byte i = 0; i < mfrc522.uid.size; i++) {\n      tagCode |= mfrc522.uid.uidByte[i] << (8*i);\n    }\n  \n    if ((tagCode != oldCode) || (millis() - readTime > 5000)) {\n      oldCode = tagCode;\n      readTime = millis();\n      Serial.printf(\"\\nTag code: %llu\\n\", tagCode);\n  \n      if (addLogRecord) {\n        DataQuery_t data;\n        if (queryExecute(data, \"SELECT username, level FROM users WHERE tag_code = %llu;\", tagCode)) {\n          sql.printResult(data, Serial);\n          String SQL = \"INSERT INTO logs (epoch, username, tag_code, reader) VALUES (UNIX_TIMESTAMP(), '\";\n          SQL += data.getRowValue(0, \"username\");  SQL += \"', \";\n          SQL += tagCode; SQL += \", \";\n          SQL += chipId; SQL += \");\";\n  \n          if (queryExecute(data, SQL.c_str())) {\n            Serial.printf(\"\\\"%s\\\" registered on reader %lu\\n\", data.getRowValue(0, \"username\"), chipId);\n          }\n        }\n      }\n    }\n  }\n}\n\n"
  },
  {
    "path": "examples/mysqlRFID/mysql_impl.h",
    "content": "#pragma once\n#include <Arduino.h>\n#include <MySQL.h>             //  https://github.com/cotestatnt/Arduino-MySQL\n#include <WiFiClient.h>\n\nextern String getSHA256(const char*);\n\n// MySQL client class\nWiFiClient client;\nMySQL sql(&client, dbHost.c_str(), dbPort);\n\n#define MAX_QUERY_LEN       512     // MAX query string length\n\ninline const char createUsersTable[] PROGMEM = R\"string_literal(\nCREATE TABLE %s (\n    id INT AUTO_INCREMENT PRIMARY KEY,\n    username VARCHAR(32) UNIQUE,\n    password VARCHAR(128),\n    name VARCHAR(64),\n    email VARCHAR(64),\n    tag_code BIGINT UNSIGNED UNIQUE,\n    level INT\n);\n)string_literal\";\n\ninline const char createLogTable[] PROGMEM = R\"string_literal(\nCREATE TABLE %s (\n    id INT AUTO_INCREMENT PRIMARY KEY,\n    epoch BIGINT,\n    username VARCHAR(32),\n    tag_code BIGINT UNSIGNED,\n    reader INT UNSIGNED\n);\n)string_literal\";\n\n\n// Insert or update a device (if ble_id already defined keep it's actual value)\ninline const char newUpdateUser[] PROGMEM = R\"string_literal(\nINSERT INTO users (username, password, name, email, tag_code, level)\nVALUES ('%s', '%s', '%s', '%s', %s, %s)\nON DUPLICATE KEY UPDATE\n  username = VALUES(username),\n  password = VALUES(password),\n  name = VALUES(name),\n  email = VALUES(email),\n  tag_code = VALUES(tag_code),\n  level = VALUES(level);\n)string_literal\";\n\n\n// Establish connection with MySQL database according to the variables defined (/setup webpage)\nbool connectToDatabase() {\n  if (sql.connected()) {\n    return true;\n  }\n  Serial.printf(\"\\nConnecting to MySQL server %s on DataBase %s...\\n\", dbHost.c_str(), database.c_str());\n  if (sql.connect(user.c_str(), password.c_str(), database.c_str())) {\n    delay(200);\n    return true;\n  }\n  Serial.println(\"Fails!\");\n  sql.disconnect();\n  return false;\n}\n\n\n// Variadic function that will execute the query selected with passed parameters\nbool queryExecute(DataQuery_t& data, const char* queryStr, ...) {\n  if (connectToDatabase()) {\n    char buf[MAX_QUERY_LEN];\n    va_list args;\n    va_start (args, queryStr);\n    vsnprintf (buf, sizeof(buf), queryStr, args);\n    va_end (args);\n\n    // Execute the query\n    Serial.printf(\"Execute SQL query: %s\\n\", buf);\n    return sql.query(data, buf);\n  }\n  return false;\n}\n\nbool checkAndCreateTables() {\n  // Create tables if not exists\n  Serial.println(\"\\nCreate table if not exists\");\n  DataQuery_t data;\n  if (!queryExecute(data, createUsersTable, \"users\")) {\n    if (strcmp(sql.getLastSQLSTATE(), \"42S01\") != 0)\n      return false;\n  }\n\n  if (!queryExecute(data, createLogTable, \"logs\")) {\n    if (strcmp(sql.getLastSQLSTATE(), \"42S01\") != 0)\n      return false;\n  }\n\n  String hashed = getSHA256(\"admin\");\n  if (queryExecute(data, newUpdateUser, \"admin\", hashed.c_str(), \"Update password!\", \"\", \"0\", \"5\")) {\n    Serial.println(\"admin@admin user created\");\n  }\n  return true;\n}\n\n"
  },
  {
    "path": "examples/mysqlRFID/partitions.csv",
    "content": "# Name,   Type, SubType, Offset,  Size, Flags\nnvs,      data, nvs,     0x9000,  0x5000,\notadata,  data, ota,     0xe000,  0x2000,\napp0,     app,  ota_0,   0x10000, 0x140000,\napp1,     app,  ota_1,   0x150000,0x140000,\nspiffs,   data, spiffs,  0x290000,0x160000,\ncoredump, data, coredump,0x3F0000,0x10000,\n"
  },
  {
    "path": "examples/mysqlRFID/platformio.ini",
    "content": "; PlatformIO Project Configuration File\n;\n;   Build options: build flags, source filter\n;   Upload options: custom upload port, speed and extra flags\n;   Library options: dependencies, extra library storages\n;   Advanced options: extra scripting\n;\n; Please visit documentation for the other options and examples\n; https://docs.platformio.org/page/projectconf.html\n\n[platformio]\nsrc_dir = .\n\n[env:esp32-s3-devkitc1-n4r2]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32-s3-devkitc1-n4r2\nframework = arduino\nupload_speed = 921600\nboard_build.partitions = partitions.csv\nlib_ldf_mode = chain\nlib_ignore = examples\n\nlib_extra_dirs = \n    ~/Documents/Arduino/libraries/esp-fs-webserver\n    ~/Documents/Arduino/libraries/MySQL_Query_Client\n    ~/Documents/Arduino/libraries/RFID_MFRC522v2\n    ~/Documents/Arduino/libraries/ArduinoJson\n\nmonitor_speed = 115200\n\n[env:esp32dev]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32dev\nframework = arduino\nupload_speed = 921600\nboard_build.partitions = partitions.csv\n\n[env:esp8266-nodemcuv2]\nplatform = espressif8266\nboard = nodemcuv2\nframework = arduino\nupload_speed = 921600\nboard_build.partitions = partitions.csv\n"
  },
  {
    "path": "examples/mysqlRFID/webserver_impl.h",
    "content": "#pragma once\n#include <Arduino.h>\n#include <LittleFS.h>\n\n#include <ArduinoJson.h>       // https://github.com/bblanchon/ArduinoJson\n#include <MySQL.h>             // https://github.com/cotestatnt/Arduino-MySQL\n#include <FSWebServer.h>  // https://github.com/cotestatnt/esp-fs-webserver\n#include \"mbedtls/md.h\"\n\n#include \"html_flash_files.h\"\nextern MySQL sql;\nextern bool queryExecute(DataQuery_t&, const char*, ...);\n\n// Webserver class\nFSWebServer myWebServer(LittleFS, 80, \"esp32rfid\");\n\nint getUserLevel(const String& user, const String&  hash) {\n  DataQuery_t data;\n  if (queryExecute(data, \"SELECT password, level FROM users WHERE username = '%s';\", user)) {\n    sql.printResult(data, Serial);\n    if (hash.equals(data.getRowValue(0, \"password\")))\n      return atoi(data.getRowValue(0, \"level\"));\n  }\n  return 0;\n}\n\n\nString getSHA256(const char* payload) {\n  String hashed = \"\";\n  byte shaResult[32];\n  mbedtls_md_context_t ctx;\n  const size_t payloadLength = strlen(payload);         \n  mbedtls_md_init(&ctx);\n  mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 0);\n  mbedtls_md_starts(&ctx);\n  mbedtls_md_update(&ctx, (const unsigned char *) payload, payloadLength);\n  mbedtls_md_finish(&ctx, shaResult);\n  mbedtls_md_free(&ctx);\n  for(int i= 0; i< sizeof(shaResult); i++){\n    char str[3];\n    sprintf(str, \"%02x\", (int)shaResult[i]);\n    hashed += str;\n  }\n  return hashed;\n}\n\n\nvoid handleGetLogs() {\n\n  String filter;\n  if(myWebServer.hasArg(\"filter\")) {\n    filter = myWebServer.arg(\"firstname\");\n  }\n\n  String SQL = \"SELECT * FROM logs \";\n  if (filter.length()) {\n    SQL += filter;\n  }\n  SQL += \" ORDER BY epoch DESC LIMIT 30;\";\n  Serial.println(SQL);\n  delay(100);\n\n  DataQuery_t data;\n  if (queryExecute(data, SQL.c_str())) {\n    sql.printResult(data, Serial);\n\n    JsonDocument doc;\n    JsonArray array = doc.to<JsonArray>();;\n    for (Record_t &row : data.records) {\n      JsonObject user = array.add<JsonObject>();\n      user[\"id\"] = row.record[0];\n      user[\"epochTime\"] = row.record[1];\n      user[\"username\"] = row.record[2];\n      user[\"tagCode\"] = row.record[3];\n      user[\"readerID\"] = row.record[4];\n    }\n    String json;\n    serializeJsonPretty(doc, json);\n    myWebServer.send(200, \"application/json\", json);\n    return;\n  }\n  myWebServer.send(500, \"text/plain\", sql.getLastError());\n}\n\nvoid handleGetUsers() {\n  DataQuery_t data;\n  if (queryExecute(data, \"SELECT id, username, name, email, tag_code, level FROM users\")) {\n    sql.printResult(data, Serial);\n\n    JsonDocument doc;\n    JsonArray array = doc.to<JsonArray>();\n    for (Record_t &row : data.records) {\n      JsonObject user = array.add<JsonObject>();\n      user[\"id\"] = row.record[0];\n      user[\"username\"] = row.record[1];\n      user[\"name\"] = row.record[2];\n      user[\"email\"] = row.record[3];\n      user[\"tagCode\"] = row.record[4];\n      user[\"level\"] = row.record[5];\n    }\n    String json;\n    serializeJsonPretty(doc, json);\n    myWebServer.send(200, \"application/json\", json);\n    return;\n  }\n  myWebServer.send(500, \"text/plain\", sql.getLastError());\n}\n\nvoid handleNewUser() {\n  String user = myWebServer.arg(\"username\");\n  String name = myWebServer.arg(\"name\");\n  String email = myWebServer.arg(\"email\");\n  String tagCode = myWebServer.arg(\"tagCode\");\n  String level = myWebServer.arg(\"level\");\n  String hashedPassword = getSHA256(myWebServer.arg(\"password\").c_str());\n  \n  DataQuery_t data;\n  if (queryExecute(data, newUpdateUser,\n    myWebServer.arg(\"username\").c_str(), hashedPassword.c_str(), myWebServer.arg(\"name\").c_str(),\n    myWebServer.arg(\"email\").c_str(), myWebServer.arg(\"tagCode\").c_str(), myWebServer.arg(\"level\").c_str()))\n  {\n    myWebServer.send(200, \"text/plain\", \"OK\");\n    return;\n  }\n  myWebServer.send(500, \"text/plain\", sql.getLastError());\n}\n\nvoid handleRemoveUser() {\n  DataQuery_t data;\n  if (queryExecute(data, \"DELETE FROM users WHERE username = '%s';\", myWebServer.arg(\"username\").c_str())) {\n    myWebServer.send(200, \"text/plain\", \"OK\");\n    return;\n  }\n  myWebServer.send(500, \"text/plain\", sql.getLastError());\n}\n\nvoid handleGetCode() {\n  uint32_t timeout = millis();\n\n  while (true) {\n    if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {\n      uint64_t tagCode = 0;\n\n      // tagCode is swapped, but it doesn't matter We need only it's a unique number\n      for(byte i = 0; i < mfrc522.uid.size; i++) {\n        tagCode |= mfrc522.uid.uidByte[i] << (8*i);\n      }\n      \n      // With 8 byte TAG code, the result integer could be too large since JavaScript \n      // uses 64-bit floating-point numbers (IEEE 754), which have a maximum precision of 2^53 - 1 \n      String result = \"{\\\"tagCode\\\": \\\"\";\n      result += String(tagCode);  \n      result += \"\\\"}\";\n\n      Serial.printf(\"Tag code: 0x%llX\", tagCode);\n      myWebServer.send(200, \"application/json\", result);\n      addLogRecord = true;\n      return;\n    }\n\n    if (millis() - timeout > 5000) {\n      myWebServer.send(500, \"application/json\", \"{\\\"error\\\": \\\"timeout\\\"}\");\n      addLogRecord = true;\n      return;\n    }\n  }\n}\n\n// This handler will be called from login page to check password\nvoid handleCheckHash() {\n\n  // Even if user con login, only user with level >= 5 can edit users table\n  if (getUserLevel(myWebServer.arg(\"username\"), myWebServer.arg(\"hash\"))) {\n    myWebServer.send(200, \"text/plain\", \"OK\");\n  }\n  else {\n    myWebServer.send(401, \"text/plain\", \"Wrong password\");\n  }\n}\n\n// This handler will be called from login page on login succesfull\nvoid handleMainPage() {\n  // Check again user and password to avoid direct page loading\n  int level = getUserLevel(myWebServer.arg(\"username\"), myWebServer.arg(\"hash\"));\n  if (level) {\n    // Even if any user con login succesfully, only user with level >= 5 can edit users table\n    // Username and user level is set here using cookie.\n    String cookie = \"username=\" ;\n    cookie += myWebServer.arg(\"username\");\n    cookie += \",\";  cookie += level;  cookie += \"; Path=/\";\n    // For the new API, send response directly with headers\n    myWebServer.sendHeader(\"Set-Cookie\", cookie);\n    myWebServer.send_P(200, \"text/html\", (const char*)index_htm, sizeof(index_htm));\n  } \n  else {\n    myWebServer.send(401, \"text/plain\", \"Wrong password\");\n  }\n}\n\n\n////////////////////  Load application options from filesystem  ////////////////////\nbool loadOptions() {\n  if (LittleFS.exists(myWebServer.getConfiFileName())) {\n    myWebServer.getOptionValue(MY_SQL_HOST, dbHost);\n    myWebServer.getOptionValue(MY_SQL_PORT, dbPort);\n    myWebServer.getOptionValue(MY_SQL_DB, database);\n    myWebServer.getOptionValue(MY_SQL_USER, user);\n    myWebServer.getOptionValue(MY_SQL_PASS, password);\n    myWebServer.closeSetupConfiguration();\n    Serial.printf(MY_SQL_HOST \": %s\\n\", dbHost.c_str());\n    Serial.printf(MY_SQL_PORT \": %d\\n\", dbPort);\n    Serial.printf(MY_SQL_DB \": %s\\n\", database.c_str());\n    Serial.printf(MY_SQL_USER \": %s\\n\", user.c_str());\n    Serial.printf(MY_SQL_PASS \": %s\\n\", password.c_str());\n    return true;\n  }\n  else\n    Serial.printf(\"File \\\"%s\\\" not exist\\n\", myWebServer.getConfiFileName());\n  return false;\n}\n\n////////////////////////////////  Filesystem  /////////////////////////////////////////\n\n// Configure and start webserver\nbool startWebServer(bool clear = false) {\n  bool connected = false;\n  // FILESYSTEM INIT\n  if (!LittleFS.begin()) {\n    Serial.println(\"ERROR on mounting filesystem. It will be formmatted!\");\n    LittleFS.format();\n    ESP.restart();\n  }\n\n  if (clear) {\n    LittleFS.remove(myWebServer.getConfiFileName());\n  }\n\n  // Load configuration (if not present, default will be created when webserver will start)\n  Serial.println(\"Load application otions:\");\n  if (!loadOptions()) Serial.println(\"Error!! Options NOT loaded!\");\n  Serial.println();\n\n  // Try to connect to WiFi (will start AP if not connected after timeout)\n  if (!myWebServer.startWiFi(20000)) {\n    Serial.println(\"\\nWiFi not connected! Starting AP mode...\");\n    myWebServer.startCaptivePortal(\"ESP32_LOGGER\", \"\", \"/setup\");\n  }\n  else \n    connected = true;\n\n  // Configure /setup page and start Web Server\n  myWebServer.addOptionBox(\"MySQL setup\");\n  myWebServer.addOption(MY_SQL_HOST, dbHost);\n  myWebServer.addOption(MY_SQL_PORT, dbPort);\n  myWebServer.addOption(MY_SQL_DB, database);\n  myWebServer.addOption(MY_SQL_USER, user);\n  myWebServer.addOption(MY_SQL_PASS, password);\n\n  // Add endpoints request handlers\n  myWebServer.on(\"/logs\", HTTP_ANY, handleGetLogs);\n  myWebServer.on(\"/users\", HTTP_GET, handleGetUsers);\n  myWebServer.on(\"/addUser\", HTTP_POST, handleNewUser);\n  myWebServer.on(\"/deleUser\", HTTP_POST, handleRemoveUser);\n  myWebServer.on(\"/getCode\", HTTP_GET, handleGetCode);\n  myWebServer.on(\"/waitCode\", HTTP_GET, [](){\n    addLogRecord = false; \n    myWebServer.send(200, \"text/plain\", \"OK\");\n  });\n\n  /* \n  * To avoid ugly and basic login prompt avalaible with \"stardard\" DIGEST_AUTH\n  * let's use a custom login web page (from flash literal string). This web page\n  * will send a POST request to /rfid enpoint passing username and password SHA256 hash\n  */\n  myWebServer.on(\"/\", HTTP_ANY, [](){\n    myWebServer.send_P(200, \"text/html\", (const char*)login_htm, sizeof(login_htm));\n  });\n  myWebServer.on(\"/login\", HTTP_ANY, [](){\n    myWebServer.send_P(200, \"text/html\", (const char*)login_htm, sizeof(login_htm));\n  });\n  myWebServer.on(\"/rfid\", HTTP_POST, handleCheckHash);\n  /*\n  * If client calculated password SHA256 hash string match with the user DB stored\n  * we can serve the /rfid web page (from flash literal string, same as login page)\n  */\n  myWebServer.on(\"/rfid\", HTTP_GET, handleMainPage);\n\n  // To enable add/edit/delete buttons, user must be admin (level >= 5)\n  myWebServer.on(\"/userLevel\", HTTP_GET, [](){\n    DataQuery_t data;\n    if (queryExecute(data, \"SELECT password, level FROM users WHERE username = '%s';\", myWebServer.arg(\"username\"))) {\n      String cookie = \"user_level=\" + String(data.getRowValue(0, \"level\")) + \"; Path=/\";\n      myWebServer.sendHeader(\"Set-Cookie\", cookie);\n      myWebServer.send(200, \"text/plain\", \"OK\");\n    }\n  });\n  \n  // Enable ACE FS file web editor and add FS info callback function\n  myWebServer.enableFsCodeEditor();\n\n  // Start the webserver\n  myWebServer.begin();\n  Serial.print(\"\\n\\nESP Web Server started on IP Address: \");\n  Serial.println(myWebServer.getServerIP());\n  Serial.println(\n    \"Open /setup page to configure optional parameters.\\n\"\n    \"Open /edit page to view, edit or upload example or your custom webserver source files.\"\n  );\n\n  return connected;\n}"
  },
  {
    "path": "examples/remoteOTA/data/index.htm",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\">\n  <title>ESP Index</title>\n\n  <link rel=\"stylesheet\" href=\"https://unpkg.com/@picocss/pico@latest/css/pico.min.css\">\n  <style>\n    #check-fw {max-width: 50%;}\n    .container { max-width: 700px;}\n  </style>\n\n</head>\n<body>\n   <main class=\"container\">\n      <article class=\"grid\">\n        <div>\n          <hgroup>\n            <h1>ESP FS WebServer - Remote OTA update</h1>\n          </hgroup>\n\n          <section id=\"loading\">\n            <button id=\"check-fw\" aria-busy=\"false\">Check for New firmware</button>\n            <article id=\"loading-fw\" aria-busy=\"false\">\n              <div id=\"fw-version\" >Current firmware version: <span id=\"current-fw\"></span></div>\n              <div id=\"update-result\"></div>\n            </article>\n          </section>\n\n          <label for=\"remember\">\n            <input type=\"checkbox\" role=\"switch\" id=\"toggle-led\" name=\"toggle-led\" >\n            Toggle built-in LED\n          </label>\n          <br>\n          <p id=\"esp-response\"></p>\n          <img src=\"espressif.jpg\" style=\"opacity: 0.75\"/>\n\n        </div>\n      </article>\n    </main>\n\n\n  <script type=\"text/javascript\">\n    var actual_version = '0.0.0';\n    var firmware_info = 'https://github.com/cotestatnt/async-esp-fs-webserver';\n\n    // This function fetch the GET request to the ESP webserver\n    function toggleLed() {\n      const pars = new URLSearchParams({\n        val:  document.getElementById('toggle-led').checked ? '1' : '0'\n      });\n\n      fetch('/led?' + pars )                // Do the request\n      .then(response => response.text())    // Parse the response\n      .then(text => {                       // DO something with response\n        console.log(text);\n        document.getElementById('esp-response').innerHTML = text + ' <i>(Builtin LED is ON with a low signal)</i>';\n      });\n    }\n\n    // v1 > v2 -> 1; v1 = v2 -> 0; v1 < v2 = -1\n    function compareVersion (v1, v2) {\n      if (typeof v1 !== 'string') return false;\n      if (typeof v2 !== 'string') return false;\n      v1 = v1.split('.');\n      v2 = v2.split('.');\n      const k = Math.min(v1.length, v2.length);\n      for (let i = 0; i < k; ++ i) {\n          v1[i] = parseInt(v1[i], 10);\n          v2[i] = parseInt(v2[i], 10);\n          if (v1[i] > v2[i]) return 1;\n          if (v1[i] < v2[i]) return -1;\n      }\n      return v1.length == v2.length ? 0: (v1.length < v2.length ? -1 : 1);\n    }\n\n    // Check if a new firmware is avalaible\n    function checkNewFirmware () {\n      document.getElementById('check-fw').setAttribute('aria-busy', true);\n      document.getElementById('current-fw').setAttribute('aria-busy', true);\n      console.log(firmware_info);\n      fetch(firmware_info)\n      .then(response => response.json())\n      .then(obj => {\n        if (compareVersion(obj.version, actual_version) === 1) {\n          // Prompt the user for confirmation\n          if (confirm(`Firmware version ${obj.version} available.\\n Are you sure you want to proceed with firmware update?`)) {\n            updateFirmware(obj);\n          }\n          else {\n            document.getElementById('check-fw').setAttribute('aria-busy', false);\n            document.getElementById('current-fw').setAttribute('aria-busy', false);\n          }\n        }\n        else {\n          alert(\"No new version available\");\n          document.getElementById('check-fw').setAttribute('aria-busy', false);\n          document.getElementById('current-fw').setAttribute('aria-busy', false);\n        }\n      });\n    }\n\n    // Get actual firmeware version from ESP\n    function getFirmwareVersion () {\n      fetch('/version')                // Do the request\n      .then(response => response.json())    // Parse the response\n      .then(obj => {                        // Do something with response\n        document.getElementById(\"current-fw\").innerHTML = obj.version;\n        actual_version = obj.version;\n        firmware_info = obj.newFirmwareInfoJSON;\n      });\n    }\n\n    // Procede with firmware update (after user confirmation)\n    function updateFirmware (obj) {\n      const pars = new URLSearchParams({\n        version: obj.version,\n        url: obj.raw_url\n      });\n\n      fetch('/firmware_update?' + pars )\n      .then(response => response.text())\n      .then(text => {\n        console.log(text);\n        document.getElementById(\"update-result\").innerHTML = text;\n      });\n    }\n\n    // Add event listener to the HTML elements\n    document.getElementById('toggle-led').addEventListener('change', toggleLed );\n    document.getElementById('check-fw').addEventListener('click', checkNewFirmware );\n    document.addEventListener(\"DOMContentLoaded\", getFirmwareVersion);\n  </script>\n\n</body>\n</html>\n"
  },
  {
    "path": "examples/remoteOTA/fw-esp32/readme.md",
    "content": "\n"
  },
  {
    "path": "examples/remoteOTA/fw-esp8266/readme.md",
    "content": "\n"
  },
  {
    "path": "examples/remoteOTA/partitions.csv",
    "content": "# Name,   Type, SubType, Offset,  Size, Flags\nnvs,      data, nvs,     0x9000,  0x5000,\notadata,  data, ota,     0xe000,  0x2000,\napp0,     app,  ota_0,   0x10000, 0x140000,\napp1,     app,  ota_1,   0x150000,0x140000,\nspiffs,   data, spiffs,  0x290000,0x160000,\ncoredump, data, coredump,0x3F0000,0x10000,\n"
  },
  {
    "path": "examples/remoteOTA/platformio.ini",
    "content": "; PlatformIO Project Configuration File\n;\n;   Build options: build flags, source filter\n;   Upload options: custom upload port, speed and extra flags\n;   Library options: dependencies, extra library storages\n;   Advanced options: extra scripting\n;\n; Please visit documentation for the other options and examples\n; https://docs.platformio.org/page/projectconf.html\n\n[platformio]\nsrc_dir = .\n\n[env:esp32-s3-devkitc1-n4r2]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32-s3-devkitc1-n4r2\nframework = arduino\nupload_speed = 921600\nboard_build.partitions = partitions.csv\nlib_extra_dirs = ../../\nlib_ignore = pio_examples\n\n[env:esp32dev]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32dev\nframework = arduino\nupload_speed = 921600\nboard_build.partitions = partitions.csv\nlib_extra_dirs = ../../\nlib_ignore = pio_examples\n\n[env:esp8266-nodemcuv2]\nplatform = espressif8266\nboard = nodemcuv2\nframework = arduino\nupload_speed = 921600\nboard_build.partitions = partitions.csv\nlib_extra_dirs = ../../\nlib_ignore = pio_examples\n"
  },
  {
    "path": "examples/remoteOTA/remoteOTA.ino",
    "content": "#ifdef ESP8266\r\n  #include <ESP8266HTTPClient.h>\r\n  #include <ESP8266httpUpdate.h>\r\n#elif defined(ESP32)\r\n  #include <WiFiClientSecure.h>\r\n  #include <HTTPClient.h>\r\n  #include <HTTPUpdate.h>\r\n#endif\r\n#include <EEPROM.h>            // For storing the firmware version\r\n\r\n#include <FS.h>\r\n#include <LittleFS.h>\r\n#include <FSWebServer.h>   // https://github.com/cotestatnt/esp-fs-webserver/\r\n\r\n#define FILESYSTEM LittleFS\r\nFSWebServer server(FILESYSTEM, 80);\r\n\r\n#ifndef LED_BUILTIN\r\n#define LED_BUILTIN 2\r\n#endif\r\n\r\n// In order to set SSID and password open the /setup webserver page\r\n// const char* ssid;\r\n// const char* password;\r\n\r\nuint8_t ledPin = LED_BUILTIN;\r\nbool apMode = false;\r\n\r\n#ifdef ESP8266\r\nString fimwareInfo = \"https://raw.githubusercontent.com/cotestatnt/async-esp-fs-webserver/master/examples/remoteOTA/version-esp8266.json\";\r\n#elif defined(ESP32)\r\nString fimwareInfo = \"https://raw.githubusercontent.com/cotestatnt/async-esp-fs-webserver/master/examples/remoteOTA/version-esp32.json\";\r\n#endif\r\n\r\nchar fw_version[10] = {\"0.0.0\"};\r\n\r\n//////////////////////////////  Firmware update /////////////////////////////////////////\r\nvoid doUpdate(const char* url, const char* version) {\r\n\r\n  #ifdef ESP8266\r\n  #define UPDATER ESPhttpUpdate\r\n  #elif defined(ESP32)\r\n  #define UPDATER httpUpdate\r\n  #if ESP_ARDUINO_VERSION_MAJOR > 2\r\n  esp_task_wdt_config_t twdt_config = {\r\n      .timeout_ms = 15*1000,\r\n      .idle_core_mask = (1 << portNUM_PROCESSORS) - 1,    // Bitmask of all cores\r\n      .trigger_panic = false,\r\n  };\r\n  ESP_ERROR_CHECK(esp_task_wdt_reconfigure(&twdt_config));\r\n  #else\r\n  ESP_ERROR_CHECK(esp_task_wdt_init(15, 0));\r\n  #endif\r\n  #endif\r\n\r\n  // onProgress handling is missing with ESP32 library\r\n  UPDATER.onProgress([](int cur, int total){\r\n    static uint32_t sendT;\r\n    if(millis() - sendT > 1000){\r\n      sendT = millis();\r\n      Serial.printf(\"Updating %d of %d bytes...\\n\", cur, total);\r\n    }\r\n  });\r\n\r\n  WiFiClientSecure client;\r\n  client.setInsecure();\r\n  UPDATER.rebootOnUpdate(false);\r\n  UPDATER.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);\r\n  UPDATER.setLedPin(LED_BUILTIN, LOW);\r\n  t_httpUpdate_return ret = UPDATER.update(client, url, fw_version);\r\n  client.stop();\r\n\r\n  switch (ret) {\r\n    case HTTP_UPDATE_FAILED:\r\n      Serial.printf(\"HTTP_UPDATE_FAILED Error (%d): %s\\n\", UPDATER.getLastError(), UPDATER.getLastErrorString().c_str());\r\n      break;\r\n\r\n    case HTTP_UPDATE_NO_UPDATES:\r\n      Serial.println(\"HTTP_UPDATE_NO_UPDATES\");\r\n      break;\r\n\r\n    case HTTP_UPDATE_OK:\r\n      Serial.println(\"HTTP_UPDATE_OK\");\r\n      strcpy(fw_version, version);\r\n      EEPROM.put(0, fw_version);\r\n      EEPROM.commit();\r\n      Serial.print(\"System will be restarted with the new version \");\r\n      Serial.println(fw_version);\r\n      delay(1000);\r\n      ESP.restart();\r\n      break;\r\n  }\r\n\r\n\r\n}\r\n\r\n////////////////////////////////  Filesystem  /////////////////////////////////////////\r\nvoid listDir(fs::FS &fs, const char * dirname, uint8_t levels){\r\n  Serial.printf(\"\\nListing directory: %s\\n\", dirname);\r\n  File root = fs.open(dirname, \"r\");\r\n  if(!root){\r\n    Serial.println(\"- failed to open directory\");\r\n    return;\r\n  }\r\n  if(!root.isDirectory()){\r\n    Serial.println(\" - not a directory\");\r\n    return;\r\n  }\r\n  File file = root.openNextFile();\r\n  while(file){\r\n    if(file.isDirectory()){\r\n      if(levels){\r\n        #ifdef ESP8266\r\n        String path = file.fullName();\r\n        path.replace(file.name(), \"\");\r\n        #elif defined(ESP32)\r\n        String path = file.path();\r\n        #endif\r\n        listDir(fs, path.c_str(), levels -1);\r\n      }\r\n    } else {\r\n      Serial.printf(\"|__ FILE: %s (%d bytes)\\n\",file.name(), file.size());\r\n    }\r\n    file = root.openNextFile();\r\n  }\r\n}\r\n\r\nbool startFilesystem() {\r\n  if (FILESYSTEM.begin()){\r\n    listDir(FILESYSTEM, \"/\", 1);\r\n    return true;\r\n  }\r\n  else {\r\n    Serial.println(\"ERROR on mounting filesystem. It will be reformatted!\");\r\n    FILESYSTEM.format();\r\n    ESP.restart();\r\n  }\r\n  return false;\r\n}\r\n\r\n\r\n////////////////////////////  HTTP Request Handlers  ////////////////////////////////////\r\nvoid handleLed() {\r\n  // http://xxx.xxx.xxx.xxx/led?val=1\r\n  if(server.hasArg(\"val\")) {\r\n    int value = server.arg(\"val\").toInt();\r\n    digitalWrite(ledPin, value);\r\n  }\r\n\r\n  String reply = \"LED is now \";\r\n  reply += digitalRead(ledPin) ? \"OFF\" : \"ON\";\r\n  server.send(200, \"text/plain\", reply);\r\n}\r\n\r\n/* Handle the update request from client.\r\n* The web page will check if is it necessary or not checking the actual version.\r\n* Info about firmware as version and remote url, are stored in \"version.json\" file\r\n*\r\n* Using this example, the correct workflow for deploying a new firmware version is:\r\n  - upload the new firmware.bin compiled on your web space (in this example Github is used)\r\n  - update the \"version.json\" file with the new version number and the address of the binary file\r\n  - on the update webpage, press the \"UPDATE\" button.\r\n*/\r\nvoid handleUpdate() {\r\n  if(server.hasArg(\"version\") && server.hasArg(\"url\")) {\r\n    const char* new_version = server.arg(\"version\").c_str();\r\n    const char* url = server.arg(\"url\").c_str();\r\n    String reply = \"Firmware is going to be updated to version \";\r\n    reply += new_version;\r\n    reply += \" from remote address \";\r\n    reply += url;\r\n    reply += \"<br>Wait 10-20 seconds and then reload page.\";\r\n    server.send(200, \"text/plain\", reply );\r\n    Serial.println(reply);\r\n    doUpdate(url, new_version);\r\n  }\r\n}\r\n\r\n///////////////////////////////////  SETUP  ///////////////////////////////////////\r\nvoid setup(){\r\n  pinMode(LED_BUILTIN, OUTPUT);\r\n  Serial.begin(115200);\r\n  EEPROM.begin(128);\r\n\r\n  // FILESYSTEM INIT\r\n  startFilesystem();\r\n  \r\n  // Try to connect to WiFi (will start AP if not connected after timeout)\r\n  if (!server.startWiFi(10000)) {\r\n    Serial.println(\"\\nWiFi not connected! Starting AP mode...\");\r\n    server.startCaptivePortal(\"ESP_AP\", \"123456789\", \"/setup\");\r\n  }\r\n\r\n  /*\r\n  * Getting FS info (total and free bytes) is strictly related to\r\n  * filesystem library used (LittleFS, FFat, SPIFFS etc etc) and ESP framework\r\n  */\r\n  #ifdef ESP32\r\n  server.setFsInfoCallback([](fsInfo_t* fsInfo) {\r\n    fsInfo->fsName = \"LittleFS\";\r\n    fsInfo->totalBytes = LittleFS.totalBytes();\r\n    fsInfo->usedBytes = LittleFS.usedBytes();\r\n  });\r\n  #endif\r\n\r\n  // Enable ACE FS file web editor and add FS info callback function\r\n  server.enableFsCodeEditor();\r\n\r\n  // Add custom handlers to webserver\r\n  server.on(\"/led\", HTTP_GET, handleLed);\r\n  server.on(\"/firmware_update\", HTTP_GET, handleUpdate);\r\n\r\n  // Add handler as lambda function (just to show a different method)\r\n  server.on(\"/version\", HTTP_GET, []() {\r\n    server.getOptionValue(\"New firmware JSON\", fimwareInfo);\r\n\r\n    EEPROM.get(0, fw_version);\r\n    if (fw_version[0] == 0xFF) // Still not stored in EEPROM (first run)\r\n      strcpy(fw_version, \"0.0.0\");\r\n    String reply = \"{\\\"version\\\":\\\"\";\r\n    reply += fw_version;\r\n    reply += \"\\\", \\\"newFirmwareInfoJSON\\\":\\\"\";\r\n    reply += fimwareInfo;\r\n    reply += \"\\\"}\";\r\n    // Send to client actual firmware version and address where to check if new firmware available\r\n    server.send(200, \"text/json\", reply);\r\n  });\r\n\r\n  // Configure /setup page and start Web Server\r\n  server.addOptionBox(\"Remote Update\");\r\n  server.addOption(\"New firmware JSON\", fimwareInfo);\r\n\r\n  // Start server with built-in websocket event handler\r\n  server.begin();\r\n  Serial.print(F(\"ESP Web Server started on IP Address: \"));\r\n  Serial.println(server.getServerIP());\r\n  Serial.println(F(\r\n    \"This is \\\"remoteOTA.ino\\\" example.\\n\"\r\n    \"Open /setup page to configure optional parameters.\\n\"\r\n    \"Open /edit page to view, edit or upload example or your custom webserver source files.\"\r\n  ));\r\n}\r\n\r\n///////////////////////////////////  LOOP  ///////////////////////////////////////\r\nvoid loop() {\r\n  server.handleClient();\r\n  if (server.isAccessPointMode())\r\n    server.updateDNS();\r\n  \r\n  // This delay is required in order to avoid loopTask() WDT reset on ESP32\r\n  delay(1);  \r\n}"
  },
  {
    "path": "examples/remoteOTA/version-esp32.json",
    "content": "{\n    \"version\": \"1.0.1\",\n    \"raw_url\": \"https://github.com/cotestatnt/async-esp-fs-webserver/raw/master/examples/remoteOTA/fw-esp32/firmware.bin\"\n}"
  },
  {
    "path": "examples/remoteOTA/version-esp8266.json",
    "content": "{\n    \"version\": \"1.0.1\",\n    \"raw_url\": \"https://github.com/cotestatnt/async-esp-fs-webserver/raw/master/examples/remoteOTA/fw-esp8266/firmware.bin\"\n}"
  },
  {
    "path": "examples/simpleServer/data/index.htm",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\">\n  <title>ESP simpleServer.ino</title>\n  \n  <!--Pico - Minimal CSS Framework for semantic info: HTML https://picocss.com/ -->\n  <link rel=\"stylesheet\" type=\"text/css\" href=\"css/pico.fluid.classless.css\">\n\n  <!-- Cyustom page styling -->\n  <style>\n    html {\n      background-color: #92938fd1\n    }\n    .container {\n      display: flex;\n      justify-content: center;\n      min-height: calc(100vh - 6rem);\n      padding: 1rem 0;\n      \n    }    \n    .container {\n      max-width: 860px;\n      padding: 40px;\n    }\n  </style>\n\n</head>\n  <body>\n    <main class=\"container\">\n      <article class=\"grid\">\n        <div>\n          <hgroup>\n            <h1>ESP Async FS WebServer - LED Switcher - simpleServer.ino</h1>\n          </hgroup>\n          <label for=\"remember\">\n            <input type=\"checkbox\" role=\"switch\" id=\"toggle-led\" name=\"toggle-led\">\n            Toggle built-in LED\n          </label>\n          <br>\n          <p id=\"esp-response\"></p>\n          <img src=\"img/espressif.jpg\" style=\"opacity: 0.75\"/>\n        </div>\n      </article>\n    </main>\n    \n    <script type=\"text/javascript\">\n      // This function fetch the GET request to the ESP webserver\n      function toggleLed() {\n        const pars = new URLSearchParams({\n          val:  document.getElementById('toggle-led').checked ? '1' : '0'\n        });\n        \n        fetch('/led?' + pars )                // Do the request\n        .then(response => response.text())    // Parse the response \n        .then(text => {                       // DO something with response\n          console.log(text);\n          document.getElementById('esp-response').innerHTML = text + ' <i>(Builtin LED is ON with a low signal)</i>';\n        });\n      }\n      \n      // Add event listener to the LED checkbox (the function will be called on every change)\n      document.getElementById('toggle-led').addEventListener('change', toggleLed );\n    </script>\n    \n  </body>\n</html>\n"
  },
  {
    "path": "examples/simpleServer/simpleServer.ino",
    "content": "#include <FS.h>\r\n#include <LittleFS.h>\r\n#include <FSWebServer.h>\r\n\r\nFSWebServer server(LittleFS, 80, \"myServer\");\r\nuint16_t testInt = 150;\r\nfloat testFloat = 123.456f;\r\nbool testBool = true;\r\n\r\n#ifndef LED_BUILTIN\r\n#define LED_BUILTIN 2\r\n#endif\r\nconst uint8_t ledPin = LED_BUILTIN;\r\n\r\n////////////////////////////////  Filesystem  /////////////////////////////////////////\r\nbool startFilesystem() {\r\n  if (LittleFS.begin()) {\r\n    server.printFileList(LittleFS, \"/\", 1, Serial);    \r\n    return true;\r\n  } else {\r\n    Serial.println(\"ERROR on mounting filesystem. It will be reformatted!\");\r\n    LittleFS.format();\r\n    ESP.restart();\r\n  }\r\n  return false;\r\n}\r\n\r\n////////////////////////////  Load custom options //////////////////////////////////////\r\nbool loadApplicationConfig() {\r\n  if (LittleFS.exists(server.getConfiFileName())) {\r\n    // Test \"options\" values\r\n    server.getOptionValue(\"Test int variable\", testInt);\r\n    server.getOptionValue(\"Test float variable\", testFloat);\r\n    server.getOptionValue(\"Test bool variable\", testBool);\r\n    return true;\r\n  }\r\n  return false;\r\n}\r\n\r\n//---------------------------------------\r\nvoid handleLed() {\r\n  static int value = false;\r\n  // http://xxx.xxx.xxx.xxx/led?val=1\r\n  if (server.hasArg(\"val\")) {\r\n    value = server.arg(\"val\").toInt();\r\n    digitalWrite(ledPin, value);\r\n  }\r\n  String reply = \"LED is now \";\r\n  reply += value ? \"ON\" : \"OFF\";\r\n  server.send(200, \"text/plain\", reply);\r\n}\r\n\r\n\r\nvoid setup() {\r\n  pinMode(ledPin, OUTPUT);\r\n  Serial.begin(115200);\r\n  delay(1000);\r\n  if (startFilesystem()) {\r\n    // Load application config options\r\n    if (loadApplicationConfig()) {\r\n      Serial.printf(\"Stored \\\"testInt\\\" value: %d\\n\", testInt);\r\n      Serial.printf(\"Stored \\\"testFloat\\\" value: %3.3f\\n\", testFloat);\r\n      Serial.printf(\"Stored \\\"testBool\\\" value: %s\\n\", testBool?\"true\":\"false\");\r\n    }\r\n  } else\r\n    Serial.println(\"LittleFS error!\");\r\n\r\n  // Try to connect to WiFi (will start AP if not connected after timeout)\r\n  if (!server.startWiFi(10000)) {\r\n    Serial.println(\"\\nWiFi not connected! Starting AP mode...\");\r\n    server.startCaptivePortal(\"ESP_AP\", \"123456789\", \"/setup\");\r\n  }\r\n\r\n  // Add custom application options tab and set custom title\r\n  server.addOptionBox(\"Custom options\");\r\n  server.addOption(\"Test int variable\", testInt);\r\n  server.addOption(\"Test float variable\", (double)testFloat, 0.0, 100.0, 0.001);\r\n  // add a helpful comment beneath the float slider\r\n  server.addComment(\"Test float variable\", \"Use this to adjust the intensity (0-100)\");\r\n\r\n  // add a helpful comment direclty (boolean comment appears inline by default)\r\n  server.addOption(\"Test bool variable\", testBool, \"Enable/disable feature\");\r\n  server.setSetupPageTitle(\"Simple ESP FS WebServer\");\r\n\r\n  // Enable ACE FS file web editor\r\n  server.enableFsCodeEditor();\r\n\r\n  // Custom endpoint handler\r\n  server.on(\"/led\", HTTP_GET, handleLed);\r\n\r\n  // Start server\r\n  server.begin();\r\n  Serial.print(F(\"\\nAsync ESP Web Server started on IP Address: \"));\r\n  Serial.println(server.getServerIP());\r\n  Serial.println(F(\r\n    \"This is \\\"simpleServer.ino\\\" example.\\n\"\r\n    \"Open /setup page to configure optional parameters.\\n\"\r\n    \"Open /edit page to view, edit or upload example or your custom webserver source files.\"));\r\n}\r\n\r\nvoid loop() {\r\n  // Handle client requests\r\n  server.run();\r\n\r\n  // Nothing to do here for this simple example\r\n  delay(1);\r\n}\r\n"
  },
  {
    "path": "examples/websocketEcharts/README.md",
    "content": "# WebSocket Chart Example\n\nReal-time telemetry dashboard demonstrating WebSocket communication with live charting.\n\nThe ESP broadcasts memory stats (heap, max block) and WiFi RSSI every second. The web interface displays data in animated charts with theme selection and auto-reconnect.\n\n## Charting Library\n\nThis example uses **[ECharts 5](https://echarts.apache.org/)** – a powerful, declarative JavaScript visualization library by Apache.\n\n- **Lightweight**: Loaded via CDN (~900KB minified)\n- **Responsive**: Auto-resizes on window changes\n- **Smooth animations**: Built-in transitions for live data\n- **Rich API**: [Full documentation](https://echarts.apache.org/en/option.html)\n\nThe dashboard implements real-time `line` charts with `time` axis and formatted tooltips. All chart options are in `data/index.htm`.\n\n\n![Websocket Echarts](../../docs/ws_echarts.jpg)\n"
  },
  {
    "path": "examples/websocketEcharts/data/index.htm",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <title>ESP WebSocket Telemetry</title>\n    <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;600&family=Space+Grotesk:wght@400;600;700&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <style>\n      /* Theme tokens */\n      :root {\n        --bg-1: #0b0f1a;\n        --bg-2: #14253d;\n        --panel: #111827cc;\n        --panel-strong: #0f172a;\n        --text: #e5e7eb;\n        --muted: #94a3b8;\n        --accent: #22d3ee;\n        --accent-2: #f59e0b;\n        --accent-3: #34d399;\n        --danger: #f87171;\n        --grid: rgba(148, 163, 184, 0.2);\n        --shadow: 0 18px 45px rgba(2, 6, 23, 0.55);\n      }\n\n      /* Theme variants */\n      body[data-theme=\"sunrise\"] {\n        --bg-1: #1b0c24;\n        --bg-2: #3f1a3a;\n        --panel: #1f1230cc;\n        --panel-strong: #1b0f2a;\n        --text: #fdf2f8;\n        --muted: #f9a8d4;\n        --accent: #fb7185;\n        --accent-2: #f59e0b;\n        --accent-3: #f472b6;\n        --danger: #f43f5e;\n        --grid: rgba(251, 113, 133, 0.2);\n        --shadow: 0 18px 45px rgba(24, 5, 32, 0.6);\n      }\n\n      body[data-theme=\"mono\"] {\n        --bg-1: #0b0d0f;\n        --bg-2: #1b2128;\n        --panel: #0f1318cc;\n        --panel-strong: #0f141a;\n        --text: #e2e8f0;\n        --muted: #94a3b8;\n        --accent: #e2e8f0;\n        --accent-2: #94a3b8;\n        --accent-3: #cbd5f5;\n        --danger: #f87171;\n        --grid: rgba(148, 163, 184, 0.2);\n        --shadow: 0 18px 45px rgba(5, 8, 12, 0.65);\n      }\n\n      /* Base reset */\n      * {\n        box-sizing: border-box;\n      }\n\n      /* Page background and typography */\n      body {\n        margin: 0;\n        font-family: \"Space Grotesk\", system-ui, sans-serif;\n        color: var(--text);\n        background: radial-gradient(1200px 600px at 15% 20%, #1b3a5e 0%, transparent 60%),\n          radial-gradient(900px 500px at 85% 0%, #15304e 0%, transparent 60%),\n          linear-gradient(180deg, var(--bg-1), var(--bg-2));\n        min-height: 100vh;\n      }\n\n      body[data-theme=\"sunrise\"] {\n        background: radial-gradient(1000px 600px at 12% 15%, #8b215b 0%, transparent 60%),\n          radial-gradient(900px 500px at 85% 5%, #f97316 0%, transparent 55%),\n          linear-gradient(180deg, var(--bg-1), var(--bg-2));\n      }\n\n      body[data-theme=\"mono\"] {\n        background: radial-gradient(1000px 600px at 12% 15%, #2b3640 0%, transparent 60%),\n          radial-gradient(900px 500px at 85% 5%, #1f2a33 0%, transparent 55%),\n          linear-gradient(180deg, var(--bg-1), var(--bg-2));\n      }\n\n      /* Subtle grid overlay */\n      body::before {\n        content: \"\";\n        position: fixed;\n        inset: 0;\n        background-image: radial-gradient(rgba(148, 163, 184, 0.15) 1px, transparent 0);\n        background-size: 24px 24px;\n        pointer-events: none;\n        opacity: 0.35;\n      }\n\n      /* Layout container */\n      .page {\n        max-width: 1200px;\n        margin: 0 auto;\n        padding: 28px 22px 40px;\n        position: relative;\n      }\n\n      /* Top header */\n      header {\n        display: flex;\n        flex-wrap: wrap;\n        align-items: center;\n        justify-content: space-between;\n        gap: 16px;\n        margin-bottom: 22px;\n      }\n\n      /* Theme selector pill */\n      .theme-switch {\n        display: flex;\n        align-items: center;\n        gap: 10px;\n        padding: 8px 12px;\n        border-radius: 999px;\n        border: 1px solid rgba(148, 163, 184, 0.25);\n        background: var(--panel);\n        box-shadow: var(--shadow);\n        font-size: 0.9rem;\n        color: var(--muted);\n      }\n\n      .theme-switch select {\n        appearance: none;\n        background: var(--panel-strong);\n        color: var(--text);\n        border: 1px solid rgba(148, 163, 184, 0.25);\n        border-radius: 999px;\n        padding: 6px 28px 6px 12px;\n        font-family: \"Space Grotesk\", system-ui, sans-serif;\n        font-size: 0.9rem;\n        position: relative;\n      }\n\n      .theme-switch select:focus {\n        outline: 2px solid rgba(34, 211, 238, 0.45);\n        outline-offset: 2px;\n      }\n\n      .title {\n        display: flex;\n        flex-direction: column;\n        gap: 4px;\n      }\n\n      .title h1 {\n        font-size: clamp(28px, 3vw, 38px);\n        margin: 0;\n        letter-spacing: -0.02em;\n      }\n\n      .title span {\n        color: var(--muted);\n        font-size: 0.95rem;\n      }\n\n      .status {\n        display: flex;\n        align-items: center;\n        gap: 10px;\n        background: var(--panel);\n        padding: 10px 16px;\n        border-radius: 999px;\n        border: 1px solid rgba(148, 163, 184, 0.25);\n        box-shadow: var(--shadow);\n      }\n\n      .status-dot {\n        width: 12px;\n        height: 12px;\n        border-radius: 50%;\n        background: var(--danger);\n        box-shadow: 0 0 10px rgba(248, 113, 113, 0.8);\n        transition: background 0.2s ease, box-shadow 0.2s ease;\n      }\n\n      .status.connected .status-dot {\n        background: var(--accent-3);\n        box-shadow: 0 0 10px rgba(52, 211, 153, 0.7);\n      }\n\n      /* Metric cards */\n      .stats {\n        display: grid;\n        gap: 16px;\n        grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n        margin-bottom: 22px;\n      }\n\n      .card {\n        background: var(--panel);\n        border-radius: 18px;\n        padding: 16px;\n        border: 1px solid rgba(148, 163, 184, 0.2);\n        box-shadow: var(--shadow);\n        animation: floatIn 0.8s ease both;\n      }\n\n      .card h3 {\n        margin: 0 0 10px;\n        font-size: 0.9rem;\n        color: var(--muted);\n        text-transform: uppercase;\n        letter-spacing: 0.08em;\n      }\n\n      .card .value {\n        font-size: 1.6rem;\n        font-weight: 700;\n      }\n\n      .card .sub {\n        font-family: \"IBM Plex Mono\", monospace;\n        color: var(--muted);\n        font-size: 0.9rem;\n      }\n\n      /* Charts grid */\n      .charts {\n        display: grid;\n        gap: 18px;\n        grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));\n      }\n\n      .chart-panel {\n        background: var(--panel-strong);\n        border-radius: 20px;\n        padding: 18px;\n        border: 1px solid rgba(148, 163, 184, 0.15);\n        box-shadow: var(--shadow);\n        min-height: 320px;\n        display: flex;\n        flex-direction: column;\n        gap: 12px;\n      }\n\n      .chart-panel h2 {\n        margin: 0;\n        font-size: 1.1rem;\n        text-align: center;\n      }\n\n      .chart {\n        flex: 1;\n        min-height: 240px;\n      }\n\n      /* Soft card reveal */\n      @keyframes floatIn {\n        from {\n          opacity: 0;\n          transform: translateY(10px);\n        }\n        to {\n          opacity: 1;\n          transform: translateY(0);\n        }\n      }\n\n      @media (max-width: 720px) {\n        .page {\n          padding: 20px 16px 32px;\n        }\n      }\n    </style>\n  </head>\n  <body data-theme=\"aurora\">\n    <div class=\"page\">\n      <!-- Header with title, theme selector, and connection status -->\n      <header>\n        <div class=\"title\">\n          <h1>ESP WebSocket Telemetry</h1>\n          <span>Real-time heap + RSSI monitoring</span>\n        </div>\n        <div style=\"display: flex; flex-wrap: wrap; gap: 12px; align-items: center;\">\n          <div class=\"theme-switch\">\n            <label for=\"themeSelect\">Theme</label>\n            <select id=\"themeSelect\" aria-label=\"Theme selector\">\n              <option value=\"aurora\">Aurora</option>\n              <option value=\"sunrise\">Sunrise</option>\n              <option value=\"mono\">Mono</option>\n            </select>\n          </div>\n          <div class=\"status\" id=\"statusBadge\">\n            <div class=\"status-dot\" id=\"statusDot\"></div>\n            <div>\n              <div id=\"statusText\">Disconnected</div>\n              <div class=\"sub\" id=\"statusMeta\">Waiting for data...</div>\n            </div>\n          </div>\n        </div>\n      </header>\n\n      <!-- Live metrics -->\n      <section class=\"stats\">\n        <div class=\"card\">\n          <h3>Total Heap</h3>\n          <div class=\"value\" id=\"heapValue\">--</div>\n          <div class=\"sub\" id=\"heapSub\">bytes</div>\n        </div>\n        <div class=\"card\">\n          <h3>Max Block</h3>\n          <div class=\"value\" id=\"blockValue\">--</div>\n          <div class=\"sub\" id=\"blockSub\">bytes</div>\n        </div>\n        <div class=\"card\">\n          <h3>WiFi RSSI</h3>\n          <div class=\"value\" id=\"rssiValue\">--</div>\n          <div class=\"sub\">dBm</div>\n        </div>\n        <div class=\"card\">\n          <h3>Last Update</h3>\n          <div class=\"value\" id=\"timeValue\">--</div>\n          <div class=\"sub\" id=\"timeSub\">local time</div>\n        </div>\n      </section>\n\n      <!-- Chart panels -->\n      <section class=\"charts\">\n        <div class=\"chart-panel\">\n          <h2>Memory Trend</h2>\n          <div id=\"heapChart\" class=\"chart\"></div>\n        </div>\n        <div class=\"chart-panel\">\n          <h2>WiFi Signal</h2>\n          <div id=\"rssiChart\" class=\"chart\"></div>\n        </div>\n      </section>\n    </div>\n\n    <!-- Charting library -->\n    <script src=\"https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js\"></script>\n    <script>\n      // DOM references\n      const statusBadge = document.getElementById(\"statusBadge\");\n      const statusText = document.getElementById(\"statusText\");\n      const statusMeta = document.getElementById(\"statusMeta\");\n      const heapValue = document.getElementById(\"heapValue\");\n      const heapSub = document.getElementById(\"heapSub\");\n      const blockValue = document.getElementById(\"blockValue\");\n      const blockSub = document.getElementById(\"blockSub\");\n      const rssiValue = document.getElementById(\"rssiValue\");\n      const timeValue = document.getElementById(\"timeValue\");\n      const timeSub = document.getElementById(\"timeSub\");\n      const themeSelect = document.getElementById(\"themeSelect\");\n\n      // Chart instances\n      const heapChart = echarts.init(document.getElementById(\"heapChart\"));\n      const rssiChart = echarts.init(document.getElementById(\"rssiChart\"));\n\n      // In-memory time series (bounded by maxPoints)\n      const heapSeries = [];\n      const blockSeries = [];\n      const rssiSeries = [];\n      const maxPoints = 120;\n\n      // Update connection badge\n      function setStatus(isConnected, message) {\n        statusBadge.classList.toggle(\"connected\", isConnected);\n        statusText.textContent = isConnected ? \"Connected\" : \"Disconnected\";\n        statusMeta.textContent = message || \"\";\n      }\n\n      // Human readable byte formatting\n      function formatBytes(value) {\n        if (!Number.isFinite(value)) return \"--\";\n        if (value >= 1024 * 1024) {\n          return (value / (1024 * 1024)).toFixed(2) + \" MB\";\n        }\n        if (value >= 1024) {\n          return (value / 1024).toFixed(1) + \" KB\";\n        }\n        return value.toFixed(0) + \" B\";\n      }\n\n      // Numeric-only axis labels in KB\n      function formatKbNumber(value) {\n        if (!Number.isFinite(value)) return \"--\";\n        return (value / 1024).toFixed(1);\n      }\n\n      // Keep fixed-size series for smooth charting\n      function pushPoint(series, point) {\n        series.push(point);\n        if (series.length > maxPoints) {\n          series.shift();\n        }\n      }\n\n      // Render charts with current series data\n      function updateCharts() {\n        heapChart.setOption({\n          animationDuration: 300,\n          grid: { left: 40, right: 20, top: 20, bottom: 44 },\n          tooltip: { trigger: \"axis\" },\n          xAxis: {\n            type: \"time\",\n            axisLine: { lineStyle: { color: \"rgba(148,163,184,0.6)\" } },\n            splitLine: { show: false },\n            axisLabel: { fontSize: 11, rotate: 45 },\n          },\n          yAxis: {\n            type: \"value\",\n            name: \"KB\",\n            nameLocation: \"middle\",\n            nameGap: 29,\n            nameRotate: 90,\n            nameTextStyle: { color: \"rgba(148,163,184,0.7)\", fontSize: 11 },\n            axisLine: { lineStyle: { color: \"rgba(148,163,184,0.6)\" } },\n            splitLine: { lineStyle: { color: \"rgba(148,163,184,0.15)\" } },\n            axisLabel: { formatter: (value) => formatKbNumber(value), fontSize: 11 },\n          },\n          series: [\n            {\n              name: \"Total Heap\",\n              type: \"line\",\n              smooth: true,\n              showSymbol: false,\n              data: heapSeries,\n              lineStyle: { color: \"#22d3ee\", width: 2 },\n              areaStyle: { color: \"rgba(34,211,238,0.25)\" },\n            },\n            {\n              name: \"Max Block\",\n              type: \"line\",\n              smooth: true,\n              showSymbol: false,\n              data: blockSeries,\n              lineStyle: { color: \"#f59e0b\", width: 2 },\n              areaStyle: { color: \"rgba(245,158,11,0.2)\" },\n            },\n          ],\n        });\n\n        rssiChart.setOption({\n          animationDuration: 300,\n          grid: { left: 40, right: 20, top: 20, bottom: 44 },\n          tooltip: { trigger: \"axis\" },\n          xAxis: {\n            type: \"time\",\n            axisLine: { lineStyle: { color: \"rgba(148,163,184,0.6)\" } },\n            splitLine: { show: false },\n            axisLabel: { fontSize: 11, rotate: 45 },\n          },\n          yAxis: {\n            type: \"value\",\n            name: \"dBm\",\n            nameLocation: \"middle\",\n            nameGap: 29,\n            nameRotate: 90,\n            nameTextStyle: { color: \"rgba(148,163,184,0.7)\", fontSize: 11 },\n            max: -30,\n            min: -95,\n            axisLine: { lineStyle: { color: \"rgba(148,163,184,0.6)\" } },\n            splitLine: { lineStyle: { color: \"rgba(148,163,184,0.15)\" } },\n            axisLabel: { formatter: (value) => Math.round(value).toString(), fontSize: 11 },\n          },\n          series: [\n            {\n              name: \"RSSI\",\n              type: \"line\",\n              smooth: true,\n              showSymbol: false,\n              data: rssiSeries,\n              lineStyle: { color: \"#34d399\", width: 2 },\n              areaStyle: { color: \"rgba(52,211,153,0.18)\" },\n              markLine: {\n                symbol: \"none\",\n                lineStyle: { color: \"rgba(148,163,184,0.55)\", width: 1 },\n                label: {\n                  show: true,\n                  position: \"insideEnd\",\n                  distance: 8,\n                  fontSize: 10,\n                  color: \"rgba(148,163,184,0.75)\",\n                  formatter: \"-70 dBm\",\n                },\n                data: [{ yAxis: -70 }],\n              },\n            },\n          ],\n        });\n      }\n\n      // Apply incoming WebSocket payload\n      function handleMessage(data) {\n        if (!data || !data.addPoint) return;\n        const timestamp = (data.timestamp || 0) * 1000;\n        if (!timestamp) return;\n\n        pushPoint(heapSeries, [timestamp, data.totalHeap || 0]);\n        pushPoint(blockSeries, [timestamp, data.maxBlock || 0]);\n        pushPoint(rssiSeries, [timestamp, data.rssi || 0]);\n\n        heapValue.textContent = formatBytes(data.totalHeap || 0);\n        heapSub.textContent = \"bytes\";\n        blockValue.textContent = formatBytes(data.maxBlock || 0);\n        blockSub.textContent = \"bytes\";\n        rssiValue.textContent = (data.rssi || 0).toFixed(0);\n\n        const localTime = new Date(timestamp);\n        timeValue.textContent = localTime.toLocaleTimeString();\n        timeSub.textContent = localTime.toLocaleDateString();\n\n        updateCharts();\n      }\n\n      // WebSocket connection state\n      let socket;\n      let reconnectTimer;\n\n      // Apply theme and persist selection\n      function applyTheme(value) {\n        document.body.setAttribute(\"data-theme\", value);\n        try {\n          localStorage.setItem(\"esp-theme\", value);\n        } catch (err) {\n          // Storage can fail in private mode.\n        }\n      }\n\n      // Initialize theme selector with saved preference\n      function initTheme() {\n        let saved = \"aurora\";\n        try {\n          saved = localStorage.getItem(\"esp-theme\") || saved;\n        } catch (err) {\n          saved = \"aurora\";\n        }\n        themeSelect.value = saved;\n        applyTheme(saved);\n        themeSelect.addEventListener(\"change\", (event) => {\n          applyTheme(event.target.value);\n        });\n      }\n\n      // Connect to ESP WebSocket endpoint\n      function connect() {\n        clearTimeout(reconnectTimer);\n        const proto = location.protocol === \"https:\" ? \"wss\" : \"ws\";\n        const wsUrl = proto + \"://\" + location.hostname + \":81/ws\";\n        statusMeta.textContent = \"Connecting to \" + wsUrl + \"...\";\n\n        socket = new WebSocket(wsUrl);\n\n        socket.addEventListener(\"open\", () => {\n          setStatus(true, \"Receiving live data\");\n        });\n\n        socket.addEventListener(\"close\", () => {\n          setStatus(false, \"Reconnecting...\");\n          reconnectTimer = setTimeout(connect, 1500);\n        });\n\n        socket.addEventListener(\"error\", () => {\n          setStatus(false, \"Connection error\");\n          socket.close();\n        });\n\n        socket.addEventListener(\"message\", (event) => {\n          try {\n            const payload = JSON.parse(event.data);\n            handleMessage(payload);\n          } catch (err) {\n            statusMeta.textContent = \"Invalid data\";\n          }\n        });\n      }\n\n      // Boot sequence\n      updateCharts();\n      initTheme();\n      connect();\n      window.addEventListener(\"resize\", () => {\n        heapChart.resize();\n        rssiChart.resize();\n      });\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/websocketEcharts/websocketEcharts.ino",
    "content": "#include <Arduino.h>\n#include <FS.h>\n#include <LittleFS.h>\n#include <FSWebServer.h>   // https://github.com/cotestatnt/esp-fs-webserver/\n#if defined(ESP32)\n#include <WiFi.h>\n#elif defined(ESP8266)\n#include <ESP8266WiFi.h>\n#endif\n\n#define FILESYSTEM LittleFS\n\nFSWebServer server(FILESYSTEM, 80, \"mywebserver\");\n\n#ifndef LED_BUILTIN\n#define LED_BUILTIN 2\n#endif\n\n// Timezone definition to get properly time from NTP server\n#define MYTZ \"CET-1CEST,M3.5.0,M10.5.0/3\"\nstruct tm Time;\n\n////////////////////////////////   WebSocket Handler  /////////////////////////////\nvoid webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {\n  switch (type) {\n    case WStype_DISCONNECTED:\n      Serial.printf(\"[%u] Disconnected!\\n\", num);\n      break;\n    case WStype_CONNECTED: {\n        IPAddress ip = server.getWebSocketServer()->remoteIP(num);\n        Serial.printf(\"Hello client #%d [%s]\\n\", (int)num, ip.toString().c_str());\n      }\n      break;\n    case WStype_TEXT:\n      Serial.printf(\"[%u] get Text: %s\\n\", num, payload);\n      break;\n    case WStype_BIN:\n      Serial.printf(\"[%u] get binary length: %u\\n\", num, length);\n      break;\n    default:\n      break;\n  }\n}\n\n\n////////////////////////////////  NTP Time  /////////////////////////////////////\nvoid getUpdatedtime(const uint32_t timeout) {\n  uint32_t start = millis();\n  Serial.print(\"Sync time...\");\n  while (millis() - start < timeout  && Time.tm_year <= (1970 - 1900)) {\n    time_t now = time(nullptr);\n    Time = *localtime(&now);\n    delay(5);\n  }\n  Serial.println(\" done.\");\n}\n\n\n////////////////////////////////  Filesystem  /////////////////////////////////////////\nbool startFilesystem() {\n  if (FILESYSTEM.begin()){\n    server.printFileList(FILESYSTEM, \"/\", 1, Serial);\n    return true;\n  }\n  else {\n    Serial.println(\"ERROR on mounting filesystem. It will be reformatted!\");\n    FILESYSTEM.format();\n    ESP.restart();\n  }\n  return false;\n}\n\n\n\nvoid setup() {\n  pinMode(LED_BUILTIN, OUTPUT);\n  Serial.begin(115200);\n\n  // FILESYSTEM INIT\n  startFilesystem();\n\n  // Try to connect to WiFi (will start AP if not connected after timeout)\n  if (!server.startWiFi(10000)) {\n    Serial.println(\"\\nWiFi not connected! Starting AP mode...\");\n    server.startCaptivePortal(\"ESP_AP\", \"123456789\", \"/setup\");\n  }\n\n  // Enable ACE FS file web editor and add FS info callback function\n  server.enableFsCodeEditor();\n\n  // Start server with custom websocket event handler\n  server.begin(webSocketEvent);\n  Serial.print(F(\"ESP Web Server started on IP Address: \"));\n  Serial.println(server.getServerIP());\n  Serial.println(F(\n    \"This is \\\"highcharts.ino\\\" example.\\n\"\n    \"Open /setup page to configure optional parameters.\\n\"\n    \"Open /edit page to view, edit or upload example or your custom webserver source files.\"\n  ));\n}\n\n\nvoid loop() {\n  server.run();  // Handle client requests and websocket events\n\n  // Send ESP system time (epoch) and heap stats to WS client\n  static uint32_t sendToClientTime;\n  if (millis() - sendToClientTime > 1000 ) {\n    sendToClientTime = millis();\n    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));\n\n    time_t now = time(nullptr);\n    CJSON::Json doc;\n    doc.setBool(\"addPoint\", true);\n    doc.setNumber(\"timestamp\", (double)now);\n#ifdef ESP32\n    doc.setNumber(\"totalHeap\", (double)heap_caps_get_free_size(0));\n    doc.setNumber(\"maxBlock\", (double)heap_caps_get_largest_free_block(0));\n#elif defined(ESP8266)\n    uint32_t free;\n    uint32_t max;\n    ESP.getHeapStats(&free, &max, nullptr);\n    doc.setNumber(\"totalHeap\", (double)free);\n    doc.setNumber(\"maxBlock\", (double)max);\n#endif\n    doc.setNumber(\"rssi\", (double)WiFi.RSSI());\n    String msg = doc.serialize();\n    server.broadcastWebSocket(msg);\n  }\n\n}"
  },
  {
    "path": "examples/withWebSocket/.gitignore",
    "content": ".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",
    "content": "#pragma once\n#include <Arduino.h>\n\ninline const char homepage[] PROGMEM = R\"EOF(\n<!DOCTYPE html>\n<html>\n  <head>\n    <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\">\n    <title>WebSocket test page</title>\n    \n    <style type=\"text/css\" media=\"screen\">\n      body {width: 70%; margin: auto; background-color: black; color: #55ff55; font-family: monospace; text-align: center; }\n      .box {border: 1px solid white; border-radius: 10px; padding: 5px; margin: 10px; }\n      .output {color: white; }\n      #log {text-align: left; margin: 20px; max-height: 500px; overflow: auto; }\n      #input_div, #input_el {line-height: 13px; color: #AAA;}\n      #input_el { width:97%; background-color: rgba(0,0,0,0); border: 0px;}\n      #input_el:focus {outline: none; }\n      #icon-setup{float: left;margin: 20px}\n    </style>\n    \n    <script type=\"text/javascript\">\n      var ws = null;\n      function ge(s){ return document.getElementById(s);} // Shorthand for get element\n      function ce(s){ return document.createElement(s);}  // Shorthand for create element\n      function stb(){ window.scrollTo(0, document.body.scrollHeight || document.documentElement.scrollHeight); } // Scroll to bottom\n      \n      // This function will add a message to the HTML element with id 'log'\n      function addMessage(m){\n        var msg = ce(\"div\");\n        msg.innerText = m;\n        ge(\"log\").appendChild(msg);\n        stb();  \n      }\n      \n      // If msg is a obj data tyep, handle it (just timestamp in this example) otherwise add to log console\n      function parseMessage(msg) {\n        try {\n          const obj = JSON.parse(msg);\n          if (typeof obj === 'object' && obj !== null) {\n            if (obj.esptime !== null) {\n              var date = new Date(0); // The 0 sets the date to epoch\n              if( date.setUTCSeconds(obj.esptime))\n                document.getElementById(\"esp-time\").innerHTML = date;\n            }\n          }\n        } catch {\n          addMessage(msg);\n        }\n      }\n      \n      // Configure and start WebSocket client\n      function startSocket(){\n        ws = new WebSocket('ws://' + location.hostname + ':81/');\n        ws.binaryType = \"arraybuffer\";\n        ws.onopen = function(e){\n          addMessage(\"WebSocket client connected to \" + 'ws://'+document.location.host+'/ws');\n        };\n        ws.onclose = function(e){\n          addMessage(\"WebSocket client disconnected\");\n        };\n        ws.onerror = function(e){\n          console.log(\"ws error\", e);\n          addMessage(\"Error\");\n        };\n        ws.onmessage = function(e){\n          parseMessage(e.data)\n        };\n        // Add event \"keydown\" listener to input box\n        ge(\"input_el\").onkeydown = function(e){\n          stb();\n          if(e.keyCode == 13 && ge(\"input_el\").value != \"\"){\n            ws.send(ge(\"input_el\").value);\n            ge(\"input_el\").value = \"\";\n          }\n        }\n      }\n  \n      // When page is fully loaded start connection\n      function onBodyLoad(){\n        startSocket();\n      }\n    </script>\n  </head>\n  <body id=\"body\" onload=\"onBodyLoad()\">\n    \n    <div class=icon>\n      <a id=icon-setup href=/setup>\n        <svg width=\"48\" height=\"48\" fill=#55ff55 viewBox=\"0 0 24 24\">\n          <path d=\"M12,8A4,4 0 0,1 16,12A4,4 0 0,1 12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8M12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12A2,2 0 0,0 12,10M10,22C9.75,22 9.54,21.82 9.5,21.58L9.13,18.93C8.5,18.68 7.96,18.34 7.44,17.94L4.95,18.95C4.73,19.03 4.46,18.95 4.34,18.73L2.34,15.27C2.21,15.05 2.27,14.78 2.46,14.63L4.57,12.97L4.5,12L4.57,11L2.46,9.37C2.27,9.22 2.21,8.95 2.34,8.73L4.34,5.27C4.46,5.05 4.73,4.96 4.95,5.05L7.44,6.05C7.96,5.66 8.5,5.32 9.13,5.07L9.5,2.42C9.54,2.18 9.75,2 10,2H14C14.25,2 14.46,2.18 14.5,2.42L14.87,5.07C15.5,5.32 16.04,5.66 16.56,6.05L19.05,5.05C19.27,4.96 19.54,5.05 19.66,5.27L21.66,8.73C21.79,8.95 21.73,9.22 21.54,9.37L19.43,11L19.5,12L19.43,13L21.54,14.63C21.73,14.78 21.79,15.05 21.66,15.27L19.66,18.73C19.54,18.95 19.27,19.04 19.05,18.95L16.56,17.95C16.04,18.34 15.5,18.68 14.87,18.93L14.5,21.58C14.46,21.82 14.25,22 14,22H10M11.25,4L10.88,6.61C9.68,6.86 8.62,7.5 7.85,8.39L5.44,7.35L4.69,8.65L6.8,10.2C6.4,11.37 6.4,12.64 6.8,13.8L4.68,15.36L5.43,16.66L7.86,15.62C8.63,16.5 9.68,17.14 10.87,17.38L11.24,20H12.76L13.13,17.39C14.32,17.14 15.37,16.5 16.14,15.62L18.57,16.66L19.32,15.36L17.2,13.81C17.6,12.64 17.6,11.37 17.2,10.2L19.31,8.65L18.56,7.35L16.15,8.39C15.38,7.5 14.32,6.86 13.12,6.62L12.75,4H11.25Z\" />\n        </svg>\n      </a>\n    </div>\n\n    <div class=\"box\">\n      <p>ESP current time, sync with NTP server and sent to this client via <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/\">WebSocket!</a></p>\n        <h4 id='esp-time'>Waiting websocket connection...</h4>\n    </div>\n\n    <div class=\"box\">\n      $<input type=\"text\" value=\"\" id=\"input_el\">\n    </div>\n    \n    <div class=box>\n        <main id=log></main>\n    </div>\n  </body>\n</html>\n)EOF\";"
  },
  {
    "path": "examples/withWebSocket/partitions.csv",
    "content": "# Name,   Type, SubType, Offset,  Size, Flags\nnvs,      data, nvs,     0x9000,  0x5000,\notadata,  data, ota,     0xe000,  0x2000,\napp0,     app,  ota_0,   0x10000, 0x140000,\napp1,     app,  ota_1,   0x150000,0x140000,\nspiffs,   data, spiffs,  0x290000,0x160000,\ncoredump, data, coredump,0x3F0000,0x10000,\n"
  },
  {
    "path": "examples/withWebSocket/platformio.ini",
    "content": "; PlatformIO Project Configuration File\n;\n;   Build options: build flags, source filter\n;   Upload options: custom upload port, speed and extra flags\n;   Library options: dependencies, extra library storages\n;   Advanced options: extra scripting\n;\n; Please visit documentation for the other options and examples\n; https://docs.platformio.org/page/projectconf.html\n\n[platformio]\nsrc_dir = .\n\n[env:esp32-s3-devkitc1-n4r2]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32-s3-devkitc1-n4r2\nframework = arduino\nupload_speed = 921600\nboard_build.partitions = partitions.csv\nlib_extra_dirs = ../../\nlib_ignore = pio_examples\n\n[env:esp32dev]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32dev\nframework = arduino\nupload_speed = 921600\nboard_build.partitions = partitions.csv\nlib_extra_dirs = ../../\nlib_ignore = pio_examples\n\n[env:esp8266-nodemcuv2]\nplatform = espressif8266\nboard = nodemcuv2\nframework = arduino\nupload_speed = 921600\nboard_build.partitions = partitions.csv\nlib_extra_dirs = ../../\nlib_ignore = pio_examples\n"
  },
  {
    "path": "examples/withWebSocket/readme.md",
    "content": "## esp-fs-webserver\nThis example is a little bit more advanced respect to the [simpleServer](https://github.com/cotestatnt/esp-fs-webserver/tree/main/examples/simpleServer) example.\n\nBasically is the same HTML code like simpleServer, but with the addition of a **WebSocket client** inside the webpage.\n\nOff course, on the ESP MCU we will run also a **WebSocket server** together to the web server.\n\nWith the help of WebSocket technology, we can send message from server-to-clients or from client-to-server in a **full-duplex communication channels over a single TCP connection.**\n\nIn this very simple example is used only to push from ESP side a message (the NTP updated ESP system time) to the connected clients, just to show hot to setup a system like this.\n\n![image](https://user-images.githubusercontent.com/27758688/151001497-6468b50f-d4cb-46e1-ab4c-4d7aca3883db.png)\n"
  },
  {
    "path": "examples/withWebSocket/withWebSocket.ino",
    "content": "#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 LittleFS\r\nconst char* hostname = \"fsbrowser\";\r\n\r\n// If you edit server port, remember to change also websocket port in index_htm.h\r\n// By default websocket port with FSWebServer library is server port + 1\r\nFSWebServer server(FILESYSTEM, 80, hostname);\r\n\r\n#ifndef LED_BUILTIN\r\n#define LED_BUILTIN 2\r\n#endif\r\n#define BOOT_BUTTON 0\r\n\r\n// Log messages both on Serial and WebSocket clients\r\nvoid wsLogPrintf(bool toSerial, const char* format, ...) {\r\n  char buffer[128];\r\n  va_list args;\r\n  va_start(args, format);\r\n  vsnprintf(buffer, 128, format, args);\r\n  va_end(args);\r\n  server.broadcastWebSocket(buffer);\r\n  if (toSerial)\r\n    Serial.println(buffer);\r\n}\r\n\r\n/////////////////////////   WebSocket event callback////////////////////////////////   WebSocket Handler  /////////////////////////////\r\nvoid webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {\r\n  switch (type) {\r\n    case WStype_DISCONNECTED:\r\n      Serial.printf(\"[%u] Disconnected!\\n\", num);\r\n      break;\r\n    case WStype_CONNECTED:{\r\n        IPAddress ip = server.getWebSocketServer()->remoteIP(num);\r\n        server.getWebSocketServer()->sendTXT(num, \"{\\\"Connected\\\": true}\");\r\n\r\n        // Print welcome message to all clients and to Serial\r\n        wsLogPrintf(true, \"Hello to client #%d [%s]\\n\", (int)num, ip.toString().c_str());\r\n      }\r\n      break;\r\n    case WStype_TEXT:\r\n      Serial.printf(\"[%u] got Text: %s\\n\", num, payload);   // Got text message from a client\r\n      break;\r\n    case WStype_BIN:\r\n      Serial.printf(\"[%u] got binary length: %u\\n\", num, length); // Got binary message from a client\r\n      break;\r\n    default:\r\n      break;\r\n  }\r\n}\r\n\r\n// Test \"config\" values\r\nString optionString = \"Test option String\";\r\nuint32_t optionULong = 1234567890;\r\nuint8_t ledPin = LED_BUILTIN;\r\nbool optionBool = true;\r\n\r\n// Timezone definition to get properly time from NTP server\r\n#define MYTZ \"CET-1CEST,M3.5.0,M10.5.0/3\"\r\nstruct tm Time;\r\n\r\n\r\n////////////////////////////////  NTP Time  /////////////////////////////////////\r\nvoid getUpdatedtime(const uint32_t timeout) {\r\n  uint32_t start = millis();\r\n  Serial.print(\"Sync time...\");\r\n  while (millis() - start < timeout && Time.tm_year <= (1970 - 1900)) {\r\n    time_t now = time(nullptr);\r\n    Time = *localtime(&now);\r\n    delay(5);\r\n  }\r\n  Serial.println(\" done.\");\r\n}\r\n\r\n\r\n////////////////////////////////  Filesystem  /////////////////////////////////////////\r\nbool startFilesystem() {\r\n  if (FILESYSTEM.begin()) {\r\n    server.printFileList(FILESYSTEM, \"/\", 1, Serial);\r\n    return true;\r\n  } else {\r\n    Serial.println(\"ERROR on mounting filesystem. It will be reformatted!\");\r\n    FILESYSTEM.format();\r\n    ESP.restart();\r\n  }\r\n  return false;\r\n}\r\n\r\n\r\n////////////////////  Load and save application configuration from filesystem  ////////////////////\r\nbool loadApplicationConfig() {\r\n  if (FILESYSTEM.exists(server.getConfiFileName())) {\r\n    server.getOptionValue(\"Option 1\", optionString);\r\n    server.getOptionValue(\"Option 2\", optionULong);\r\n    server.getOptionValue(\"LED Pin\", ledPin);\r\n    server.getOptionValue(\"Option Bool\", optionBool);\r\n    server.getOptionValue(\"Option Bool\", optionBool);\r\n    server.closeSetupConfiguration();  // Close configuration to free resources\r\n    return true;\r\n  }\r\n  return false;\r\n}\r\n\r\n\r\nvoid setup() {\r\n  pinMode(LED_BUILTIN, OUTPUT);\r\n  pinMode(BOOT_BUTTON, INPUT_PULLUP);\r\n\r\n  Serial.begin(115200);\r\n  delay(1000);\r\n\r\n  // FILESYSTEM INIT\r\n  if (startFilesystem()) {\r\n    // Load configuration (if not present, default will be created when webserver will start)\r\n    if (loadApplicationConfig()) {\r\n      Serial.println(F(\"\\nApplication option loaded\"));\r\n      Serial.printf(\"  LED Pin: %d\\n\", ledPin);\r\n      Serial.printf(\"  Option 1: %s\\n\", optionString.c_str());   \r\n      Serial.printf(\"  Option 2: %u\\n\", optionULong);\r\n      Serial.printf(\"  Option Bool: %s\\n\", optionBool?\"true\":\"false\");\r\n    }\r\n    else\r\n      Serial.println(F(\"Application options NOT loaded!\"));\r\n  }\r\n\r\n  // Try to connect to WiFi (will start AP if not connected after timeout)\r\n  if (!server.startWiFi(10000)) {\r\n    Serial.println(\"\\nWiFi not connected! Starting AP mode...\");\r\n    server.startCaptivePortal(\"ESP_AP\", \"123456789\", \"/setup\");\r\n  }\r\n\r\n  // Configure /setup page\r\n  server.addOptionBox(\"My Options\");\r\n  server.addOption(\"LED Pin\", ledPin);\r\n  server.addOption(\"Option 1\", optionString.c_str());\r\n  server.addOption(\"Option 2\", optionULong);\r\n  server.addOption(\"Option Bool\", optionBool);\r\n  server.addComment(\"Option Bool\", \"Enable this feature on startup\");\r\n\r\n  // Add custom page handlers\r\n  server.on(\"/\", HTTP_GET, [](){\r\n    server.send_P(200, \"text/html\", homepage);\r\n  });\r\n\r\n  // Enable ACE FS file web editor and add FS info callback function\r\n  server.enableFsCodeEditor();\r\n  /*\r\n  * Getting FS info (total and free bytes) is strictly related to\r\n  * filesystem library used (LittleFS, FFat, SPIFFS etc etc)\r\n  * (On ESP8266 will be used \"built-in\" fsInfo data type)\r\n  */\r\n#ifdef ESP32\r\n  server.setFsInfoCallback([](fsInfo_t* fsInfo) {\r\n    fsInfo->fsName = \"LittleFS\";\r\n    fsInfo->totalBytes = LittleFS.totalBytes();\r\n    fsInfo->usedBytes = LittleFS.usedBytes();  \r\n  });\r\n#endif\r\n\r\n  // Init with WebSocket event handler and start server\r\n  server.begin(webSocketEvent);\r\n\r\n  Serial.print(F(\"ESP Web Server started on IP Address: \"));\r\n  Serial.println(server.getServerIP());\r\n  Serial.println(F(\r\n    \"This is \\\"withWebSocket.ino\\\" example.\\n\"\r\n    \"Open /setup page to configure optional parameters.\\n\"\r\n    \"Open /edit page to view, edit or upload example or your custom webserver source files.\"\r\n  ));\r\n\r\n  // Set hostname\r\n  WiFi.setHostname(hostname);\r\n  configTzTime(MYTZ, \"time.google.com\", \"time.windows.com\", \"pool.ntp.org\");\r\n}\r\n\r\n\r\nvoid loop() {\r\n  server.run();  // Handle client requests\r\n\r\n  if (digitalRead(BOOT_BUTTON) == LOW) {\r\n    wsLogPrintf(true, \"Button on GPIO %d clicked\", BOOT_BUTTON);\r\n    delay(1000);\r\n  }\r\n\r\n  // Send ESP system time (epoch) to WS client\r\n  static uint32_t sendToClientTime;\r\n  if (millis() - sendToClientTime > 1000) {\r\n    sendToClientTime = millis();\r\n    time_t now = time(nullptr);\r\n    wsLogPrintf(false, \"{\\\"esptime\\\": %d}\", (int)now);\r\n  }\r\n\r\n  delay(10);\r\n}"
  },
  {
    "path": "keywords.txt",
    "content": "AsyncFsWebServer\tKEYWORD1\nWebServer\t        KEYWORD1\nFSWebServer         KEYWORD1\n\ninit\t            KEYWORD2\nbegin\t            KEYWORD2\nrun\t\t            KEYWORD2\naddHandler\t\t    KEYWORD2\nsetCaptiveWebage\tKEYWORD2\nsetAPmode\t        KEYWORD2\nstartWiFi\t\t\tKEYWORD2\nstartCaptivePortal  KEYWORD2\ngetRequest\t\t    KEYWORD2\naddOptionBox  \t    KEYWORD2\naddJavascript\t    KEYWORD2\naddHTML     \t\tKEYWORD2\naddCSS              KEYWORD2\naddDropdownList     KEYWORD2\naddOption\t\t    KEYWORD2\ngetOptionValue\t    KEYWORD2\nsaveOptionValue\t    KEYWORD2\nenableFsCodeEditor  KEYWORD2\nsetAuthentication   KEYWORD2\nprintFileList       KEYWORD2\nsendOK              KEYWORD2\ngetWebSocket        KEYWORD2\nwsBroadcast         KEYWORD2\nwsBroadcastBinary   KEYWORD2\nupdateDNS           KEYWORD2\nsetFsInfoCallback   KEYWORD2\nsetConfigSavedCallback  KEYWORD2\nsetFirmwareVersion  KEYWORD2\nsetHostname         KEYWORD2\ngetVersion          KEYWORD2\ngetServerIP         KEYWORD2\nisAccessPointMode   KEYWORD2\ngetConfigFile       KEYWORD2\nclearConfigFile     KEYWORD2\ngetConfiFileName    KEYWORD2\nsetSetupPageTitle   KEYWORD2\nsetLogoBase64       KEYWORD2\ngetTaskHandler      KEYWORD2\n\nwebserver           KEYWORD3\nfsInfo_t            KEYWORD1\nFsInfoCallbackF     KEYWORD1\nCallbackF           KEYWORD1\nConfigSavedCallbackF  KEYWORD1\n"
  },
  {
    "path": "library.properties",
    "content": "name=esp-fs-webserver\nversion=3.3.0\nauthor=Tolentino Cotesta <cotestatnt@yahoo.com>\nmaintainer=Tolentino Cotesta <cotestatnt@yahoo.com>\nsentence=Advanced and complete web server with file system management for ESP32 and ESP8266.\nparagraph=V3.x.x might be a breaking release. Check your code using new APIs.\\nESP32/ESP8266 webserver, WiFi manager and web editor all in one Arduino library.\ncategory=Communication\nurl=https://github.com/cotestatnt/esp-fs-webserver\narchitectures=esp32, esp8266\ndepends="
  },
  {
    "path": "partitions.csv",
    "content": "# Name   ,Type ,SubType  ,Offset ,Size  ,Flags\nnvs      ,data ,nvs      ,36K    ,20K   ,\notadata  ,data ,ota      ,56K    ,8K    ,\napp0     ,app  ,ota_0    ,64K    ,1856K  ,\napp1     ,app  ,ota_1    ,1920K  ,1856K ,\nspiffs   ,data ,spiffs   ,3776K  ,256K   ,\ncoredump ,data ,coredump ,4032K  ,64K   ,\n"
  },
  {
    "path": "pio_examples/customOptions/.gitignore",
    "content": ".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",
    "content": "{\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",
    "content": "; PlatformIO Project Configuration File\n;\n;   Build options: build flags, source filter\n;   Upload options: custom upload port, speed and extra flags\n;   Library options: dependencies, extra library storages\n;   Advanced options: extra scripting\n;\n; Please visit documentation for the other options and examples\n; https://docs.platformio.org/page/projectconf.html\n\n[env:esp32-s3-devkitc-1]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32-s3-devkitc-1\nframework = arduino\nlib_extra_dirs = ../../\n\n\n[env:esp32dev]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32dev\nframework = arduino\nlib_extra_dirs = ../../\n\n[env:esp8266-nodemcuv2]\nplatform = espressif8266\nboard = nodemcuv2\nframework = arduino\nupload_speed = 921600\nlib_extra_dirs = ../../"
  },
  {
    "path": "pio_examples/customOptions/src/customOptions.ino",
    "content": "#include <Arduino.h>\n#include <FS.h>\n#include <LittleFS.h>\n#include <FSWebServer.h>  //https://github.com/cotestatnt/async-esp-fs-webserver\n\n// Timezone definition to get properly time from NTP server\n#define MYTZ \"CET-1CEST,M3.5.0,M10.5.0/3\"\nstruct tm Time;\n\n#define FILESYSTEM LittleFS\nFSWebServer server(FILESYSTEM, 80, \"myserver\");\n\n// Define built-in LED if not defined by board (eg. generic dev boards)\n#ifndef LED_BUILTIN\n#define LED_BUILTIN 2\n#endif\n\n#ifndef BOOT_PIN\n#define BOOT_PIN    0\n#endif\n\n// Labels used in /setup webpage for options\n#define LED_LABEL       \"The LED pin number\"\n#define BOOL_LABEL      \"A bool variable\"\n#define BOOL_LABEL2     \"A second bool variable\"\n#define LONG_LABEL      \"A long variable\"\n#define FLOAT_LABEL     \"A float variable\"\n#define STRING_LABEL    \"A String variable\"\n#define DROPDOWN_LABEL  \"Days of week\"\n#define BRIGHTNESS_LABEL \"Brightness\"\n\n// Test \"options\" values\nuint8_t ledPin = LED_BUILTIN;\nbool boolVar = true;\nbool boolVar2 = false;\nuint32_t longVar = 1234567890;\nfloat floatVar = 15.51F;\nString stringVar = \"Test option String\";\n\n// Add a dropdown list box in /setup page\nconst char* days[] = {\"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\", \"Sunday\"};\nuint8_t daySelected = 2;// Default to \"Wednesday\"\nSetupConfig::DropdownList dayOfWeek{ DROPDOWN_LABEL, days, 7, daySelected}; \n\n// Add a slider in /setup page\nSetupConfig::Slider brightness{ BRIGHTNESS_LABEL, 0.0, 100.0, 1.0, 50.0 };\n\nstatic const char reload_btn_htm[] PROGMEM = R\"EOF(\n<div class=\"bar\">\n  <a class=\"btn\" id=\"reload-btn\">Reload options</a>\n</div>\n)EOF\";\n\nstatic const char reload_btn_script[] PROGMEM = R\"EOF(\n/* Add click listener to button */\nconst reloadCfg = () => {\n  console.log('Reload configuration options');\n  fetch('/reload')\n  .then((response) => {\n    if (response.ok) {\n      openModal('Options loaded', 'Options was reloaded from configuration file');\n      return;\n    }\n    throw new Error('Something goes wrong with fetch');\n  })\n  .catch((error) => {\n    openModal('Error', 'Something goes wrong with your request');\n  });\n};\ndocument.getElementById('reload-btn').addEventListener('click', reloadCfg);\n)EOF\";\n\n// Generated from: .\\custom_logo.png\n// Size: 2150 bytes\n// MIME type: image/png\nconst uint8_t custom_logo[] PROGMEM = {\n  0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,\n  0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x08, 0x06, 0x00, 0x00, 0x00, 0xC3, 0x3E, 0x61,\n  0xCB, 0x00, 0x00, 0x00, 0x04, 0x73, 0x42, 0x49, 0x54, 0x08, 0x08, 0x08, 0x08, 0x7C, 0x08, 0x64,\n  0x88, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x03, 0xB1, 0x00, 0x00, 0x03,\n  0xB1, 0x01, 0xF5, 0x83, 0xED, 0x49, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F,\n  0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x00, 0x77, 0x77, 0x77, 0x2E, 0x69, 0x6E, 0x6B, 0x73, 0x63,\n  0x61, 0x70, 0x65, 0x2E, 0x6F, 0x72, 0x67, 0x9B, 0xEE, 0x3C, 0x1A, 0x00, 0x00, 0x07, 0xE3, 0x49,\n  0x44, 0x41, 0x54, 0x78, 0x9C, 0xED, 0x9D, 0x5D, 0x6C, 0x14, 0x55, 0x14, 0xC7, 0xFF, 0x77, 0x66,\n  0xB7, 0x4A, 0x17, 0x02, 0x26, 0x2C, 0x1A, 0x45, 0x02, 0x22, 0x01, 0x0B, 0xBE, 0x98, 0xE0, 0x47,\n  0x78, 0x10, 0xBF, 0xA5, 0x85, 0xB4, 0x10, 0xB7, 0x86, 0xE8, 0x8B, 0xA9, 0x42, 0x5B, 0x82, 0x56,\n  0x49, 0x30, 0x1A, 0xA3, 0x68, 0x7C, 0xF1, 0x23, 0xC4, 0x58, 0x69, 0x81, 0x48, 0x34, 0x31, 0x06,\n  0xD2, 0xE5, 0xCB, 0x96, 0x96, 0x07, 0x88, 0xC2, 0x83, 0x51, 0xD1, 0xF8, 0x22, 0x52, 0xD4, 0xA4,\n  0xA2, 0x54, 0x8C, 0x96, 0x84, 0x8F, 0x96, 0x22, 0xB0, 0x33, 0xC7, 0x87, 0x52, 0xED, 0x6E, 0xE7,\n  0xCE, 0xCC, 0x9D, 0xEE, 0xEC, 0xCE, 0xF6, 0x9E, 0xDF, 0xDB, 0xCE, 0x3D, 0x67, 0xE6, 0xE6, 0x9E,\n  0xFF, 0x9E, 0xFB, 0xB1, 0xA7, 0x1D, 0x80, 0x61, 0x18, 0x86, 0x61, 0x18, 0x86, 0x61, 0x18, 0xBD,\n  0x10, 0x41, 0x9C, 0x52, 0x1D, 0xAB, 0x66, 0x90, 0x30, 0xAB, 0x85, 0x81, 0x2A, 0x90, 0x98, 0x09,\n  0x60, 0x3A, 0x80, 0x44, 0x3E, 0x3B, 0xC6, 0x78, 0x72, 0x01, 0x40, 0x2F, 0x04, 0x9D, 0x00, 0xC4,\n  0x3E, 0xD3, 0xC4, 0x67, 0x3B, 0x1E, 0x69, 0x39, 0xA9, 0x7A, 0x13, 0x25, 0x01, 0xA4, 0x3A, 0xEA,\n  0x6F, 0x82, 0x69, 0xBE, 0x0A, 0xA2, 0x3A, 0x00, 0xA6, 0xEA, 0xC3, 0x98, 0x50, 0xB1, 0x85, 0x10,\n  0xBB, 0x0C, 0x61, 0xAD, 0xDF, 0xB1, 0x64, 0xF3, 0x09, 0xBF, 0x4E, 0xBE, 0x05, 0x50, 0xDB, 0xD5,\n  0x50, 0x43, 0xB6, 0xF8, 0x04, 0x02, 0x13, 0x03, 0x75, 0x8F, 0x29, 0x14, 0xFD, 0x20, 0x7A, 0x32,\n  0xBD, 0xB4, 0xB5, 0xDD, 0x8F, 0xB1, 0xE1, 0xC7, 0x28, 0xD5, 0xD9, 0xF8, 0x1C, 0x91, 0xD8, 0xC5,\n  0xC1, 0x2F, 0x09, 0x26, 0x41, 0x88, 0x3D, 0xB5, 0x9D, 0x6B, 0x9E, 0xF5, 0x63, 0xEC, 0x99, 0x01,\n  0x6A, 0xBB, 0x1A, 0x6A, 0x88, 0xC4, 0x2E, 0xF8, 0x14, 0x0B, 0x13, 0x19, 0x6C, 0x10, 0x2D, 0xF7,\n  0xCA, 0x04, 0xAE, 0x02, 0x58, 0xD1, 0xB5, 0x76, 0xBA, 0x69, 0x5B, 0xDD, 0xFC, 0xCD, 0x2F, 0x59,\n  0xFA, 0xCD, 0x8C, 0x31, 0x6F, 0x47, 0xF5, 0x07, 0xA7, 0x64, 0x06, 0x31, 0x37, 0xEF, 0x18, 0xD9,\n  0x6F, 0x90, 0x24, 0xF8, 0xC2, 0x30, 0x90, 0x98, 0x36, 0x07, 0xE5, 0x53, 0x67, 0xA1, 0xAC, 0x7C,\n  0x32, 0x84, 0x91, 0x7D, 0xAB, 0x93, 0x5F, 0x7F, 0x1A, 0xA8, 0xC7, 0x8C, 0x33, 0x37, 0xDF, 0xFD,\n  0x44, 0xD6, 0x67, 0xB2, 0x33, 0xB8, 0x3C, 0x78, 0x0E, 0x17, 0x4F, 0xFF, 0x8A, 0x81, 0xBF, 0x7F,\n  0x01, 0xD9, 0xB6, 0x93, 0xDB, 0x24, 0x3B, 0x66, 0xBD, 0x0E, 0xE0, 0x19, 0xD9, 0x7D, 0xA5, 0x19,\n  0x20, 0xD5, 0xB1, 0x6A, 0x06, 0x8C, 0x58, 0x0F, 0x1C, 0x56, 0xFB, 0x66, 0x59, 0x39, 0x92, 0x73,\n  0xEF, 0x43, 0x3C, 0x31, 0x45, 0xDA, 0x61, 0x16, 0x40, 0x7E, 0xC9, 0x15, 0xC0, 0x48, 0xAE, 0x5C,\n  0x38, 0x83, 0xBE, 0x9F, 0x0E, 0xC1, 0xBA, 0x3C, 0xE8, 0xD4, 0x6C, 0x59, 0xC2, 0x9C, 0xB9, 0xBB,\n  0xB2, 0xB9, 0xD7, 0xA9, 0x51, 0x3A, 0xAF, 0x0B, 0x23, 0x5E, 0x03, 0x87, 0xE0, 0x0B, 0xC3, 0xF0,\n  0x0C, 0x3E, 0x53, 0x58, 0xE2, 0x89, 0xEB, 0x90, 0x9C, 0xBB, 0x18, 0xC2, 0x70, 0x0C, 0xA7, 0x19,\n  0xA3, 0x4C, 0xB5, 0xCC, 0x57, 0x2A, 0x00, 0x02, 0x2D, 0x71, 0xBA, 0x9E, 0x98, 0x36, 0x87, 0x83,\n  0x1F, 0x41, 0xE2, 0x89, 0xEB, 0x90, 0x48, 0xDE, 0xEA, 0xD8, 0x46, 0x24, 0x2A, 0x65, 0x7E, 0xF2,\n  0x35, 0x00, 0xE1, 0x56, 0xA7, 0x09, 0x22, 0x31, 0x75, 0x66, 0xD6, 0xE7, 0x8B, 0x67, 0xFE, 0xC0,\n  0x99, 0x5F, 0x8F, 0xC8, 0xD2, 0x8F, 0x27, 0xDB, 0x17, 0x9D, 0xCD, 0xFA, 0xBC, 0xF2, 0xCB, 0x29,\n  0x91, 0x6A, 0x8F, 0x0A, 0xB9, 0x53, 0x6A, 0xAC, 0xAC, 0x1C, 0x53, 0x6E, 0xB9, 0x0B, 0x13, 0xA6,\n  0xDC, 0xF8, 0xDF, 0xB5, 0x44, 0x72, 0x16, 0x06, 0xFE, 0xFA, 0x79, 0xB4, 0xB3, 0xC0, 0x6C, 0xD9,\n  0x7D, 0xDD, 0xB6, 0x76, 0x37, 0x38, 0x5D, 0x8C, 0x97, 0x67, 0x0F, 0xD0, 0xD9, 0x31, 0x04, 0x9F,\n  0x09, 0x4E, 0xE6, 0xF2, 0x20, 0xCE, 0xF4, 0x7C, 0x93, 0x75, 0x2D, 0x36, 0x61, 0xB2, 0xB3, 0x31,\n  0xE1, 0x26, 0xD9, 0x7D, 0xE4, 0x19, 0x40, 0xBA, 0xFA, 0xCF, 0x76, 0xC9, 0x68, 0x12, 0xFC, 0xDC,\n  0x4C, 0xA1, 0x4A, 0x18, 0x99, 0x25, 0xF7, 0x8B, 0x67, 0x98, 0x71, 0x67, 0x43, 0x97, 0x6D, 0x3C,\n  0x1F, 0xEE, 0x68, 0x8E, 0xEB, 0x39, 0x40, 0x21, 0xF0, 0xFA, 0x66, 0x14, 0xBB, 0x7D, 0xBC, 0xC3,\n  0x19, 0x40, 0x73, 0x94, 0x33, 0x00, 0x1F, 0xF0, 0x44, 0x8B, 0xB1, 0xC6, 0x83, 0x33, 0x80, 0xE6,\n  0x48, 0x05, 0x70, 0x6D, 0xAC, 0x7C, 0xA2, 0x6D, 0x8A, 0x05, 0x00, 0x9A, 0x00, 0x1C, 0x2D, 0x5C,\n  0x97, 0x98, 0x3C, 0xF1, 0x03, 0x80, 0x26, 0xDB, 0x14, 0x0B, 0xAE, 0x8D, 0x95, 0x4B, 0x77, 0x01,\n  0xBE, 0x0A, 0x42, 0x52, 0x6D, 0x29, 0x53, 0x24, 0xA6, 0xD6, 0x13, 0x8C, 0x8D, 0x00, 0x95, 0xE5,\n  0xAD, 0x8B, 0x25, 0x44, 0x14, 0xB7, 0x81, 0x12, 0x2E, 0x41, 0x88, 0xE7, 0xE7, 0x1F, 0x49, 0x6E,\n  0xD9, 0xB0, 0x61, 0x83, 0xE3, 0x2F, 0x44, 0x23, 0x51, 0x2B, 0x09, 0xEB, 0xAA, 0xBF, 0x1F, 0x64,\n  0xEE, 0xF7, 0x23, 0x82, 0x8F, 0xEE, 0x7D, 0x45, 0xE5, 0xD6, 0x78, 0x6B, 0x4B, 0x97, 0x92, 0x7D,\n  0xA9, 0xF3, 0xE2, 0x6A, 0xE9, 0xE9, 0xAC, 0x23, 0x4F, 0x1D, 0x7E, 0xD3, 0x8F, 0xD9, 0x25, 0x41,\n  0x58, 0xD2, 0xB6, 0xB4, 0xE5, 0x0B, 0xBF, 0xF7, 0x55, 0x5A, 0x03, 0xA4, 0x2B, 0x37, 0x7F, 0x0E,\n  0x41, 0xEB, 0x54, 0x7C, 0x98, 0xC2, 0x21, 0x40, 0x4D, 0x2A, 0xC1, 0x07, 0x82, 0x2C, 0x02, 0x07,\n  0xFA, 0x5A, 0x41, 0xF8, 0x51, 0xD9, 0x8F, 0x09, 0x9B, 0x1F, 0x2A, 0xBE, 0xBD, 0x7E, 0xAB, 0xAA,\n  0x93, 0xB2, 0x00, 0xD2, 0xB5, 0x69, 0x4B, 0x08, 0x6C, 0x53, 0xF5, 0x63, 0xC2, 0x45, 0x40, 0x7C,\n  0xE8, 0x67, 0xCE, 0xCF, 0x25, 0xD0, 0x36, 0x50, 0x90, 0x7D, 0x20, 0x88, 0x1F, 0x13, 0x1E, 0x24,\n  0x32, 0x07, 0x83, 0xF8, 0x05, 0x12, 0xC0, 0x45, 0xEB, 0xF2, 0x6F, 0x41, 0xFC, 0x98, 0x10, 0x99,\n  0x60, 0xFE, 0x1E, 0xC4, 0x2D, 0x90, 0x00, 0x26, 0xC5, 0x13, 0x81, 0xFE, 0xA2, 0x88, 0x09, 0x8F,\n  0x4B, 0xE7, 0xFF, 0x09, 0x14, 0x93, 0x40, 0x02, 0xC8, 0xD8, 0x57, 0x66, 0x04, 0xF1, 0x63, 0xC2,\n  0xE3, 0x9A, 0x78, 0xFC, 0xE6, 0x20, 0x7E, 0x81, 0x7E, 0x0D, 0xB4, 0x85, 0x78, 0xD8, 0xCB, 0xC6,\n  0xE7, 0xBE, 0xF5, 0x3F, 0xE6, 0xE1, 0x0E, 0xD7, 0xF6, 0x83, 0x87, 0x0E, 0x29, 0xDD, 0xAF, 0xD8,\n  0x3C, 0xB8, 0x78, 0xB1, 0x6B, 0xBB, 0xEA, 0xF8, 0x78, 0x42, 0xE6, 0x43, 0x00, 0x8E, 0xA9, 0xBA,\n  0x29, 0x67, 0x80, 0x54, 0x5B, 0xCA, 0x04, 0x8C, 0x3A, 0x55, 0x3F, 0x26, 0x74, 0x9E, 0x1E, 0x8A,\n  0x8D, 0x1A, 0xCA, 0x02, 0x10, 0x89, 0x69, 0x6B, 0x00, 0xAA, 0x50, 0xF5, 0x63, 0x42, 0x67, 0x81,\n  0x48, 0x4C, 0xAD, 0x57, 0x75, 0x52, 0x12, 0x40, 0xED, 0xBE, 0x86, 0x07, 0x09, 0xF4, 0xAE, 0xEA,\n  0x43, 0x98, 0xC2, 0x40, 0x30, 0x36, 0x3E, 0xB6, 0x7F, 0xCD, 0x03, 0x2A, 0x3E, 0xBE, 0xD6, 0x00,\n  0x43, 0x3F, 0x06, 0x4D, 0x5B, 0x73, 0x35, 0xF8, 0x92, 0xC2, 0x33, 0xA6, 0xF8, 0x50, 0x99, 0xB0,\n  0x45, 0x57, 0xAA, 0xAB, 0x71, 0x1D, 0x06, 0xFA, 0x5A, 0xD3, 0xB5, 0x69, 0xCB, 0xCB, 0x43, 0x2A,\n  0x80, 0xD4, 0x17, 0x8D, 0x13, 0x8D, 0x0B, 0xF6, 0xCC, 0xA1, 0x05, 0x9F, 0x51, 0x47, 0x9C, 0xF6,\n  0x4B, 0x04, 0x2A, 0x03, 0xA1, 0x19, 0xE5, 0xC9, 0xFA, 0xDA, 0xCE, 0xC6, 0x6D, 0x82, 0xEC, 0x03,\n  0x76, 0xC2, 0x38, 0x91, 0xBE, 0xAF, 0x65, 0xC0, 0xC9, 0x5A, 0x9E, 0x01, 0x06, 0xD1, 0x6F, 0x8B,\n  0xE1, 0x19, 0x82, 0xC2, 0xE8, 0x69, 0x5E, 0x38, 0xB0, 0xFD, 0xED, 0xAC, 0xCF, 0x0F, 0xAD, 0x5C,\n  0x5F, 0xA4, 0x9E, 0x44, 0x0C, 0x81, 0xF9, 0x04, 0x6C, 0x24, 0x61, 0x00, 0x83, 0x57, 0xAF, 0x38,\n  0xC0, 0x15, 0x41, 0x9A, 0x13, 0x5A, 0x55, 0xB0, 0x72, 0x3D, 0xC0, 0x71, 0xBD, 0xEA, 0x01, 0x54,\n  0xC7, 0x27, 0xEF, 0xE7, 0x06, 0x57, 0xE1, 0x0C, 0xA0, 0x39, 0xCA, 0x19, 0xC0, 0xED, 0xCF, 0x94,\n  0x8B, 0x81, 0x2E, 0x73, 0xBE, 0xDF, 0x71, 0x57, 0xAD, 0x12, 0xE6, 0x0C, 0xA0, 0x39, 0x2C, 0x00,\n  0xCD, 0x61, 0x01, 0x68, 0xCE, 0x98, 0x77, 0x01, 0xD2, 0x39, 0x47, 0x71, 0x95, 0xCB, 0xB8, 0x23,\n  0x1B, 0xE7, 0xB1, 0xAE, 0xC9, 0x38, 0x03, 0x68, 0x4E, 0x68, 0xE7, 0x00, 0xF9, 0xAE, 0x07, 0x18,\n  0x6F, 0x84, 0xB5, 0xAF, 0x57, 0x85, 0x33, 0x80, 0xE6, 0xB0, 0x00, 0x34, 0x87, 0x05, 0xA0, 0x39,\n  0x2C, 0x00, 0xCD, 0x61, 0x01, 0x68, 0x4E, 0xD1, 0xFF, 0x47, 0x50, 0xA9, 0x31, 0xDE, 0xEA, 0x0F,\n  0x38, 0x03, 0x68, 0x0E, 0xD7, 0x03, 0x14, 0x09, 0xAE, 0x07, 0x60, 0x22, 0xC1, 0x98, 0x33, 0x40,\n  0xD4, 0xEA, 0x03, 0xC6, 0x2B, 0x61, 0x8D, 0x33, 0x67, 0x00, 0xCD, 0x61, 0x01, 0x68, 0x0E, 0x0B,\n  0x40, 0x73, 0xB4, 0xAD, 0x07, 0xF0, 0xDA, 0xCF, 0xE7, 0xB6, 0x17, 0x1B, 0xAE, 0x07, 0x60, 0x42,\n  0x81, 0xEB, 0x01, 0x8A, 0x04, 0xD7, 0x03, 0x30, 0x91, 0x40, 0xDB, 0xDF, 0x02, 0xBC, 0xCE, 0xF0,\n  0x4B, 0xFD, 0x8C, 0xDF, 0x2F, 0x9C, 0x01, 0x34, 0x87, 0x05, 0xA0, 0x39, 0x2C, 0x00, 0xCD, 0x61,\n  0x01, 0x68, 0x0E, 0x0B, 0x40, 0x73, 0x4A, 0xA6, 0x1E, 0xC0, 0xEB, 0xFF, 0xEE, 0x95, 0x1A, 0x5C,\n  0x0F, 0xC0, 0x44, 0x02, 0xAE, 0x07, 0x28, 0x11, 0xB8, 0x1E, 0x80, 0x09, 0x05, 0x16, 0x80, 0xE6,\n  0xB0, 0x00, 0x34, 0x47, 0xDB, 0x7A, 0x80, 0x52, 0x83, 0xEB, 0x01, 0x98, 0x50, 0x88, 0x4C, 0x3D,\n  0x00, 0xE6, 0xE5, 0xE7, 0xB9, 0xB9, 0xFB, 0x6B, 0x59, 0x3F, 0xBC, 0xEC, 0xF2, 0x75, 0x1F, 0x19,\n  0x4F, 0x1D, 0xFE, 0xDE, 0x97, 0x5D, 0xD8, 0x70, 0x06, 0xD0, 0x1C, 0x16, 0x80, 0xE6, 0xB0, 0x00,\n  0x34, 0x87, 0x05, 0xA0, 0x39, 0xE3, 0x4A, 0x00, 0x77, 0x26, 0xFD, 0xBD, 0xD2, 0xC0, 0xAF, 0x5D,\n  0xA1, 0xEE, 0x5D, 0x4C, 0xF2, 0xBE, 0x0B, 0xC8, 0x7D, 0xCD, 0x7A, 0xA1, 0x5E, 0x9B, 0x7E, 0x67,\n  0xB2, 0x02, 0xAB, 0x6E, 0xAB, 0xC9, 0xBA, 0x66, 0xD1, 0xE8, 0x37, 0xA9, 0xFA, 0xB5, 0xB3, 0xC8,\n  0x86, 0x29, 0xFE, 0xFF, 0x7E, 0x0C, 0xFB, 0x1C, 0xE9, 0x3B, 0x16, 0xC8, 0x4E, 0x86, 0xD7, 0x78,\n  0x85, 0x3D, 0x9E, 0xE3, 0x22, 0x03, 0x2C, 0xBC, 0x1A, 0xD4, 0x91, 0x81, 0xB0, 0xC9, 0xC6, 0xB6,\n  0xE3, 0xED, 0x81, 0xEC, 0x00, 0x60, 0xF3, 0xB1, 0x3D, 0x59, 0xC2, 0x30, 0x85, 0x81, 0xD5, 0xB7,\n  0xD5, 0xE0, 0x9E, 0xEB, 0x6F, 0x0F, 0x64, 0x17, 0x55, 0x4A, 0x5E, 0x00, 0x0B, 0x93, 0x15, 0x58,\n  0xED, 0x10, 0xD4, 0x0F, 0x8F, 0xB7, 0xE3, 0xAB, 0xBF, 0x8F, 0x2A, 0xDB, 0x0D, 0xF3, 0xDD, 0xE9,\n  0xEE, 0x51, 0xC1, 0x35, 0x84, 0x81, 0xA7, 0xE7, 0x2E, 0xCB, 0x0A, 0xAE, 0x5F, 0xBB, 0xA8, 0x52,\n  0xF2, 0x02, 0xC8, 0x0D, 0xAA, 0x45, 0x36, 0xB6, 0x74, 0xEF, 0x1D, 0x15, 0x54, 0xBF, 0x76, 0x23,\n  0xF9, 0xEE, 0x74, 0x37, 0xB6, 0x76, 0xEF, 0x1D, 0x15, 0xDC, 0xBA, 0xB9, 0xCB, 0x02, 0xD9, 0x45,\n  0x91, 0xBC, 0xAF, 0x01, 0x0A, 0x35, 0xE7, 0x0F, 0x33, 0x32, 0xA8, 0x00, 0xB0, 0xB5, 0x7B, 0xAF,\n  0xE3, 0xFC, 0xEB, 0xD7, 0x2E, 0x97, 0x61, 0x9B, 0x86, 0x8A, 0x15, 0xD2, 0x7B, 0xA9, 0xD8, 0xE5,\n  0xE2, 0x35, 0x5E, 0x61, 0x8F, 0x67, 0xC9, 0x67, 0x80, 0x5C, 0xFC, 0x2E, 0xBE, 0xFC, 0xDA, 0x85,\n  0x75, 0xCF, 0xA8, 0x30, 0xEE, 0x04, 0xC0, 0xA8, 0xC1, 0x02, 0xD0, 0x1C, 0x16, 0x80, 0xE6, 0xB0,\n  0x00, 0x34, 0xC7, 0xF1, 0x6D, 0x92, 0x00, 0x90, 0xEA, 0x6C, 0x74, 0x7C, 0x5D, 0x68, 0x54, 0xAA,\n  0x80, 0xDF, 0x36, 0x37, 0xB9, 0xB6, 0x5B, 0xDF, 0x9E, 0x73, 0x6D, 0x37, 0x17, 0x4E, 0x2E, 0xAA,\n  0xFF, 0x4B, 0x77, 0xBC, 0xEC, 0xDA, 0x1E, 0x14, 0x59, 0xE5, 0x50, 0xBA, 0xAA, 0x85, 0xDF, 0x1C,\n  0xCA, 0x8C, 0x86, 0x05, 0xA0, 0x39, 0x2C, 0x00, 0xCD, 0x51, 0x3E, 0x09, 0x54, 0x7D, 0x33, 0x65,\n  0x68, 0x2C, 0x2A, 0x76, 0x07, 0xC6, 0x46, 0x54, 0xC6, 0x91, 0x33, 0x80, 0xE6, 0xB0, 0x00, 0x34,\n  0x87, 0x05, 0xA0, 0x39, 0x2C, 0x00, 0xCD, 0x61, 0x01, 0x68, 0x0E, 0x0B, 0x40, 0x73, 0x58, 0x00,\n  0x9A, 0x23, 0x3F, 0x07, 0x20, 0x0C, 0x40, 0x60, 0x62, 0x58, 0x0F, 0x96, 0x9D, 0x4D, 0x0F, 0x23,\n  0xFB, 0x2D, 0x42, 0x17, 0xF2, 0x3C, 0x3E, 0xE7, 0x65, 0x0D, 0xF2, 0x0C, 0x20, 0x70, 0x4A, 0xE1,\n  0x01, 0x4C, 0xB4, 0x91, 0xC6, 0xD2, 0x6D, 0x0A, 0xE8, 0x09, 0xA1, 0x23, 0x4C, 0x31, 0x20, 0x79,\n  0x2C, 0xDD, 0x32, 0x40, 0x67, 0x28, 0x9D, 0x61, 0x0A, 0x8F, 0x21, 0xF6, 0xC9, 0x9A, 0xA4, 0x6B,\n  0x00, 0xD3, 0xC4, 0x67, 0x56, 0x06, 0xEF, 0x01, 0x30, 0x43, 0xE9, 0xD4, 0x18, 0xF1, 0xAE, 0x96,\n  0xF5, 0x68, 0xFF, 0xD2, 0xEB, 0x09, 0x21, 0xFB, 0x17, 0x8E, 0x8C, 0x05, 0xA3, 0x43, 0xD6, 0x28,\n  0xCD, 0x00, 0x3B, 0x1E, 0x69, 0x39, 0x09, 0xE0, 0xE3, 0x30, 0x7A, 0xC4, 0x14, 0x0E, 0x01, 0x6C,\n  0xDB, 0x5D, 0xD9, 0xDC, 0x2B, 0x6B, 0x77, 0xDF, 0x06, 0xDA, 0xF6, 0x6B, 0x00, 0xFA, 0xF3, 0xDD,\n  0x29, 0xA6, 0x60, 0x9C, 0xCF, 0x98, 0x99, 0xD7, 0xDD, 0x0C, 0x5C, 0x05, 0x90, 0x5E, 0xB6, 0xF9,\n  0x0F, 0x12, 0xF4, 0x38, 0x00, 0x2B, 0xAF, 0xDD, 0x62, 0x0A, 0x81, 0x2D, 0x04, 0x9E, 0xDC, 0xFD,\n  0xE8, 0xD6, 0x3F, 0xDD, 0x8C, 0x3C, 0x0F, 0x82, 0x76, 0x56, 0xB6, 0xEE, 0x17, 0x10, 0x2F, 0x00,\n  0x18, 0xFD, 0x27, 0xB4, 0x4C, 0x54, 0xB1, 0x89, 0xA8, 0xA9, 0xAD, 0xB2, 0x45, 0x3A, 0xF7, 0x0F,\n  0xE3, 0xEB, 0x24, 0xB0, 0xAD, 0x6A, 0xD3, 0xFB, 0x20, 0x5A, 0x0E, 0x9E, 0x0E, 0x4A, 0x81, 0xF3,\n  0x04, 0x51, 0xBD, 0x73, 0x69, 0x6B, 0xB3, 0x1F, 0x63, 0xDF, 0x47, 0xC1, 0xE9, 0xA5, 0xAD, 0xED,\n  0x10, 0xE6, 0x6C, 0x02, 0xDE, 0x07, 0x90, 0x09, 0xDC, 0x3D, 0x26, 0x2C, 0x6C, 0x80, 0x3E, 0xB1,\n  0xCC, 0xCC, 0xBC, 0x9D, 0x55, 0x9B, 0xA4, 0xDB, 0xBE, 0x5C, 0x94, 0x4A, 0xC2, 0xD2, 0x95, 0xCD,\n  0x7D, 0x00, 0x9E, 0x5B, 0xD1, 0xB5, 0xF6, 0x9D, 0x18, 0x65, 0xAA, 0x09, 0xA2, 0x0A, 0xC0, 0x2C,\n  0x10, 0xA6, 0x87, 0x79, 0x6C, 0xCC, 0x38, 0x30, 0x74, 0x54, 0xDF, 0x0B, 0x42, 0x0F, 0x01, 0x9D,\n  0xB6, 0x61, 0xB6, 0xBB, 0xAD, 0xF6, 0x19, 0x86, 0x61, 0x18, 0x86, 0x61, 0x18, 0x86, 0x61, 0x86,\n  0xF8, 0x17, 0xDE, 0x1A, 0xA1, 0xFA, 0xD8, 0xAA, 0xBF, 0x34, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45,\n  0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82\n};\n\n\n\n/////////// Callback: notify user when the configuration file is saved  /////////////\nvoid onConfigSaved(const char* path) {\n  Serial.print(\"\\n[Config] File salvato: \");\n  Serial.println(path); \n}\n\n////////////////////////////////  Filesystem  /////////////////////////////////////////\nbool startFilesystem() {\n  if (FILESYSTEM.begin()){\n    server.printFileList(FILESYSTEM, \"/\", 2);\n    return true;\n  }\n  else {\n    Serial.println(\"ERROR on mounting filesystem. It will be reformatted!\");\n    FILESYSTEM.format();\n    ESP.restart();\n  }\n  return false;\n}\n\n////////////////////  Load application options from filesystem  ////////////////////\nbool loadOptions() {\n  if (FILESYSTEM.exists(server.getConfiFileName())) {\n    server.getOptionValue(LED_LABEL, ledPin);\n    server.getOptionValue(BOOL_LABEL, boolVar);\n    server.getOptionValue(BOOL_LABEL2, boolVar2);\n    server.getOptionValue(LONG_LABEL, longVar);\n    server.getOptionValue(FLOAT_LABEL, floatVar);\n    server.getOptionValue(STRING_LABEL, stringVar);\n    server.getDropdownSelection(dayOfWeek);    \n    server.getSliderValue(brightness);\n    server.closeSetupConfiguration();  // Close configuration to free resources\n\n    Serial.println(\"\\nThis are the current values stored: \\n\");\n    Serial.print(\"LED pin value: \"); Serial.println(ledPin);    \n    Serial.print(\"Bool value: \"); Serial.println(boolVar ? \"true\" : \"false\");\n    Serial.print(\"Bool value2: \"); Serial.println(boolVar2 ? \"true\" : \"false\");\n    Serial.print(\"Long value: \"); Serial.println(longVar);\n    Serial.print(\"Float value: \"); Serial.println(floatVar);\n    Serial.print(\"String value: \"); Serial.println(stringVar);\n    Serial.print(\"Dropdown selected value: \"); Serial.println(days[dayOfWeek.selectedIndex]);\n    Serial.print(\"Slider value: \"); Serial.println(brightness.value);\n    return true;\n  }\n  else\n    Serial.println(F(\"Config file not exist\"));\n  return false;\n}\n\n\n////////////////////////////  HTTP Request Handlers  ////////////////////////////////////\nvoid handleLoadOptions() {\n  server.send(200, \"text/plain\", \"Options loaded\");\n  loadOptions();\n  Serial.println(\"Application option loaded after web request\");\n}\n\n\nvoid setup() {\n  Serial.begin(115200);\n\n  // FILESYSTEM INIT\n  if (startFilesystem()){\n    // Load configuration (if not present, default will be created when webserver will start)\n    loadOptions();\n  }\n\n  // Firmware version is set to compile time internally with a macro, but you can set a custom string\n  // Custom firmware version -> Major.Minor.Build\n  // Build is a packed 32-bit value; for display use unpack or keep compact.\n  String version = \"1.0.\" + String(BUILD_TIMESTAMP);\n  server.setFirmwareVersion(version);\n\n  // Set custom title for /setup page\n  server.setSetupPageTitle(\"Custom Options Example\");\n\n  // Set custom logo for /setup page (uncomment one of the lines below to test)\n  // server.setSetupPageLogo(custom_logo_svg);  // Use SVG text directly\n  server.setSetupPageLogo(custom_logo, sizeof(custom_logo), \"image/png\");  // Use binary image data (PNG, JPEG, GIF, or gzipped SVG)\n\n  // Try to connect to WiFi (will start AP if not connected after timeout)\n  if (!server.startWiFi(10000)) {\n    Serial.println(\"\\nWiFi not connected! Starting AP mode...\");\n    WiFiConnectParams params (\"ESP_AP\", \"123456789\");            \n    params.config.local_ip = IPAddress(192, 168, 1, 1);\n    params.config.gateway = IPAddress(192, 168, 1, 1);\n    params.config.subnet = IPAddress(255, 255, 255, 0);    \n    server.startCaptivePortal(params, \"/setup\");\n  }\n\n  // Add custom page handler\n  server.on(\"/reload\", HTTP_GET, handleLoadOptions);\n\n  // Configure /setup page and start Web Server\n  server.addOptionBox(\"My Options\");\n\n  // boolean options can be grouped or left inline by passing `grouped`\n  // as the fourth argument (hidden is the third, preserving old sketches):\n  // grouped = true  (default) → all switches collected in a row\n  // grouped = false            → option appears exactly where added\n  // server.addOption(BOOL_LABEL, boolVar);               // grouped\n  // server.addOption(BOOL_LABEL, boolVar, false);        // hidden=false, grouped=true\n  // server.addOption(BOOL_LABEL, boolVar, false, false); // not grouped\n\n  server.addHTML(reload_btn_htm, \"buttons\", /*overwrite*/ false);\n  server.addJavascript(reload_btn_script, \"script\", /*overwrite*/ false);\n  server.addOption(BOOL_LABEL, boolVar, false, false);\n  server.addOption(LED_LABEL, ledPin);\n  server.addComment(LED_LABEL, \"Select the GPIO for the LED\");\n  server.addOption(LONG_LABEL, longVar);\n  server.addComment(LONG_LABEL, \"Large integer value\");\n  server.addOption(FLOAT_LABEL, floatVar, 1.0, 100.0, 0.01);\n  server.addComment(FLOAT_LABEL, \"Adjust between 1 and 100\");\n  server.addOption(STRING_LABEL, stringVar);\n  server.addComment(STRING_LABEL, \"Small text string\");\n  server.addOption(BOOL_LABEL2, boolVar2, false, false);\n  server.addComment(BOOL_LABEL2, \"Second boolean setting\");\n  server.addDropdownList(dayOfWeek);\n  server.addSlider(brightness);  \n\n  bool boolLED1ENABLE, boolLED2ENABLE, boolLED3ENABLE, boolLED4ENABLE;\n  byte byteLED1PIN, byteLED2PIN, byteLED3PIN, byteLED4PIN;\n  server.addOptionBox(\"LEDs\");\n  server.addOption(\"LED1 enable\", boolLED1ENABLE, \"Enable LED1\");\n  server.addOption(\"LED1 PIN\", byteLED1PIN);\n  server.addOption(\"LED2 enable\", boolLED2ENABLE , \"Enable LED2\");\n  server.addOption(\"LED2 PIN\", byteLED2PIN);\n  server.addOption(\"LED3 enable\", boolLED3ENABLE, \"Enable LED3\");\n  server.addOption(\"LED3 PIN\", byteLED3PIN);\n  server.addOption(\"LED4 enable\", boolLED4ENABLE, \"Enable LED4\");\n  server.addOption(\"LED4 PIN\", byteLED4PIN);\n\n\n  // Enable ACE FS file web editor and add FS info callback function\n  server.enableFsCodeEditor();\n\n  // set /setup and /edit page authentication\n  // server.setAuthentication(\"admin\", \"admin\");\n\n  // Inform user when config.json is saved via /edit or /upload\n  server.setConfigSavedCallback(onConfigSaved);\n\n  // Start server\n  server.begin();\n  Serial.print(F(\"\\nESP Web Server started on IP Address: \"));\n  Serial.println(server.getServerIP());\n  Serial.println(F(\n      \"\\nThis is \\\"customOptions.ino\\\" example.\\n\"\n      \"Open /setup page to configure optional parameters.\\n\"\n      \"Open /edit page to view, edit or upload example or your custom webserver source files.\"\n  ));\n\n  Serial.print(F(\"Compile time (default firmware version): \"));\n  Serial.println(BUILD_TIMESTAMP);\n}\n\nvoid loop() {\n  server.run(); // Handle client requests\n\n  // Keep BOOT_PIN pressed 5 seconds to clear application options\n  static uint32_t buttonPressStart = 0;\n  static bool buttonPressed = false;\n  \n  if (digitalRead(BOOT_PIN) == LOW) {\n    if (!buttonPressed) {\n      buttonPressed = true;\n      buttonPressStart = millis();\n    } \n    else if (millis() - buttonPressStart >= 5000) {\n      Serial.println(\"\\nClearing application options...\");\n      server.clearConfigFile();\n      delay(1000);\n      ESP.restart();\n    }\n  } else {\n    buttonPressed = false;\n  }\n\n  // Nothing to do here, just a small delay for task yield\n  delay(10);  \n}\n"
  },
  {
    "path": "pio_examples/esp32-p4/.gitignore",
    "content": ".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",
    "content": "# Name,   Type, SubType, Offset,  Size, Flags\nnvs,      data, nvs,     0x9000,  0x5000,\notadata,  data, ota,     0xe000,  0x2000,\napp0,     app,  ota_0,   0x10000, 0x300000,\napp1,     app,  ota_1,   0x310000,0x300000,\nspiffs,   data, spiffs,  0x610000,0x9E0000,\ncoredump, data, coredump,0xFF0000,0x10000,\n"
  },
  {
    "path": "pio_examples/esp32-p4/platformio.ini",
    "content": "; PlatformIO Project Configuration File\n;\n;   Build options: build flags, source filter\n;   Upload options: custom upload port, speed and extra flags\n;   Library options: dependencies, extra library storages\n;   Advanced options: extra scripting\n;\n; Please visit documentation for the other options and examples\n; https://docs.platformio.org/page/projectconf.html\n\n[env]\nmonitor_speed = 115200\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.37/platform-espressif32.zip\nframework = arduino\n\n; sanity check\n[env:m5stack-atoms3]\nboard = m5stack-atoms3\nlib_extra_dirs = ../../\nlib_ignore = pio_examples\nbuild_flags =\n\t-DARDUINO_USB_CDC_ON_BOOT=1\n\t-DCORE_DEBUG_LEVEL=5\n\n[env:esp32p4_waveshare_devkit]\nboard = esp32-p4-evboard\nbuild_type = debug\ndebug_tool = esp-builtin\ndebug_init_break = tbreak setup\nboard_build.partitions = app3M_spiffs9M_16MB.csv\nbuild_flags =\n\t; -DCORE_DEBUG_LEVEL=5\nlib_extra_dirs = ../../\nlib_ignore = pio_examples\n\n\n[env:m5stack-tab5-p4]\nboard = m5stack-tab5-p4\nbuild_type = debug\nboard_build.partitions =  app3M_spiffs9M_16MB.csv\ndebug_tool = esp-builtin\ndebug_init_break = tbreak setup\nbuild_flags =\n\t-DUSE_M5UNIFIED\n\t; -DCORE_DEBUG_LEVEL=5\nlib_extra_dirs = ../../\nlib_ignore = pio_examples\nlib_deps =\n    https://github.com/M5Stack/M5Unified.git\n    https://github.com/M5Stack/M5GFX.git"
  },
  {
    "path": "pio_examples/esp32-p4/src/index_htm.h",
    "content": "#pragma once\n#include <Arduino.h>\n\ninline const char homepage[] PROGMEM = R\"EOF(\n<!DOCTYPE html>\n<html>\n  <head>\n    <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\">\n    <title>WebSocket test page</title>\n    \n    <style type=\"text/css\" media=\"screen\">\n      body {width: 70%; margin: auto; background-color: black; color: #55ff55; font-family: monospace; text-align: center; }\n      .box {border: 1px solid white; border-radius: 10px; padding: 5px; margin: 10px; }\n      .output {color: white; }\n      #log {text-align: left; margin: 20px; max-height: 500px; overflow: auto; }\n      #input_div, #input_el {line-height: 13px; color: #AAA;}\n      #input_el { width:97%; background-color: rgba(0,0,0,0); border: 0px;}\n      #input_el:focus {outline: none; }\n      #icon-setup{float: left;margin: 20px}\n    </style>\n    \n    <script type=\"text/javascript\">\n      var ws = null;\n      function ge(s){ return document.getElementById(s);} // Shorthand for get element\n      function ce(s){ return document.createElement(s);}  // Shorthand for create element\n      function stb(){ window.scrollTo(0, document.body.scrollHeight || document.documentElement.scrollHeight); } // Scroll to bottom\n      \n      // This function will add a message to the HTML element with id 'log'\n      function addMessage(m){\n        var msg = ce(\"div\");\n        msg.innerText = m;\n        ge(\"log\").appendChild(msg);\n        stb();  \n      }\n      \n      // If msg is a obj data tyep, handle it (just timestamp in this example) otherwise add to log console\n      function parseMessage(msg) {\n        try {\n          const obj = JSON.parse(msg);\n          if (typeof obj === 'object' && obj !== null) {\n            if (obj.esptime !== null) {\n              var date = new Date(0); // The 0 sets the date to epoch\n              if( date.setUTCSeconds(obj.esptime))\n                document.getElementById(\"esp-time\").innerHTML = date;\n            }\n          }\n        } catch {\n          addMessage(msg);\n        }\n      }\n      \n      // Configure and start WebSocket client\n      function startSocket(){\n        ws = new WebSocket('ws://' + location.hostname + ':81/');\n        ws.binaryType = \"arraybuffer\";\n        ws.onopen = function(e){\n          addMessage(\"WebSocket client connected to \" + 'ws://'+document.location.host+'/ws');\n        };\n        ws.onclose = function(e){\n          addMessage(\"WebSocket client disconnected\");\n        };\n        ws.onerror = function(e){\n          console.log(\"ws error\", e);\n          addMessage(\"Error\");\n        };\n        ws.onmessage = function(e){\n          parseMessage(e.data)\n        };\n        // Add event \"keydown\" listener to input box\n        ge(\"input_el\").onkeydown = function(e){\n          stb();\n          if(e.keyCode == 13 && ge(\"input_el\").value != \"\"){\n            ws.send(ge(\"input_el\").value);\n            ge(\"input_el\").value = \"\";\n          }\n        }\n      }\n  \n      // When page is fully loaded start connection\n      function onBodyLoad(){\n        startSocket();\n      }\n    </script>\n  </head>\n  <body id=\"body\" onload=\"onBodyLoad()\">\n    \n    <div class=icon>\n      <a id=icon-setup href=/setup>\n        <svg width=\"48\" height=\"48\" fill=#55ff55 viewBox=\"0 0 24 24\">\n          <path d=\"M12,8A4,4 0 0,1 16,12A4,4 0 0,1 12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8M12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12A2,2 0 0,0 12,10M10,22C9.75,22 9.54,21.82 9.5,21.58L9.13,18.93C8.5,18.68 7.96,18.34 7.44,17.94L4.95,18.95C4.73,19.03 4.46,18.95 4.34,18.73L2.34,15.27C2.21,15.05 2.27,14.78 2.46,14.63L4.57,12.97L4.5,12L4.57,11L2.46,9.37C2.27,9.22 2.21,8.95 2.34,8.73L4.34,5.27C4.46,5.05 4.73,4.96 4.95,5.05L7.44,6.05C7.96,5.66 8.5,5.32 9.13,5.07L9.5,2.42C9.54,2.18 9.75,2 10,2H14C14.25,2 14.46,2.18 14.5,2.42L14.87,5.07C15.5,5.32 16.04,5.66 16.56,6.05L19.05,5.05C19.27,4.96 19.54,5.05 19.66,5.27L21.66,8.73C21.79,8.95 21.73,9.22 21.54,9.37L19.43,11L19.5,12L19.43,13L21.54,14.63C21.73,14.78 21.79,15.05 21.66,15.27L19.66,18.73C19.54,18.95 19.27,19.04 19.05,18.95L16.56,17.95C16.04,18.34 15.5,18.68 14.87,18.93L14.5,21.58C14.46,21.82 14.25,22 14,22H10M11.25,4L10.88,6.61C9.68,6.86 8.62,7.5 7.85,8.39L5.44,7.35L4.69,8.65L6.8,10.2C6.4,11.37 6.4,12.64 6.8,13.8L4.68,15.36L5.43,16.66L7.86,15.62C8.63,16.5 9.68,17.14 10.87,17.38L11.24,20H12.76L13.13,17.39C14.32,17.14 15.37,16.5 16.14,15.62L18.57,16.66L19.32,15.36L17.2,13.81C17.6,12.64 17.6,11.37 17.2,10.2L19.31,8.65L18.56,7.35L16.15,8.39C15.38,7.5 14.32,6.86 13.12,6.62L12.75,4H11.25Z\" />\n        </svg>\n      </a>\n    </div>\n\n    <div class=\"box\">\n      <p>ESP current time, sync with NTP server and sent to this client via <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/\">WebSocket!</a></p>\n        <h4 id='esp-time'>Waiting websocket connection...</h4>\n    </div>\n\n    <div class=\"box\">\n      $<input type=\"text\" value=\"\" id=\"input_el\">\n    </div>\n    \n    <div class=box>\n        <main id=log></main>\n    </div>\n  </body>\n</html>\n)EOF\";"
  },
  {
    "path": "pio_examples/esp32-p4/src/withWebSocket.cpp",
    "content": "#ifdef USE_M5UNIFIED\n    #include <M5Unified.h>\n#endif\n#include \"ESP_HostedOTA.h\"\n#include <FS.h>\n#include <LittleFS.h>\n#include \"FSWebServer.h\"\n\n#include \"index_htm.h\"\n\n#define FILESYSTEM LittleFS\nconst char* hostname = \"fsbrowser\";\n\n// If you edit server port, remember to change also websocket port in index_htm.h\n// By default websocket port with F,WebServer library is server port + 1\nFSWebServer server(FILESYSTEM, 80, hostname);\n\n// Log messages both on Serial and WebSocket clients\nvoid wsLogPrintf(bool toSerial, const char* format, ...) {\n    char buffer[128];\n    va_list args;\n    va_start(args, format);\n    vsnprintf(buffer, 128, format, args);\n    va_end(args);\n    server.broadcastWebSocket(buffer);\n    if (toSerial)\n        Serial.println(buffer);\n}\n\n/////////////////////////   WebSocket event callback////////////////////////////////   WebSocket Handler  /////////////////////////////\nvoid webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {\n    switch (type) {\n        case WStype_DISCONNECTED:\n            Serial.printf(\"[%u] Disconnected!\\n\", num);\n            break;\n        case WStype_CONNECTED: {\n                IPAddress ip = server.getWebSocketServer()->remoteIP(num);\n                server.getWebSocketServer()->sendTXT(num, \"{\\\"Connected\\\": true}\");\n\n                // Print welcome message to all clients and to Serial\n                wsLogPrintf(true, \"Hello to client #%d [%s]\\n\", (int)num, ip.toString().c_str());\n            }\n            break;\n        case WStype_TEXT:\n            Serial.printf(\"[%u] got Text: %s\\n\", num, payload);   // Got text message from a client\n            break;\n        case WStype_BIN:\n            Serial.printf(\"[%u] got binary length: %u\\n\", num, length); // Got binary message from a client\n            break;\n        default:\n            break;\n    }\n}\n\n// Test \"config\" values\nString optionString = \"Test option String\";\nuint32_t optionULong = 1234567890;\n\n// Timezone definition to get properly time from NTP server\n#define MYTZ \"CET-1CEST,M3.5.0,M10.5.0/3\"\nstruct tm Time;\n\n\n////////////////////////////////  NTP Time  /////////////////////////////////////\nvoid getUpdatedtime(const uint32_t timeout) {\n    uint32_t start = millis();\n    Serial.print(\"Sync time...\");\n    while (millis() - start < timeout && Time.tm_year <= (1970 - 1900)) {\n        time_t now = time(nullptr);\n        Time = *localtime(&now);\n        delay(5);\n    }\n    Serial.println(\" done.\");\n}\n\n\n////////////////////////////////  Filesystem  /////////////////////////////////////////\nbool startFilesystem() {\n    if (FILESYSTEM.begin()) {\n        server.printFileList(FILESYSTEM, \"/\", 1, Serial);\n        return true;\n    } else {\n        Serial.println(\"ERROR on mounting filesystem. It will be reformatted!\");\n        FILESYSTEM.format();\n        ESP.restart();\n    }\n    return false;\n}\n\n\n////////////////////  Load and save application configuration from filesystem  ////////////////////\nbool loadApplicationConfig() {\n    if (FILESYSTEM.exists(server.getConfiFileName())) {\n        server.getOptionValue(\"Option 1\", optionString);\n        server.getOptionValue(\"Option 2\", optionULong);\n        server.closeSetupConfiguration();  // Close configuration to free resources\n        return true;\n    }\n    return false;\n}\n\n\nvoid setup() {\n\n    Serial.begin(115200);\n#ifdef USE_M5UNIFIED\n    auto cfg = M5.config();\n    M5.begin(cfg);\n#endif\n    // FILESYSTEM INIT\n    if (startFilesystem()) {\n        // Load configuration (if not present, default will be created when webserver will start)\n        if (loadApplicationConfig()) {\n            Serial.println(F(\"\\nApplication option loaded\"));\n            Serial.printf(\"  Option 1: %s\\n\", optionString.c_str());\n            Serial.printf(\"  Option 2: %u\\n\", optionULong);\n        } else\n            Serial.println(F(\"Application options NOT loaded!\"));\n    }\n\n    // Try to connect to WiFi (will start AP if not connected after timeout)\n    if (!server.startWiFi(30000)) {\n        Serial.println(\"\\nWiFi not connected! Starting AP mode...\");\n        server.startCaptivePortal(\"ESP_AP\", \"123456789\", \"/setup\");\n    }\n\n    // Update C6 firmware if required (must run before WiFi connect attempt)\n    if (updateEspHostedSlave()) {\n        Serial.println(\"ESP-Hosted slave updated, restarting...\");\n        ESP.restart();\n    }\n\n    // Configure /setup page\n    server.addOptionBox(\"My Options\");\n    server.addOption(\"Option 1\", optionString.c_str());\n    server.addOption(\"Option 2\", optionULong);\n\n    // Add custom page handlers\n    server.on(\"/\", HTTP_GET, []() {\n        server.send_P(200, \"text/html\", homepage);\n    });\n\n    // Enable ACE FS file web editor\n    server.enableFsCodeEditor();\n\n    // Init with WebSocket event handler and start server\n    server.begin(webSocketEvent);\n\n    Serial.print(F(\"ESP Web Server started on IP Address: \"));\n    Serial.println(server.getServerIP());\n    Serial.println(F(\n                       \"This is \\\"withWebSocket.ino\\\" example.\\n\"\n                       \"Open /setup page to configure optional parameters.\\n\"\n                       \"Open /edit page to view, edit or upload example or your custom webserver source files.\"\n                   ));\n\n    // Set hostname\n    WiFi.setHostname(hostname);\n    configTzTime(MYTZ, \"time.google.com\", \"time.windows.com\", \"pool.ntp.org\");\n}\n\n\nvoid loop() {\n#ifdef USE_M5UNIFIED\n    M5.update();\n#endif\n    server.run();  // Handle client requests\n\n    // Send ESP system time (epoch) to WS client\n    static uint32_t sendToClientTime;\n    if (millis() - sendToClientTime > 1000) {\n        sendToClientTime = millis();\n        time_t now = time(nullptr);\n        wsLogPrintf(false, \"{\\\"esptime\\\": %d}\", (int)now);\n    }\n\n    delay(1);\n}"
  },
  {
    "path": "pio_examples/leanWebserver/.gitignore",
    "content": ".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",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\">\n  <title>ESP simpleServer.ino</title>\n  \n  <!--Pico - Minimal CSS Framework for semantic info: HTML https://picocss.com/ -->\n  <link rel=\"stylesheet\" type=\"text/css\" href=\"css/pico.fluid.classless.css\">\n\n  <!-- Cyustom page styling -->\n  <style>\n    html {\n      background-color: #92938fd1\n    }\n    .container {\n      display: flex;\n      justify-content: center;\n      min-height: calc(100vh - 6rem);\n      padding: 1rem 0;\n      \n    }    \n    .container {\n      max-width: 860px;\n      padding: 40px;\n    }\n  </style>\n\n</head>\n  <body>\n    <main class=\"container\">\n      <article class=\"grid\">\n        <div>\n          <hgroup>\n            <h1>ESP Async FS WebServer - LED Switcher - simpleServer.ino</h1>\n          </hgroup>\n          <label for=\"remember\">\n            <input type=\"checkbox\" role=\"switch\" id=\"toggle-led\" name=\"toggle-led\">\n            Toggle built-in LED\n          </label>\n          <br>\n          <p id=\"esp-response\"></p>\n          <img src=\"img/espressif.jpg\" style=\"opacity: 0.75\"/>\n        </div>\n      </article>\n    </main>\n    \n    <script type=\"text/javascript\">\n      // This function fetch the GET request to the ESP webserver\n      function toggleLed() {\n        const pars = new URLSearchParams({\n          val:  document.getElementById('toggle-led').checked ? '1' : '0'\n        });\n        \n        fetch('/led?' + pars )                // Do the request\n        .then(response => response.text())    // Parse the response \n        .then(text => {                       // DO something with response\n          console.log(text);\n          document.getElementById('esp-response').innerHTML = text + ' <i>(Builtin LED is ON with a low signal)</i>';\n        });\n      }\n      \n      // Add event listener to the LED checkbox (the function will be called on every change)\n      document.getElementById('toggle-led').addEventListener('change', toggleLed );\n    </script>\n    \n  </body>\n</html>\n"
  },
  {
    "path": "pio_examples/leanWebserver/partitions.csv",
    "content": "# Name,   Type, SubType, Offset,  Size, Flags\nnvs,      data, nvs,     0x9000,  0x5000,\notadata,  data, ota,     0xe000,  0x2000,\napp0,     app,  ota_0,   0x10000, 0x140000,\napp1,     app,  ota_1,   0x150000,0x140000,\nspiffs,   data, spiffs,  0x290000,0x160000,\ncoredump, data, coredump,0x3F0000,0x10000,\n"
  },
  {
    "path": "pio_examples/leanWebserver/platformio.ini",
    "content": "; PlatformIO Project Configuration File\n;\n;   Build options: build flags, source filter\n;   Upload options: custom upload port, speed and extra flags\n;   Library options: dependencies, extra library storages\n;   Advanced options: extra scripting\n;\n; Please visit documentation for the other options and examples\n; https://docs.platformio.org/page/projectconf.html\n\n[env:esp32-s3-devkitc1-n4r2-lean]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32-s3-devkitc1-n4r2\nframework = arduino\nboard_build.partitions = partitions.csv\nbuild_flags =\n    -Os\n    -ffunction-sections\n    -fdata-sections\n    -D ESP_FS_WS_MDNS=0\n    -D ESP_FS_WS_EDIT=0\n    -D ESP_FS_WS_SETUP=0\n    -D ESP_FS_WS_SETUP_HTM=0\n    -D ESP_FS_WS_WEBSOCKET=0\nlink_flags =\n    -Wl,--gc-sections\nlib_extra_dirs = ../../\n\n\n\n[env:esp32dev-lean]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32dev\nframework = arduino\nboard_build.partitions = partitions.csv\nbuild_flags =\n    -Os\n    -ffunction-sections\n    -fdata-sections\n    -D ESP_FS_WS_MDNS=0\n    -D ESP_FS_WS_EDIT=0\n    -D ESP_FS_WS_SETUP=0\n    -D ESP_FS_WS_SETUP_HTM=0\n    -D ASYNCWEBSERVER_USE_CHUNK_INFLIGHT=0\nlink_flags =\n    -Wl,--gc-sectionsl\nlib_extra_dirs = ../../\n\n"
  },
  {
    "path": "pio_examples/leanWebserver/src/leanWebserver.ino",
    "content": "#include <Arduino.h>\n#include <FS.h>\n#include <LittleFS.h>\n#include <FSWebServer.h>  //https://github.com/cotestatnt/async-esp-fs-webserver\n\n#define FILESYSTEM LittleFS\nFSWebServer server(80, FILESYSTEM, \"myserver\");\n\n// Define built-in LED if not defined by board (eg. generic dev boards)\n#ifndef LED_BUILTIN\n#define LED_BUILTIN 2\n#endif\n\n#ifndef BOOT_PIN\n#define BOOT_PIN    0\n#endif\n\n\n////////////////////////////////  Filesystem  /////////////////////////////////////////\nbool startFilesystem() {\n  if (FILESYSTEM.begin()){\n    server.printFileList(FILESYSTEM, \"/\", 2);\n    return true;\n  }\n  else {\n    Serial.println(\"ERROR on mounting filesystem. It will be reformatted!\");\n    FILESYSTEM.format();\n    ESP.restart();\n  }\n  return false;\n}\n\n///////////////////////////////  HTTP endpoint  ///////////////////////////////////////\nvoid handleLed() {\n  static int value = false;\n  // http://xxx.xxx.xxx.xxx/led?val=1\n  if(server.hasArg(\"val\")) {\n    value = server.arg(\"val\").toInt();\n    digitalWrite(LED_BUILTIN, value);\n  }\n  String reply = \"LED is now \";\n  reply += value ? \"ON\" : \"OFF\";\n  server.send(200, \"text/plain\", reply);\n}\n\n\nvoid setup() {\n  pinMode(LED_BUILTIN, OUTPUT);\n  pinMode(BOOT_PIN, INPUT_PULLUP);\n  Serial.begin(115200);\n\n  // FILESYSTEM INIT\n  startFilesystem();\n\n  // Try to connect to WiFi (will start AP if not connected after timeout)\n  if (!server.startWiFi(10000)) {\n    Serial.println(\"\\nWiFi not connected! Starting AP mode...\");\n    server.startCaptivePortal(\"ESP_AP\", \"123456789\", \"/setup\");\n  }\n\n  // Define HTTP endpoints\n  server.on(\"/led\", HTTP_GET, handleLed);\n  \n  // Start server\n  server.begin();\n  Serial.print(F(\"\\nESP Web Server started on IP Address: \"));\n  Serial.println(server.getServerIP());\n  Serial.println(F(\n      \"\\nThis is \\\"customOptions.ino\\\" example.\\n\"\n      \"Open /setup page to configure optional parameters.\\n\"\n      \"Open /edit page to view, edit or upload example or your custom webserver source files.\"\n  ));\n\n  Serial.print(F(\"Compile time (default firmware version): \"));\n  Serial.println(BUILD_TIMESTAMP);\n}\n\nvoid loop() {\n  server.handleClient();\n  if (server.isAccessPointMode())\n    server.updateDNS();\n\n  // Nothing to do here, just a small delay for task yield\n  delay(10);  \n}\n"
  },
  {
    "path": "pio_examples/simpleServer/.gitignore",
    "content": ".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",
    "content": "[\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\Crypto.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\Crypto.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\Crypto.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\Crypto.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\Esp-frag.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\Esp-frag.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\Esp-frag.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\Esp-frag.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\Esp-version.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\Esp-version.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\Esp-version.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\Esp-version.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\Esp.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\Esp.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\Esp.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\Esp.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\FS.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\FS.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\FS.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\FS.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\FSnoop.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\FSnoop.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\FSnoop.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\FSnoop.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\FunctionalInterrupt.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\FunctionalInterrupt.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\FunctionalInterrupt.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\FunctionalInterrupt.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\HardwareSerial.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\HardwareSerial.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\HardwareSerial.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\HardwareSerial.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\IPAddress.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\IPAddress.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\IPAddress.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\IPAddress.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\LwipDhcpServer-NonOS.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\LwipDhcpServer-NonOS.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\LwipDhcpServer-NonOS.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\LwipDhcpServer-NonOS.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\LwipDhcpServer.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\LwipDhcpServer.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\LwipDhcpServer.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\LwipDhcpServer.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\LwipIntf.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\LwipIntf.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\LwipIntf.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\LwipIntf.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\LwipIntfCB.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\LwipIntfCB.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\LwipIntfCB.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\LwipIntfCB.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\MD5Builder.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\MD5Builder.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\MD5Builder.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\MD5Builder.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\Print.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\Print.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\Print.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\Print.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\Schedule.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\Schedule.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\Schedule.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\Schedule.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\StackThunk.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\StackThunk.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\StackThunk.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\StackThunk.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\Stream.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\Stream.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\Stream.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\Stream.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\StreamSend.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\StreamSend.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\StreamSend.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\StreamSend.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\Tone.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\Tone.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\Tone.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\Tone.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\TypeConversion.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\TypeConversion.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\TypeConversion.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\TypeConversion.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\Updater.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\Updater.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\Updater.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\Updater.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\WMath.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\WMath.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\WMath.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\WMath.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\WString.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\WString.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\WString.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\WString.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\abi.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\abi.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\abi.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\abi.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\aes_unwrap.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\aes_unwrap.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\aes_unwrap.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\aes_unwrap.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\base64.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\base64.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\base64.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\base64.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\cbuf.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\cbuf.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\cbuf.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\cbuf.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-gcc.exe -mlongcalls -mtext-section-literals -x assembler-with-cpp -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu -c -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\cont.S.o C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\cont.S\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\cont.S\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\cont.S.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\cont_util.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\cont_util.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\cont_util.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\cont_util.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_app_entry_noextra4k.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_app_entry_noextra4k.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_app_entry_noextra4k.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_app_entry_noextra4k.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_eboot_command.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_eboot_command.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_eboot_command.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_eboot_command.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_features.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_features.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_features.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_features.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_flash_quirks.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_flash_quirks.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_flash_quirks.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_flash_quirks.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_flash_utils.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_flash_utils.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_flash_utils.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_flash_utils.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_i2s.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_i2s.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_i2s.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_i2s.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_main.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_main.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_main.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_main.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_non32xfer.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_non32xfer.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_non32xfer.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_non32xfer.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_noniso.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_noniso.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_noniso.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_noniso.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_phy.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_phy.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_phy.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_phy.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_postmortem.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_postmortem.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_postmortem.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_postmortem.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_si2c.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_si2c.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_si2c.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_si2c.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_sigma_delta.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_sigma_delta.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_sigma_delta.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_sigma_delta.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_spi_utils.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_spi_utils.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_spi_utils.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_spi_utils.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_timer.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_timer.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_timer.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_timer.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_vm.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_vm.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_vm.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_vm.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_waveform_phase.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_waveform_phase.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_waveform_phase.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_waveform_phase.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_waveform_pwm.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_waveform_pwm.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_waveform_pwm.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_waveform_pwm.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_wiring.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_wiring.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_wiring.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_wiring.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_wiring_analog.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_wiring_analog.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_wiring_analog.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_wiring_analog.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_wiring_digital.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_wiring_digital.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_wiring_digital.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_wiring_digital.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_wiring_pulse.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_wiring_pulse.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_wiring_pulse.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_wiring_pulse.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_wiring_pwm.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_wiring_pwm.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_wiring_pwm.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_wiring_pwm.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_wiring_shift.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_wiring_shift.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\core_esp8266_wiring_shift.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\core_esp8266_wiring_shift.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\crc32.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\crc32.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\crc32.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\crc32.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\debug.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\debug.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\debug.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\debug.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-gcc.exe -mlongcalls -mtext-section-literals -x assembler-with-cpp -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu -c -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\exc-c-wrapper-handler.S.o C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\exc-c-wrapper-handler.S\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\exc-c-wrapper-handler.S\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\exc-c-wrapper-handler.S.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\exc-sethandler.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\exc-sethandler.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\exc-sethandler.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\exc-sethandler.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\flash_hal.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\flash_hal.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\flash_hal.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\flash_hal.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\gdb_hooks.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\gdb_hooks.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\gdb_hooks.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\gdb_hooks.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\heap.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\heap.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\heap.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\heap.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\hwdt_app_entry.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\hwdt_app_entry.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\hwdt_app_entry.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\hwdt_app_entry.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\libb64\\\\cdecode.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\libb64\\\\cdecode.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\libb64\\\\cdecode.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\libb64\\\\cdecode.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\libb64\\\\cencode.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\libb64\\\\cencode.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\libb64\\\\cencode.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\libb64\\\\cencode.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\libc_replacements.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\libc_replacements.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\libc_replacements.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\libc_replacements.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\mmu_iram.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\mmu_iram.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\mmu_iram.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\mmu_iram.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\reboot_uart_dwnld.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\reboot_uart_dwnld.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\reboot_uart_dwnld.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\reboot_uart_dwnld.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\spiffs\\\\spiffs_cache.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\spiffs\\\\spiffs_cache.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\spiffs\\\\spiffs_cache.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\spiffs\\\\spiffs_cache.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\spiffs\\\\spiffs_check.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\spiffs\\\\spiffs_check.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\spiffs\\\\spiffs_check.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\spiffs\\\\spiffs_check.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\spiffs\\\\spiffs_gc.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\spiffs\\\\spiffs_gc.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\spiffs\\\\spiffs_gc.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\spiffs\\\\spiffs_gc.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\spiffs\\\\spiffs_hydrogen.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\spiffs\\\\spiffs_hydrogen.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\spiffs\\\\spiffs_hydrogen.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\spiffs\\\\spiffs_hydrogen.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\spiffs\\\\spiffs_nucleus.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\spiffs\\\\spiffs_nucleus.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\spiffs\\\\spiffs_nucleus.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\spiffs\\\\spiffs_nucleus.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\spiffs_api.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\spiffs_api.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\spiffs_api.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\spiffs_api.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\sqrt32.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\sqrt32.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\sqrt32.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\sqrt32.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\stdlib_noniso.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\stdlib_noniso.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\stdlib_noniso.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\stdlib_noniso.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\time.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\time.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\time.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\time.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\uart.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\uart.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\uart.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\uart.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-gcc.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\umm_malloc\\\\umm_info.c.o -c -std=gnu17 -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\umm_malloc\\\\umm_info.c\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\umm_malloc\\\\umm_info.c\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\umm_malloc\\\\umm_info.c.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-gcc.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\umm_malloc\\\\umm_integrity.c.o -c -std=gnu17 -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\umm_malloc\\\\umm_integrity.c\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\umm_malloc\\\\umm_integrity.c\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\umm_malloc\\\\umm_integrity.c.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-gcc.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\umm_malloc\\\\umm_local.c.o -c -std=gnu17 -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\umm_malloc\\\\umm_local.c\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\umm_malloc\\\\umm_local.c\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\umm_malloc\\\\umm_local.c.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\umm_malloc\\\\umm_malloc.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\umm_malloc\\\\umm_malloc.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\umm_malloc\\\\umm_malloc.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\umm_malloc\\\\umm_malloc.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-gcc.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\umm_malloc\\\\umm_poison.c.o -c -std=gnu17 -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\umm_malloc\\\\umm_poison.c\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\umm_malloc\\\\umm_poison.c\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\umm_malloc\\\\umm_poison.c.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\wpa2_eap_patch.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\wpa2_eap_patch.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266\\\\wpa2_eap_patch.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\FrameworkArduino\\\\wpa2_eap_patch.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\BearSSLHelpers.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\BearSSLHelpers.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\BearSSLHelpers.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\BearSSLHelpers.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\CertStoreBearSSL.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\CertStoreBearSSL.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\CertStoreBearSSL.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\CertStoreBearSSL.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\ESP8266WiFi.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\ESP8266WiFi.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\ESP8266WiFi.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\ESP8266WiFi.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\ESP8266WiFiAP.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\ESP8266WiFiAP.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\ESP8266WiFiAP.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\ESP8266WiFiAP.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\ESP8266WiFiGeneric.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\ESP8266WiFiGeneric.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\ESP8266WiFiGeneric.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\ESP8266WiFiGeneric.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\ESP8266WiFiGratuitous.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\ESP8266WiFiGratuitous.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\ESP8266WiFiGratuitous.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\ESP8266WiFiGratuitous.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\ESP8266WiFiMulti.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\ESP8266WiFiMulti.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\ESP8266WiFiMulti.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\ESP8266WiFiMulti.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\ESP8266WiFiSTA-WPS.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\ESP8266WiFiSTA-WPS.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\ESP8266WiFiSTA-WPS.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\ESP8266WiFiSTA-WPS.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\ESP8266WiFiSTA.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\ESP8266WiFiSTA.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\ESP8266WiFiSTA.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\ESP8266WiFiSTA.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\ESP8266WiFiScan.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\ESP8266WiFiScan.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\ESP8266WiFiScan.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\ESP8266WiFiScan.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\WiFiClient.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\WiFiClient.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\WiFiClient.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\WiFiClient.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\WiFiClientSecureBearSSL.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\WiFiClientSecureBearSSL.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\WiFiClientSecureBearSSL.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\WiFiClientSecureBearSSL.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\WiFiServer.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\WiFiServer.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\WiFiServer.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\WiFiServer.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\WiFiServerSecureBearSSL.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\WiFiServerSecureBearSSL.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\WiFiServerSecureBearSSL.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\WiFiServerSecureBearSSL.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\WiFiUdp.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\WiFiUdp.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\WiFiUdp.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\WiFiUdp.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\enable_wifi_at_boot_time.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\enable_wifi_at_boot_time.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src\\\\enable_wifi_at_boot_time.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib24c\\\\ESP8266WiFi\\\\enable_wifi_at_boot_time.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib9ba\\\\DNSServer\\\\DNSServer.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\DNSServer\\\\src\\\\DNSServer.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\DNSServer\\\\src\\\\DNSServer.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib9ba\\\\DNSServer\\\\DNSServer.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib923\\\\Hash\\\\Hash.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\Hash\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\Hash\\\\src\\\\Hash.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\Hash\\\\src\\\\Hash.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib923\\\\Hash\\\\Hash.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib006\\\\ESP8266WebServer\\\\detail\\\\mimetable.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WebServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WebServer\\\\src\\\\detail\\\\mimetable.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WebServer\\\\src\\\\detail\\\\mimetable.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib006\\\\ESP8266WebServer\\\\detail\\\\mimetable.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib091\\\\ESP8266mDNS\\\\ESP8266mDNS.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src\\\\ESP8266mDNS.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src\\\\ESP8266mDNS.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib091\\\\ESP8266mDNS\\\\ESP8266mDNS.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib091\\\\ESP8266mDNS\\\\LEAmDNS.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src\\\\LEAmDNS.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src\\\\LEAmDNS.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib091\\\\ESP8266mDNS\\\\LEAmDNS.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib091\\\\ESP8266mDNS\\\\LEAmDNS_Control.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src\\\\LEAmDNS_Control.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src\\\\LEAmDNS_Control.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib091\\\\ESP8266mDNS\\\\LEAmDNS_Control.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib091\\\\ESP8266mDNS\\\\LEAmDNS_Helpers.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src\\\\LEAmDNS_Helpers.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src\\\\LEAmDNS_Helpers.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib091\\\\ESP8266mDNS\\\\LEAmDNS_Helpers.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib091\\\\ESP8266mDNS\\\\LEAmDNS_Structs.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src\\\\LEAmDNS_Structs.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src\\\\LEAmDNS_Structs.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib091\\\\ESP8266mDNS\\\\LEAmDNS_Structs.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib091\\\\ESP8266mDNS\\\\LEAmDNS_Transfer.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src\\\\LEAmDNS_Transfer.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src\\\\LEAmDNS_Transfer.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib091\\\\ESP8266mDNS\\\\LEAmDNS_Transfer.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\libaff\\\\LittleFS\\\\LittleFS.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\LittleFS\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\LittleFS\\\\src\\\\LittleFS.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\LittleFS\\\\src\\\\LittleFS.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\libaff\\\\LittleFS\\\\LittleFS.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-gcc.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\libaff\\\\LittleFS\\\\lfs.c.o -c -std=gnu17 -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\LittleFS\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\LittleFS\\\\src\\\\lfs.c\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\LittleFS\\\\src\\\\lfs.c\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\libaff\\\\LittleFS\\\\lfs.c.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-gcc.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\libaff\\\\LittleFS\\\\lfs_util.c.o -c -std=gnu17 -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\LittleFS\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\LittleFS\\\\src\\\\lfs_util.c\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\LittleFS\\\\src\\\\lfs_util.c\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\libaff\\\\LittleFS\\\\lfs_util.c.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib4e7\\\\src\\\\CredentialManager.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\LittleFS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266HTTPUpdateServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WebServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\Hash\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src\\\\CredentialManager.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src\\\\CredentialManager.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib4e7\\\\src\\\\CredentialManager.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib4e7\\\\src\\\\FSWebServer.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\LittleFS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266HTTPUpdateServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WebServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\Hash\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src\\\\FSWebServer.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src\\\\FSWebServer.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib4e7\\\\src\\\\FSWebServer.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib4e7\\\\src\\\\Json.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\LittleFS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266HTTPUpdateServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WebServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\Hash\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src\\\\Json.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src\\\\Json.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib4e7\\\\src\\\\Json.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib4e7\\\\src\\\\WiFiService.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\LittleFS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266HTTPUpdateServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WebServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\Hash\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src\\\\WiFiService.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src\\\\WiFiService.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib4e7\\\\src\\\\WiFiService.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-gcc.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib4e7\\\\src\\\\json\\\\cJSON.c.o -c -std=gnu17 -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\LittleFS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266HTTPUpdateServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WebServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\Hash\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src\\\\json\\\\cJSON.c\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src\\\\json\\\\cJSON.c\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib4e7\\\\src\\\\json\\\\cJSON.c.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib4e7\\\\src\\\\mimetable\\\\mimetable.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\LittleFS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266HTTPUpdateServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WebServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\Hash\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src\\\\mimetable\\\\mimetable.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src\\\\mimetable\\\\mimetable.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib4e7\\\\src\\\\mimetable\\\\mimetable.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib4e7\\\\src\\\\websocket\\\\SocketIOclient.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\LittleFS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266HTTPUpdateServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WebServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\Hash\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src\\\\websocket\\\\SocketIOclient.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src\\\\websocket\\\\SocketIOclient.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib4e7\\\\src\\\\websocket\\\\SocketIOclient.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib4e7\\\\src\\\\websocket\\\\WebSockets.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\LittleFS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266HTTPUpdateServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WebServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\Hash\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src\\\\websocket\\\\WebSockets.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src\\\\websocket\\\\WebSockets.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib4e7\\\\src\\\\websocket\\\\WebSockets.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib4e7\\\\src\\\\websocket\\\\WebSocketsClient.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\LittleFS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266HTTPUpdateServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WebServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\Hash\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src\\\\websocket\\\\WebSocketsClient.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src\\\\websocket\\\\WebSocketsClient.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib4e7\\\\src\\\\websocket\\\\WebSocketsClient.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib4e7\\\\src\\\\websocket\\\\WebSocketsServer.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\LittleFS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266HTTPUpdateServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WebServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\Hash\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src\\\\websocket\\\\WebSocketsServer.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src\\\\websocket\\\\WebSocketsServer.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib4e7\\\\src\\\\websocket\\\\WebSocketsServer.cpp.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-gcc.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib4e7\\\\src\\\\websocket\\\\libb64\\\\cdecode.c.o -c -std=gnu17 -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\LittleFS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266HTTPUpdateServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WebServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\Hash\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src\\\\websocket\\\\libb64\\\\cdecode.c\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src\\\\websocket\\\\libb64\\\\cdecode.c\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib4e7\\\\src\\\\websocket\\\\libb64\\\\cdecode.c.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-gcc.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib4e7\\\\src\\\\websocket\\\\libb64\\\\cencode.c.o -c -std=gnu17 -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\LittleFS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266HTTPUpdateServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WebServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\Hash\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src\\\\websocket\\\\libb64\\\\cencode.c\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src\\\\websocket\\\\libb64\\\\cencode.c\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib4e7\\\\src\\\\websocket\\\\libb64\\\\cencode.c.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-gcc.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\lib4e7\\\\src\\\\websocket\\\\libsha1\\\\libsha1.c.o -c -std=gnu17 -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -IC:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\LittleFS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266HTTPUpdateServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WebServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\Hash\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src\\\\websocket\\\\libsha1\\\\libsha1.c\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src\\\\websocket\\\\libsha1\\\\libsha1.c\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\lib4e7\\\\src\\\\websocket\\\\libsha1\\\\libsha1.c.o\"\n    },\n    {\n        \"command\": \"C:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\bin\\\\xtensa-lx106-elf-g++.exe -o .pio\\\\build\\\\esp8266-nodemcuv2\\\\src\\\\simpleServer.ino.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_NODEMCU_ESP12E -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\\\\\"PLATFORMIO_NODEMCUV2\\\\\\\" -DARDUINO_BOARD_ID=\\\\\\\"nodemcuv2\\\\\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Isrc -IC:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\LittleFS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266mDNS\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266HTTPUpdateServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WebServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\Hash\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\libraries\\\\ESP8266WiFi\\\\src -Iinclude -I.pio\\\\libdeps\\\\esp8266-nodemcuv2\\\\DNSServer\\\\src -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\cores\\\\esp8266 -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\toolchain-xtensa\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\tools\\\\sdk\\\\lwip2\\\\include -IC:\\\\Users\\\\cotes\\\\.platformio\\\\packages\\\\framework-arduinoespressif8266\\\\variants\\\\nodemcu src\\\\simpleServer.ino.cpp\",\n        \"directory\": \"C:\\\\Users\\\\cotes\\\\OneDrive\\\\Documenti\\\\Arduino\\\\libraries\\\\esp-fs-webserver\\\\pio_examples\\\\simpleServer\",\n        \"file\": \"src\\\\simpleServer.ino.cpp\",\n        \"output\": \".pio\\\\build\\\\esp8266-nodemcuv2\\\\src\\\\simpleServer.ino.cpp.o\"\n    }\n]\n"
  },
  {
    "path": "pio_examples/simpleServer/partitions.csv",
    "content": "# Name,   Type, SubType, Offset,  Size, Flags\nnvs,      data, nvs,     0x9000,  0x5000,\notadata,  data, ota,     0xe000,  0x2000,\napp0,     app,  ota_0,   0x10000, 0x140000,\napp1,     app,  ota_1,   0x150000,0x140000,\nspiffs,   data, spiffs,  0x290000,0x160000,\ncoredump, data, coredump,0x3F0000,0x10000,\n"
  },
  {
    "path": "pio_examples/simpleServer/platformio.ini",
    "content": "; PlatformIO Project Configuration File\n;\n;   Build options: build flags, source filter\n;   Upload options: custom upload port, speed and extra flags\n;   Library options: dependencies, extra library storages\n;   Advanced options: extra scripting\n;\n; Please visit documentation for the other options and examples\n; https://docs.platformio.org/page/projectconf.html\n\n[env:esp32-s3-devkitc1-n4r2]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32-s3-devkitc1-n4r2\nframework = arduino\nboard_build.partitions = partitions.csv\nlib_extra_dirs = ../../\nlib_ignore = pio_examples\nbuild_flags = -Os\n\n\n[env:esp32dev]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32dev\nframework = arduino\nlib_extra_dirs = ../../\nlib_ignore = pio_examples\n\n[env:esp8266-nodemcuv2]\nplatform = espressif8266\nboard = nodemcuv2\nframework = arduino\nupload_speed = 921600\nlib_extra_dirs = ../../\nbuild_flags =\n\t-I include\n\t-I ${PROJECT_LIBDEPS_DIR}/${PIOENV}/DNSServer/src\nlib_deps =\n\tDNSServer\nlib_ignore = pio_examples"
  },
  {
    "path": "pio_examples/simpleServer/src/simpleServer.ino",
    "content": "#include <FS.h>\n#include <LittleFS.h>\n#include \"FSWebServer.h\"\n\nFSWebServer server(LittleFS, 80, \"myServer\");\nuint16_t testInt = 150;\nfloat testFloat = 123.456f;\n\n#ifndef LED_BUILTIN\n#define LED_BUILTIN 2\n#endif\nconst uint8_t ledPin = LED_BUILTIN;\n\n////////////////////////////////  Filesystem  /////////////////////////////////////////\nbool startFilesystem() {\n  if (LittleFS.begin()) {\n    server.printFileList(LittleFS, \"/\", 1, Serial);    \n    return true;\n  } else {\n    Serial.println(\"ERROR on mounting filesystem. It will be reformatted!\");\n    LittleFS.format();\n    ESP.restart();\n  }\n  return false;\n}\n\n//////////////////////////////// WiFi Event Callbacks /////////////////////////////////////////\nuint8_t wifiStatus = WL_IDLE_STATUS;  // WiFi status variable to track connection state\n\n\n////////////////////////////  Load custom options //////////////////////////////////////\nbool loadApplicationConfig() {\n  if (LittleFS.exists(server.getConfiFileName())) {\n    // Test \"options\" values\n    server.getOptionValue(\"Test int variable\", testInt);\n    server.getOptionValue(\"Test float variable\", testFloat);\n    return true;\n  }\n  return false;\n}\n\n//---------------------------------------\nvoid handleLed() {\n  static int value = false;\n  // http://xxx.xxx.xxx.xxx/led?val=1\n  if (server.hasArg(\"val\")) {\n    value = server.arg(\"val\").toInt();\n    digitalWrite(ledPin, value);\n  }\n  String reply = \"LED is now \";\n  reply += value ? \"ON\" : \"OFF\";\n  server.send(200, \"text/plain\", reply);\n}\n\n\nvoid setup() {\n  pinMode(ledPin, OUTPUT);\n  Serial.begin(115200);\n  delay(1000);\n  if (startFilesystem()) {\n    // Load application config options\n    if (loadApplicationConfig()) {\n      Serial.printf(\"Stored \\\"testInt\\\" value: %d\\n\", testInt);\n      Serial.printf(\"Stored \\\"testFloat\\\" value: %3.3f\\n\", testFloat);\n    }\n  } else\n    Serial.println(\"LittleFS error!\");\n\n  // Clear saved config and wifi credentials (for testing purposes) \n  //server.clearAll(); // Uncomment to clear saved config on each boot (for testing)\n\n  // Register WiFi event callbacks\n  #ifdef ESP32\n  server.setWiFiConnectionCallbacks(\n    [](WiFiEvent_t event, WiFiEventInfo_t info) {\n      (void)event;\n      (void)info;\n      Serial.println(\"WiFi connected, IP address: \" + WiFi.localIP().toString());\n      wifiStatus = WL_CONNECTED;\n    },\n    [](WiFiEvent_t event, WiFiEventInfo_t info) {\n      (void)event;\n      if (wifiStatus == WL_CONNECTED) {\n        Serial.println(\"WiFi disconnected, reason: \" + String(info.wifi_sta_disconnected.reason));\n      }\n      wifiStatus = WiFi.status();\n    }\n  );\n  #elif defined(ESP8266)\n  server.setWiFiConnectionCallbacks(\n    [](const WiFiEventStationModeGotIP& event) {\n      Serial.println(\"WiFi connected, IP address: \" + event.ip.toString());\n      wifiStatus = WL_CONNECTED;\n    },\n    [](const WiFiEventStationModeDisconnected& event) {\n      if (wifiStatus == WL_CONNECTED) {\n        Serial.println(\"WiFi disconnected, reason: \" + String(event.reason));\n      }\n      wifiStatus = WiFi.status();\n    }\n  );\n  #endif\n\n\n  // Try to connect to WiFi (will start AP if not connected after timeout)\n  if (!server.startWiFi(10000)) {\n    Serial.println(\"\\nWiFi not connected! Starting AP mode...\");\n    WiFiConnectParams params;\n    params.config.local_ip = IPAddress(192, 168, 1, 1);\n    params.config.gateway = IPAddress(192, 168, 1, 1);\n    params.config.subnet = IPAddress(255, 255, 255, 0);  \n    server.setAP(\"ESP_AP\", \"123456789\");  \n    server.startCaptivePortal(params, \"/setup.htm\");\n  }\n\n  // Add custom application options tab and set custom title\n  server.addOptionBox(\"Custom options\");\n  server.addOption(\"Test int variable\", testInt);\n  server.addOption(\"Test float variable\", (double)testFloat, 0.0, 100.0, 0.001);\n  server.setSetupPageTitle(\"ESP FS WebServer\");\n\n  // Enable ACE FS file web editor\n  server.enableFsCodeEditor();\n\n  // Custom endpoint handler\n  server.on(\"/led\", HTTP_GET, handleLed);\n\n  // Start server\n  server.begin();\n  Serial.print(F(\"\\nAsync ESP Web Server started on IP Address: \"));\n  Serial.println(server.getServerIP());\n  Serial.println(F(\n    \"This is \\\"simpleServer.ino\\\" example.\\n\"\n    \"Open /setup page to configure optional parameters.\\n\"\n    \"Open /edit page to view, edit or upload example or your custom webserver source files.\"));\n}\n\nvoid loop() {\n  // Handle client requests\n  server.run();\n\n  // Nothing to do here for this simple example\n  delay(10);\n}\n"
  },
  {
    "path": "pio_examples/websocketEcharts/.gitignore",
    "content": ".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",
    "content": "# WebSocket Chart Example\n\nReal-time telemetry dashboard demonstrating WebSocket communication with live charting.\n\nThe ESP broadcasts memory stats (heap, max block) and WiFi RSSI every second. The web interface displays data in animated charts with theme selection and auto-reconnect.\n\n## Charting Library\n\nThis example uses **[ECharts 5](https://echarts.apache.org/)** – a powerful, declarative JavaScript visualization library by Apache.\n\n- **Lightweight**: Loaded via CDN (~900KB minified)\n- **Responsive**: Auto-resizes on window changes\n- **Smooth animations**: Built-in transitions for live data\n- **Rich API**: [Full documentation](https://echarts.apache.org/en/option.html)\n\nThe dashboard implements real-time `line` charts with `time` axis and formatted tooltips. All chart options are in `data/index.htm`.\n"
  },
  {
    "path": "pio_examples/websocketEcharts/data/index.htm",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <title>ESP WebSocket Telemetry</title>\n    <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;600&family=Space+Grotesk:wght@400;600;700&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <style>\n      /* Theme tokens */\n      :root {\n        --bg-1: #0b0f1a;\n        --bg-2: #14253d;\n        --panel: #111827cc;\n        --panel-strong: #0f172a;\n        --text: #e5e7eb;\n        --muted: #94a3b8;\n        --accent: #22d3ee;\n        --accent-2: #f59e0b;\n        --accent-3: #34d399;\n        --danger: #f87171;\n        --grid: rgba(148, 163, 184, 0.2);\n        --shadow: 0 18px 45px rgba(2, 6, 23, 0.55);\n      }\n\n      /* Theme variants */\n      body[data-theme=\"sunrise\"] {\n        --bg-1: #1b0c24;\n        --bg-2: #3f1a3a;\n        --panel: #1f1230cc;\n        --panel-strong: #1b0f2a;\n        --text: #fdf2f8;\n        --muted: #f9a8d4;\n        --accent: #fb7185;\n        --accent-2: #f59e0b;\n        --accent-3: #f472b6;\n        --danger: #f43f5e;\n        --grid: rgba(251, 113, 133, 0.2);\n        --shadow: 0 18px 45px rgba(24, 5, 32, 0.6);\n      }\n\n      body[data-theme=\"mono\"] {\n        --bg-1: #0b0d0f;\n        --bg-2: #1b2128;\n        --panel: #0f1318cc;\n        --panel-strong: #0f141a;\n        --text: #e2e8f0;\n        --muted: #94a3b8;\n        --accent: #e2e8f0;\n        --accent-2: #94a3b8;\n        --accent-3: #cbd5f5;\n        --danger: #f87171;\n        --grid: rgba(148, 163, 184, 0.2);\n        --shadow: 0 18px 45px rgba(5, 8, 12, 0.65);\n      }\n\n      /* Base reset */\n      * {\n        box-sizing: border-box;\n      }\n\n      /* Page background and typography */\n      body {\n        margin: 0;\n        font-family: \"Space Grotesk\", system-ui, sans-serif;\n        color: var(--text);\n        background: radial-gradient(1200px 600px at 15% 20%, #1b3a5e 0%, transparent 60%),\n          radial-gradient(900px 500px at 85% 0%, #15304e 0%, transparent 60%),\n          linear-gradient(180deg, var(--bg-1), var(--bg-2));\n        min-height: 100vh;\n      }\n\n      body[data-theme=\"sunrise\"] {\n        background: radial-gradient(1000px 600px at 12% 15%, #8b215b 0%, transparent 60%),\n          radial-gradient(900px 500px at 85% 5%, #f97316 0%, transparent 55%),\n          linear-gradient(180deg, var(--bg-1), var(--bg-2));\n      }\n\n      body[data-theme=\"mono\"] {\n        background: radial-gradient(1000px 600px at 12% 15%, #2b3640 0%, transparent 60%),\n          radial-gradient(900px 500px at 85% 5%, #1f2a33 0%, transparent 55%),\n          linear-gradient(180deg, var(--bg-1), var(--bg-2));\n      }\n\n      /* Subtle grid overlay */\n      body::before {\n        content: \"\";\n        position: fixed;\n        inset: 0;\n        background-image: radial-gradient(rgba(148, 163, 184, 0.15) 1px, transparent 0);\n        background-size: 24px 24px;\n        pointer-events: none;\n        opacity: 0.35;\n      }\n\n      /* Layout container */\n      .page {\n        max-width: 1200px;\n        margin: 0 auto;\n        padding: 28px 22px 40px;\n        position: relative;\n      }\n\n      /* Top header */\n      header {\n        display: flex;\n        flex-wrap: wrap;\n        align-items: center;\n        justify-content: space-between;\n        gap: 16px;\n        margin-bottom: 22px;\n      }\n\n      /* Theme selector pill */\n      .theme-switch {\n        display: flex;\n        align-items: center;\n        gap: 10px;\n        padding: 8px 12px;\n        border-radius: 999px;\n        border: 1px solid rgba(148, 163, 184, 0.25);\n        background: var(--panel);\n        box-shadow: var(--shadow);\n        font-size: 0.9rem;\n        color: var(--muted);\n      }\n\n      .theme-switch select {\n        appearance: none;\n        background: var(--panel-strong);\n        color: var(--text);\n        border: 1px solid rgba(148, 163, 184, 0.25);\n        border-radius: 999px;\n        padding: 6px 28px 6px 12px;\n        font-family: \"Space Grotesk\", system-ui, sans-serif;\n        font-size: 0.9rem;\n        position: relative;\n      }\n\n      .theme-switch select:focus {\n        outline: 2px solid rgba(34, 211, 238, 0.45);\n        outline-offset: 2px;\n      }\n\n      .title {\n        display: flex;\n        flex-direction: column;\n        gap: 4px;\n      }\n\n      .title h1 {\n        font-size: clamp(28px, 3vw, 38px);\n        margin: 0;\n        letter-spacing: -0.02em;\n      }\n\n      .title span {\n        color: var(--muted);\n        font-size: 0.95rem;\n      }\n\n      .status {\n        display: flex;\n        align-items: center;\n        gap: 10px;\n        background: var(--panel);\n        padding: 10px 16px;\n        border-radius: 999px;\n        border: 1px solid rgba(148, 163, 184, 0.25);\n        box-shadow: var(--shadow);\n      }\n\n      .status-dot {\n        width: 12px;\n        height: 12px;\n        border-radius: 50%;\n        background: var(--danger);\n        box-shadow: 0 0 10px rgba(248, 113, 113, 0.8);\n        transition: background 0.2s ease, box-shadow 0.2s ease;\n      }\n\n      .status.connected .status-dot {\n        background: var(--accent-3);\n        box-shadow: 0 0 10px rgba(52, 211, 153, 0.7);\n      }\n\n      /* Metric cards */\n      .stats {\n        display: grid;\n        gap: 16px;\n        grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n        margin-bottom: 22px;\n      }\n\n      .card {\n        background: var(--panel);\n        border-radius: 18px;\n        padding: 16px;\n        border: 1px solid rgba(148, 163, 184, 0.2);\n        box-shadow: var(--shadow);\n        animation: floatIn 0.8s ease both;\n      }\n\n      .card h3 {\n        margin: 0 0 10px;\n        font-size: 0.9rem;\n        color: var(--muted);\n        text-transform: uppercase;\n        letter-spacing: 0.08em;\n      }\n\n      .card .value {\n        font-size: 1.6rem;\n        font-weight: 700;\n      }\n\n      .card .sub {\n        font-family: \"IBM Plex Mono\", monospace;\n        color: var(--muted);\n        font-size: 0.9rem;\n      }\n\n      /* Charts grid */\n      .charts {\n        display: grid;\n        gap: 18px;\n        grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));\n      }\n\n      .chart-panel {\n        background: var(--panel-strong);\n        border-radius: 20px;\n        padding: 18px;\n        border: 1px solid rgba(148, 163, 184, 0.15);\n        box-shadow: var(--shadow);\n        min-height: 320px;\n        display: flex;\n        flex-direction: column;\n        gap: 12px;\n      }\n\n      .chart-panel h2 {\n        margin: 0;\n        font-size: 1.1rem;\n        text-align: center;\n      }\n\n      .chart {\n        flex: 1;\n        min-height: 240px;\n      }\n\n      /* Soft card reveal */\n      @keyframes floatIn {\n        from {\n          opacity: 0;\n          transform: translateY(10px);\n        }\n        to {\n          opacity: 1;\n          transform: translateY(0);\n        }\n      }\n\n      @media (max-width: 720px) {\n        .page {\n          padding: 20px 16px 32px;\n        }\n      }\n    </style>\n  </head>\n  <body data-theme=\"aurora\">\n    <div class=\"page\">\n      <!-- Header with title, theme selector, and connection status -->\n      <header>\n        <div class=\"title\">\n          <h1>ESP WebSocket Telemetry</h1>\n          <span>Real-time heap + RSSI monitoring</span>\n        </div>\n        <div style=\"display: flex; flex-wrap: wrap; gap: 12px; align-items: center;\">\n          <div class=\"theme-switch\">\n            <label for=\"themeSelect\">Theme</label>\n            <select id=\"themeSelect\" aria-label=\"Theme selector\">\n              <option value=\"aurora\">Aurora</option>\n              <option value=\"sunrise\">Sunrise</option>\n              <option value=\"mono\">Mono</option>\n            </select>\n          </div>\n          <div class=\"status\" id=\"statusBadge\">\n            <div class=\"status-dot\" id=\"statusDot\"></div>\n            <div>\n              <div id=\"statusText\">Disconnected</div>\n              <div class=\"sub\" id=\"statusMeta\">Waiting for data...</div>\n            </div>\n          </div>\n        </div>\n      </header>\n\n      <!-- Live metrics -->\n      <section class=\"stats\">\n        <div class=\"card\">\n          <h3>Total Heap</h3>\n          <div class=\"value\" id=\"heapValue\">--</div>\n          <div class=\"sub\" id=\"heapSub\">bytes</div>\n        </div>\n        <div class=\"card\">\n          <h3>Max Block</h3>\n          <div class=\"value\" id=\"blockValue\">--</div>\n          <div class=\"sub\" id=\"blockSub\">bytes</div>\n        </div>\n        <div class=\"card\">\n          <h3>WiFi RSSI</h3>\n          <div class=\"value\" id=\"rssiValue\">--</div>\n          <div class=\"sub\">dBm</div>\n        </div>\n        <div class=\"card\">\n          <h3>Last Update</h3>\n          <div class=\"value\" id=\"timeValue\">--</div>\n          <div class=\"sub\" id=\"timeSub\">local time</div>\n        </div>\n      </section>\n\n      <!-- Chart panels -->\n      <section class=\"charts\">\n        <div class=\"chart-panel\">\n          <h2>Memory Trend</h2>\n          <div id=\"heapChart\" class=\"chart\"></div>\n        </div>\n        <div class=\"chart-panel\">\n          <h2>WiFi Signal</h2>\n          <div id=\"rssiChart\" class=\"chart\"></div>\n        </div>\n      </section>\n    </div>\n\n    <!-- Charting library -->\n    <script src=\"https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js\"></script>\n    <script>\n      // DOM references\n      const statusBadge = document.getElementById(\"statusBadge\");\n      const statusText = document.getElementById(\"statusText\");\n      const statusMeta = document.getElementById(\"statusMeta\");\n      const heapValue = document.getElementById(\"heapValue\");\n      const heapSub = document.getElementById(\"heapSub\");\n      const blockValue = document.getElementById(\"blockValue\");\n      const blockSub = document.getElementById(\"blockSub\");\n      const rssiValue = document.getElementById(\"rssiValue\");\n      const timeValue = document.getElementById(\"timeValue\");\n      const timeSub = document.getElementById(\"timeSub\");\n      const themeSelect = document.getElementById(\"themeSelect\");\n\n      // Chart instances\n      const heapChart = echarts.init(document.getElementById(\"heapChart\"));\n      const rssiChart = echarts.init(document.getElementById(\"rssiChart\"));\n\n      // In-memory time series (bounded by maxPoints)\n      const heapSeries = [];\n      const blockSeries = [];\n      const rssiSeries = [];\n      const maxPoints = 120;\n\n      // Update connection badge\n      function setStatus(isConnected, message) {\n        statusBadge.classList.toggle(\"connected\", isConnected);\n        statusText.textContent = isConnected ? \"Connected\" : \"Disconnected\";\n        statusMeta.textContent = message || \"\";\n      }\n\n      // Human readable byte formatting\n      function formatBytes(value) {\n        if (!Number.isFinite(value)) return \"--\";\n        if (value >= 1024 * 1024) {\n          return (value / (1024 * 1024)).toFixed(2) + \" MB\";\n        }\n        if (value >= 1024) {\n          return (value / 1024).toFixed(1) + \" KB\";\n        }\n        return value.toFixed(0) + \" B\";\n      }\n\n      // Numeric-only axis labels in KB\n      function formatKbNumber(value) {\n        if (!Number.isFinite(value)) return \"--\";\n        return (value / 1024).toFixed(1);\n      }\n\n      // Keep fixed-size series for smooth charting\n      function pushPoint(series, point) {\n        series.push(point);\n        if (series.length > maxPoints) {\n          series.shift();\n        }\n      }\n\n      // Render charts with current series data\n      function updateCharts() {\n        heapChart.setOption({\n          animationDuration: 300,\n          grid: { left: 40, right: 20, top: 20, bottom: 44 },\n          tooltip: { trigger: \"axis\" },\n          xAxis: {\n            type: \"time\",\n            axisLine: { lineStyle: { color: \"rgba(148,163,184,0.6)\" } },\n            splitLine: { show: false },\n            axisLabel: { fontSize: 11, rotate: 45 },\n          },\n          yAxis: {\n            type: \"value\",\n            name: \"KB\",\n            nameLocation: \"middle\",\n            nameGap: 29,\n            nameRotate: 90,\n            nameTextStyle: { color: \"rgba(148,163,184,0.7)\", fontSize: 11 },\n            axisLine: { lineStyle: { color: \"rgba(148,163,184,0.6)\" } },\n            splitLine: { lineStyle: { color: \"rgba(148,163,184,0.15)\" } },\n            axisLabel: { formatter: (value) => formatKbNumber(value), fontSize: 11 },\n          },\n          series: [\n            {\n              name: \"Total Heap\",\n              type: \"line\",\n              smooth: true,\n              showSymbol: false,\n              data: heapSeries,\n              lineStyle: { color: \"#22d3ee\", width: 2 },\n              areaStyle: { color: \"rgba(34,211,238,0.25)\" },\n            },\n            {\n              name: \"Max Block\",\n              type: \"line\",\n              smooth: true,\n              showSymbol: false,\n              data: blockSeries,\n              lineStyle: { color: \"#f59e0b\", width: 2 },\n              areaStyle: { color: \"rgba(245,158,11,0.2)\" },\n            },\n          ],\n        });\n\n        rssiChart.setOption({\n          animationDuration: 300,\n          grid: { left: 40, right: 20, top: 20, bottom: 44 },\n          tooltip: { trigger: \"axis\" },\n          xAxis: {\n            type: \"time\",\n            axisLine: { lineStyle: { color: \"rgba(148,163,184,0.6)\" } },\n            splitLine: { show: false },\n            axisLabel: { fontSize: 11, rotate: 45 },\n          },\n          yAxis: {\n            type: \"value\",\n            name: \"dBm\",\n            nameLocation: \"middle\",\n            nameGap: 29,\n            nameRotate: 90,\n            nameTextStyle: { color: \"rgba(148,163,184,0.7)\", fontSize: 11 },\n            max: -30,\n            min: -95,\n            axisLine: { lineStyle: { color: \"rgba(148,163,184,0.6)\" } },\n            splitLine: { lineStyle: { color: \"rgba(148,163,184,0.15)\" } },\n            axisLabel: { formatter: (value) => Math.round(value).toString(), fontSize: 11 },\n          },\n          series: [\n            {\n              name: \"RSSI\",\n              type: \"line\",\n              smooth: true,\n              showSymbol: false,\n              data: rssiSeries,\n              lineStyle: { color: \"#34d399\", width: 2 },\n              areaStyle: { color: \"rgba(52,211,153,0.18)\" },\n              markLine: {\n                symbol: \"none\",\n                lineStyle: { color: \"rgba(148,163,184,0.55)\", width: 1 },\n                label: {\n                  show: true,\n                  position: \"insideEnd\",\n                  distance: 8,\n                  fontSize: 10,\n                  color: \"rgba(148,163,184,0.75)\",\n                  formatter: \"-70 dBm\",\n                },\n                data: [{ yAxis: -70 }],\n              },\n            },\n          ],\n        });\n      }\n\n      // Apply incoming WebSocket payload\n      function handleMessage(data) {\n        if (!data || !data.addPoint) return;\n        const timestamp = (data.timestamp || 0) * 1000;\n        if (!timestamp) return;\n\n        pushPoint(heapSeries, [timestamp, data.totalHeap || 0]);\n        pushPoint(blockSeries, [timestamp, data.maxBlock || 0]);\n        pushPoint(rssiSeries, [timestamp, data.rssi || 0]);\n\n        heapValue.textContent = formatBytes(data.totalHeap || 0);\n        heapSub.textContent = \"bytes\";\n        blockValue.textContent = formatBytes(data.maxBlock || 0);\n        blockSub.textContent = \"bytes\";\n        rssiValue.textContent = (data.rssi || 0).toFixed(0);\n\n        const localTime = new Date(timestamp);\n        timeValue.textContent = localTime.toLocaleTimeString();\n        timeSub.textContent = localTime.toLocaleDateString();\n\n        updateCharts();\n      }\n\n      // WebSocket connection state\n      let socket;\n      let reconnectTimer;\n\n      // Apply theme and persist selection\n      function applyTheme(value) {\n        document.body.setAttribute(\"data-theme\", value);\n        try {\n          localStorage.setItem(\"esp-theme\", value);\n        } catch (err) {\n          // Storage can fail in private mode.\n        }\n      }\n\n      // Initialize theme selector with saved preference\n      function initTheme() {\n        let saved = \"aurora\";\n        try {\n          saved = localStorage.getItem(\"esp-theme\") || saved;\n        } catch (err) {\n          saved = \"aurora\";\n        }\n        themeSelect.value = saved;\n        applyTheme(saved);\n        themeSelect.addEventListener(\"change\", (event) => {\n          applyTheme(event.target.value);\n        });\n      }\n\n      // Connect to ESP WebSocket endpoint\n      function connect() {\n        clearTimeout(reconnectTimer);\n        const proto = location.protocol === \"https:\" ? \"wss\" : \"ws\";\n        const wsUrl = proto + \"://\" + location.hostname + \":81/ws\";\n        statusMeta.textContent = \"Connecting to \" + wsUrl + \"...\";\n\n        socket = new WebSocket(wsUrl);\n\n        socket.addEventListener(\"open\", () => {\n          setStatus(true, \"Receiving live data\");\n        });\n\n        socket.addEventListener(\"close\", () => {\n          setStatus(false, \"Reconnecting...\");\n          reconnectTimer = setTimeout(connect, 1500);\n        });\n\n        socket.addEventListener(\"error\", () => {\n          setStatus(false, \"Connection error\");\n          socket.close();\n        });\n\n        socket.addEventListener(\"message\", (event) => {\n          try {\n            const payload = JSON.parse(event.data);\n            handleMessage(payload);\n          } catch (err) {\n            statusMeta.textContent = \"Invalid data\";\n          }\n        });\n      }\n\n      // Boot sequence\n      updateCharts();\n      initTheme();\n      connect();\n      window.addEventListener(\"resize\", () => {\n        heapChart.resize();\n        rssiChart.resize();\n      });\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "pio_examples/websocketEcharts/data/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <title>ESP WebSocket Telemetry</title>\n    <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;600&family=Space+Grotesk:wght@400;600;700&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <style>\n      /* Theme tokens */\n      :root {\n        --bg-1: #0b0f1a;\n        --bg-2: #14253d;\n        --panel: #111827cc;\n        --panel-strong: #0f172a;\n        --text: #e5e7eb;\n        --muted: #94a3b8;\n        --accent: #22d3ee;\n        --accent-2: #f59e0b;\n        --accent-3: #34d399;\n        --danger: #f87171;\n        --grid: rgba(148, 163, 184, 0.2);\n        --shadow: 0 18px 45px rgba(2, 6, 23, 0.55);\n      }\n\n      /* Theme variants */\n      body[data-theme=\"sunrise\"] {\n        --bg-1: #1b0c24;\n        --bg-2: #3f1a3a;\n        --panel: #1f1230cc;\n        --panel-strong: #1b0f2a;\n        --text: #fdf2f8;\n        --muted: #f9a8d4;\n        --accent: #fb7185;\n        --accent-2: #f59e0b;\n        --accent-3: #f472b6;\n        --danger: #f43f5e;\n        --grid: rgba(251, 113, 133, 0.2);\n        --shadow: 0 18px 45px rgba(24, 5, 32, 0.6);\n      }\n\n      body[data-theme=\"mono\"] {\n        --bg-1: #0b0d0f;\n        --bg-2: #1b2128;\n        --panel: #0f1318cc;\n        --panel-strong: #0f141a;\n        --text: #e2e8f0;\n        --muted: #94a3b8;\n        --accent: #e2e8f0;\n        --accent-2: #94a3b8;\n        --accent-3: #cbd5f5;\n        --danger: #f87171;\n        --grid: rgba(148, 163, 184, 0.2);\n        --shadow: 0 18px 45px rgba(5, 8, 12, 0.65);\n      }\n\n      /* Base reset */\n      * {\n        box-sizing: border-box;\n      }\n\n      /* Page background and typography */\n      body {\n        margin: 0;\n        font-family: \"Space Grotesk\", system-ui, sans-serif;\n        color: var(--text);\n        background: radial-gradient(1200px 600px at 15% 20%, #1b3a5e 0%, transparent 60%),\n          radial-gradient(900px 500px at 85% 0%, #15304e 0%, transparent 60%),\n          linear-gradient(180deg, var(--bg-1), var(--bg-2));\n        min-height: 100vh;\n      }\n\n      body[data-theme=\"sunrise\"] {\n        background: radial-gradient(1000px 600px at 12% 15%, #8b215b 0%, transparent 60%),\n          radial-gradient(900px 500px at 85% 5%, #f97316 0%, transparent 55%),\n          linear-gradient(180deg, var(--bg-1), var(--bg-2));\n      }\n\n      body[data-theme=\"mono\"] {\n        background: radial-gradient(1000px 600px at 12% 15%, #2b3640 0%, transparent 60%),\n          radial-gradient(900px 500px at 85% 5%, #1f2a33 0%, transparent 55%),\n          linear-gradient(180deg, var(--bg-1), var(--bg-2));\n      }\n\n      /* Subtle grid overlay */\n      body::before {\n        content: \"\";\n        position: fixed;\n        inset: 0;\n        background-image: radial-gradient(rgba(148, 163, 184, 0.15) 1px, transparent 0);\n        background-size: 24px 24px;\n        pointer-events: none;\n        opacity: 0.35;\n      }\n\n      /* Layout container */\n      .page {\n        max-width: 1200px;\n        margin: 0 auto;\n        padding: 28px 22px 40px;\n        position: relative;\n      }\n\n      /* Top header */\n      header {\n        display: flex;\n        flex-wrap: wrap;\n        align-items: center;\n        justify-content: space-between;\n        gap: 16px;\n        margin-bottom: 22px;\n      }\n\n      /* Theme selector pill */\n      .theme-switch {\n        display: flex;\n        align-items: center;\n        gap: 10px;\n        padding: 8px 12px;\n        border-radius: 999px;\n        border: 1px solid rgba(148, 163, 184, 0.25);\n        background: var(--panel);\n        box-shadow: var(--shadow);\n        font-size: 0.9rem;\n        color: var(--muted);\n      }\n\n      .theme-switch select {\n        appearance: none;\n        background: var(--panel-strong);\n        color: var(--text);\n        border: 1px solid rgba(148, 163, 184, 0.25);\n        border-radius: 999px;\n        padding: 6px 28px 6px 12px;\n        font-family: \"Space Grotesk\", system-ui, sans-serif;\n        font-size: 0.9rem;\n        position: relative;\n      }\n\n      .theme-switch select:focus {\n        outline: 2px solid rgba(34, 211, 238, 0.45);\n        outline-offset: 2px;\n      }\n\n      .title {\n        display: flex;\n        flex-direction: column;\n        gap: 4px;\n      }\n\n      .title h1 {\n        font-size: clamp(28px, 3vw, 38px);\n        margin: 0;\n        letter-spacing: -0.02em;\n      }\n\n      .title span {\n        color: var(--muted);\n        font-size: 0.95rem;\n      }\n\n      .status {\n        display: flex;\n        align-items: center;\n        gap: 10px;\n        background: var(--panel);\n        padding: 10px 16px;\n        border-radius: 999px;\n        border: 1px solid rgba(148, 163, 184, 0.25);\n        box-shadow: var(--shadow);\n      }\n\n      .status-dot {\n        width: 12px;\n        height: 12px;\n        border-radius: 50%;\n        background: var(--danger);\n        box-shadow: 0 0 10px rgba(248, 113, 113, 0.8);\n        transition: background 0.2s ease, box-shadow 0.2s ease;\n      }\n\n      .status.connected .status-dot {\n        background: var(--accent-3);\n        box-shadow: 0 0 10px rgba(52, 211, 153, 0.7);\n      }\n\n      /* Metric cards */\n      .stats {\n        display: grid;\n        gap: 16px;\n        grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n        margin-bottom: 22px;\n      }\n\n      .card {\n        background: var(--panel);\n        border-radius: 18px;\n        padding: 16px;\n        border: 1px solid rgba(148, 163, 184, 0.2);\n        box-shadow: var(--shadow);\n        animation: floatIn 0.8s ease both;\n      }\n\n      .card h3 {\n        margin: 0 0 10px;\n        font-size: 0.9rem;\n        color: var(--muted);\n        text-transform: uppercase;\n        letter-spacing: 0.08em;\n      }\n\n      .card .value {\n        font-size: 1.6rem;\n        font-weight: 700;\n      }\n\n      .card .sub {\n        font-family: \"IBM Plex Mono\", monospace;\n        color: var(--muted);\n        font-size: 0.9rem;\n      }\n\n      /* Charts grid */\n      .charts {\n        display: grid;\n        gap: 18px;\n        grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));\n      }\n\n      .chart-panel {\n        background: var(--panel-strong);\n        border-radius: 20px;\n        padding: 18px;\n        border: 1px solid rgba(148, 163, 184, 0.15);\n        box-shadow: var(--shadow);\n        min-height: 320px;\n        display: flex;\n        flex-direction: column;\n        gap: 12px;\n      }\n\n      .chart-panel h2 {\n        margin: 0;\n        font-size: 1.1rem;\n      }\n\n      .chart {\n        flex: 1;\n        min-height: 240px;\n      }\n\n      /* Soft card reveal */\n      @keyframes floatIn {\n        from {\n          opacity: 0;\n          transform: translateY(10px);\n        }\n        to {\n          opacity: 1;\n          transform: translateY(0);\n        }\n      }\n\n      @media (max-width: 720px) {\n        .page {\n          padding: 20px 16px 32px;\n        }\n      }\n    </style>\n  </head>\n  <body data-theme=\"aurora\">\n    <div class=\"page\">\n      <!-- Header with title, theme selector, and connection status -->\n      <header>\n        <div class=\"title\">\n          <h1>ESP WebSocket Telemetry</h1>\n          <span>Real-time heap + RSSI monitoring</span>\n        </div>\n        <div style=\"display: flex; flex-wrap: wrap; gap: 12px; align-items: center;\">\n          <div class=\"theme-switch\">\n            <label for=\"themeSelect\">Theme</label>\n            <select id=\"themeSelect\" aria-label=\"Theme selector\">\n              <option value=\"aurora\">Aurora</option>\n              <option value=\"sunrise\">Sunrise</option>\n              <option value=\"mono\">Mono</option>\n            </select>\n          </div>\n          <div class=\"status\" id=\"statusBadge\">\n            <div class=\"status-dot\" id=\"statusDot\"></div>\n            <div>\n              <div id=\"statusText\">Disconnected</div>\n              <div class=\"sub\" id=\"statusMeta\">Waiting for data...</div>\n            </div>\n          </div>\n        </div>\n      </header>\n\n      <!-- Live metrics -->\n      <section class=\"stats\">\n        <div class=\"card\">\n          <h3>Total Heap</h3>\n          <div class=\"value\" id=\"heapValue\">--</div>\n          <div class=\"sub\" id=\"heapSub\">bytes</div>\n        </div>\n        <div class=\"card\">\n          <h3>Max Block</h3>\n          <div class=\"value\" id=\"blockValue\">--</div>\n          <div class=\"sub\" id=\"blockSub\">bytes</div>\n        </div>\n        <div class=\"card\">\n          <h3>WiFi RSSI</h3>\n          <div class=\"value\" id=\"rssiValue\">--</div>\n          <div class=\"sub\">dBm</div>\n        </div>\n        <div class=\"card\">\n          <h3>Last Update</h3>\n          <div class=\"value\" id=\"timeValue\">--</div>\n          <div class=\"sub\" id=\"timeSub\">local time</div>\n        </div>\n      </section>\n\n      <!-- Chart panels -->\n      <section class=\"charts\">\n        <div class=\"chart-panel\">\n          <h2>Memory Trend</h2>\n          <div id=\"heapChart\" class=\"chart\"></div>\n        </div>\n        <div class=\"chart-panel\">\n          <h2>WiFi Signal</h2>\n          <div id=\"rssiChart\" class=\"chart\"></div>\n        </div>\n      </section>\n    </div>\n\n    <!-- Charting library -->\n    <script src=\"https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js\"></script>\n    <script>\n      // DOM references\n      const statusBadge = document.getElementById(\"statusBadge\");\n      const statusText = document.getElementById(\"statusText\");\n      const statusMeta = document.getElementById(\"statusMeta\");\n      const heapValue = document.getElementById(\"heapValue\");\n      const heapSub = document.getElementById(\"heapSub\");\n      const blockValue = document.getElementById(\"blockValue\");\n      const blockSub = document.getElementById(\"blockSub\");\n      const rssiValue = document.getElementById(\"rssiValue\");\n      const timeValue = document.getElementById(\"timeValue\");\n      const timeSub = document.getElementById(\"timeSub\");\n      const themeSelect = document.getElementById(\"themeSelect\");\n\n      // Chart instances\n      const heapChart = echarts.init(document.getElementById(\"heapChart\"));\n      const rssiChart = echarts.init(document.getElementById(\"rssiChart\"));\n\n      // In-memory time series (bounded by maxPoints)\n      const heapSeries = [];\n      const blockSeries = [];\n      const rssiSeries = [];\n      const maxPoints = 120;\n\n      // Update connection badge\n      function setStatus(isConnected, message) {\n        statusBadge.classList.toggle(\"connected\", isConnected);\n        statusText.textContent = isConnected ? \"Connected\" : \"Disconnected\";\n        statusMeta.textContent = message || \"\";\n      }\n\n      // Human readable byte formatting\n      function formatBytes(value) {\n        if (!Number.isFinite(value)) return \"--\";\n        if (value >= 1024 * 1024) {\n          return (value / (1024 * 1024)).toFixed(2) + \" MB\";\n        }\n        if (value >= 1024) {\n          return (value / 1024).toFixed(1) + \" KB\";\n        }\n        return value.toFixed(0) + \" B\";\n      }\n\n      // Numeric-only axis labels\n      function formatNumber(value) {\n        if (!Number.isFinite(value)) return \"--\";\n        return Math.round(value).toLocaleString();\n      }\n\n      // Keep fixed-size series for smooth charting\n      function pushPoint(series, point) {\n        series.push(point);\n        if (series.length > maxPoints) {\n          series.shift();\n        }\n      }\n\n      // Render charts with current series data\n      function updateCharts() {\n        heapChart.setOption({\n          animationDuration: 300,\n          grid: { left: 40, right: 20, top: 20, bottom: 30 },\n          tooltip: { trigger: \"axis\" },\n          xAxis: {\n            type: \"time\",\n            axisLine: { lineStyle: { color: \"rgba(148,163,184,0.6)\" } },\n            splitLine: { show: false },\n          },\n          yAxis: {\n            type: \"value\",\n            name: \"Bytes\",\n            nameLocation: \"end\",\n            nameGap: 12,\n            nameTextStyle: { color: \"rgba(148,163,184,0.7)\", fontSize: 12 },\n            axisLine: { lineStyle: { color: \"rgba(148,163,184,0.6)\" } },\n            splitLine: { lineStyle: { color: \"rgba(148,163,184,0.15)\" } },\n            axisLabel: { formatter: (value) => formatNumber(value) },\n          },\n          series: [\n            {\n              name: \"Total Heap\",\n              type: \"line\",\n              smooth: true,\n              showSymbol: false,\n              data: heapSeries,\n              lineStyle: { color: \"#22d3ee\", width: 2 },\n              areaStyle: { color: \"rgba(34,211,238,0.25)\" },\n            },\n            {\n              name: \"Max Block\",\n              type: \"line\",\n              smooth: true,\n              showSymbol: false,\n              data: blockSeries,\n              lineStyle: { color: \"#f59e0b\", width: 2 },\n              areaStyle: { color: \"rgba(245,158,11,0.2)\" },\n            },\n          ],\n        });\n\n        rssiChart.setOption({\n          animationDuration: 300,\n          grid: { left: 40, right: 20, top: 20, bottom: 30 },\n          tooltip: { trigger: \"axis\" },\n          xAxis: {\n            type: \"time\",\n            axisLine: { lineStyle: { color: \"rgba(148,163,184,0.6)\" } },\n            splitLine: { show: false },\n          },\n          yAxis: {\n            type: \"value\",\n            name: \"dBm\",\n            nameLocation: \"end\",\n            nameGap: 12,\n            nameTextStyle: { color: \"rgba(148,163,184,0.7)\", fontSize: 12 },\n            max: -30,\n            min: -95,\n            axisLine: { lineStyle: { color: \"rgba(148,163,184,0.6)\" } },\n            splitLine: { lineStyle: { color: \"rgba(148,163,184,0.15)\" } },\n            axisLabel: { formatter: (value) => Math.round(value).toString() },\n          },\n          series: [\n            {\n              name: \"RSSI\",\n              type: \"line\",\n              smooth: true,\n              showSymbol: false,\n              data: rssiSeries,\n              lineStyle: { color: \"#34d399\", width: 2 },\n              areaStyle: { color: \"rgba(52,211,153,0.18)\" },\n              markLine: {\n                symbol: \"none\",\n                lineStyle: { color: \"rgba(248,113,113,0.8)\", type: \"dashed\" },\n                data: [{ yAxis: -70, name: \"Weak\" }],\n              },\n            },\n          ],\n        });\n      }\n\n      // Apply incoming WebSocket payload\n      function handleMessage(data) {\n        if (!data || !data.addPoint) return;\n        const timestamp = (data.timestamp || 0) * 1000;\n        if (!timestamp) return;\n\n        pushPoint(heapSeries, [timestamp, data.totalHeap || 0]);\n        pushPoint(blockSeries, [timestamp, data.maxBlock || 0]);\n        pushPoint(rssiSeries, [timestamp, data.rssi || 0]);\n\n        heapValue.textContent = formatBytes(data.totalHeap || 0);\n        heapSub.textContent = \"bytes\";\n        blockValue.textContent = formatBytes(data.maxBlock || 0);\n        blockSub.textContent = \"bytes\";\n        rssiValue.textContent = (data.rssi || 0).toFixed(0);\n\n        const localTime = new Date(timestamp);\n        timeValue.textContent = localTime.toLocaleTimeString();\n        timeSub.textContent = localTime.toLocaleDateString();\n\n        updateCharts();\n      }\n\n      // WebSocket connection state\n      let socket;\n      let reconnectTimer;\n\n      // Apply theme and persist selection\n      function applyTheme(value) {\n        document.body.setAttribute(\"data-theme\", value);\n        try {\n          localStorage.setItem(\"esp-theme\", value);\n        } catch (err) {\n          // Storage can fail in private mode.\n        }\n      }\n\n      // Initialize theme selector with saved preference\n      function initTheme() {\n        let saved = \"aurora\";\n        try {\n          saved = localStorage.getItem(\"esp-theme\") || saved;\n        } catch (err) {\n          saved = \"aurora\";\n        }\n        themeSelect.value = saved;\n        applyTheme(saved);\n        themeSelect.addEventListener(\"change\", (event) => {\n          applyTheme(event.target.value);\n        });\n      }\n\n      // Connect to ESP WebSocket endpoint\n      function connect() {\n        clearTimeout(reconnectTimer);\n        const proto = location.protocol === \"https:\" ? \"wss\" : \"ws\";\n        const wsUrl = proto + \"://\" + location.hostname + \":81/ws\";\n        statusMeta.textContent = \"Connecting to \" + wsUrl + \"...\";\n\n        socket = new WebSocket(wsUrl);\n\n        socket.addEventListener(\"open\", () => {\n          setStatus(true, \"Receiving live data\");\n        });\n\n        socket.addEventListener(\"close\", () => {\n          setStatus(false, \"Reconnecting...\");\n          reconnectTimer = setTimeout(connect, 1500);\n        });\n\n        socket.addEventListener(\"error\", () => {\n          setStatus(false, \"Connection error\");\n          socket.close();\n        });\n\n        socket.addEventListener(\"message\", (event) => {\n          try {\n            const payload = JSON.parse(event.data);\n            handleMessage(payload);\n          } catch (err) {\n            statusMeta.textContent = \"Invalid data\";\n          }\n        });\n      }\n\n      // Boot sequence\n      updateCharts();\n      initTheme();\n      connect();\n      window.addEventListener(\"resize\", () => {\n        heapChart.resize();\n        rssiChart.resize();\n      });\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "pio_examples/websocketEcharts/platformio.ini",
    "content": "; PlatformIO Project Configuration File\n;\n;   Build options: build flags, source filter\n;   Upload options: custom upload port, speed and extra flags\n;   Library options: dependencies, extra library storages\n;   Advanced options: extra scripting\n;\n; Please visit documentation for the other options and examples\n; https://docs.platformio.org/page/projectconf.html\n\n[env:esp32-s3-devkitc-1]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32-s3-devkitc-1\nframework = arduino\nlib_extra_dirs = ../../\n\n\n[env:esp32dev]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32dev\nframework = arduino\nlib_extra_dirs = ../../\n\n[env:esp8266-nodemcuv2]\nplatform = espressif8266\nboard = nodemcuv2\nframework = arduino\nupload_speed = 921600\nlib_extra_dirs = ../../\n\n[env:seeed_xiao_esp32_c6]\nplatform = https://github.com/Seeed-Studio/platform-seeedboards.git\nboard = seeed-xiao-esp32-c6\nframework = arduino\nlib_extra_dirs = ../../"
  },
  {
    "path": "pio_examples/websocketEcharts/src/websocketEcharts.ino",
    "content": "#include <Arduino.h>\n#include <FS.h>\n#include <LittleFS.h>\n#include <FSWebServer.h>   // https://github.com/cotestatnt/esp-fs-webserver/\n#if defined(ESP32)\n#include <WiFi.h>\n#elif defined(ESP8266)\n#include <ESP8266WiFi.h>\n#endif\n\n#define FILESYSTEM LittleFS\n\nFSWebServer server(FILESYSTEM, 80, \"mywebserver\");\n\n#ifndef LED_BUILTIN\n#define LED_BUILTIN 2\n#endif\n\n// Timezone definition to get properly time from NTP server\n#define MYTZ \"CET-1CEST,M3.5.0,M10.5.0/3\"\nstruct tm Time;\n\n////////////////////////////////   WebSocket Handler  /////////////////////////////\nvoid webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {\n  switch (type) {\n    case WStype_DISCONNECTED:\n      Serial.printf(\"[%u] Disconnected!\\n\", num);\n      break;\n    case WStype_CONNECTED: {\n        IPAddress ip = server.getWebSocketServer()->remoteIP(num);\n        Serial.printf(\"Hello client #%d [%s]\\n\", (int)num, ip.toString().c_str());\n      }\n      break;\n    case WStype_TEXT:\n      Serial.printf(\"[%u] get Text: %s\\n\", num, payload);\n      break;\n    case WStype_BIN:\n      Serial.printf(\"[%u] get binary length: %u\\n\", num, length);\n      break;\n    default:\n      break;\n  }\n}\n\n\n////////////////////////////////  NTP Time  /////////////////////////////////////\nvoid getUpdatedtime(const uint32_t timeout) {\n  uint32_t start = millis();\n  Serial.print(\"Sync time...\");\n  while (millis() - start < timeout  && Time.tm_year <= (1970 - 1900)) {\n    time_t now = time(nullptr);\n    Time = *localtime(&now);\n    delay(5);\n  }\n  Serial.println(\" done.\");\n}\n\n\n////////////////////////////////  Filesystem  /////////////////////////////////////////\nbool startFilesystem() {\n  if (FILESYSTEM.begin()){\n    server.printFileList(FILESYSTEM, \"/\", 1, Serial);\n    return true;\n  }\n  else {\n    Serial.println(\"ERROR on mounting filesystem. It will be reformatted!\");\n    FILESYSTEM.format();\n    ESP.restart();\n  }\n  return false;\n}\n\n\n\nvoid setup() {\n  pinMode(LED_BUILTIN, OUTPUT);\n  Serial.begin(115200);\n\n  // FILESYSTEM INIT\n  startFilesystem();\n\n  // Try to connect to WiFi (will start AP if not connected after timeout)\n  if (!server.startWiFi(10000)) {\n    Serial.println(\"\\nWiFi not connected! Starting AP mode...\");\n    server.startCaptivePortal(\"ESP_AP\", \"123456789\", \"/setup\");\n  }\n\n  // Enable ACE FS file web editor and add FS info callback function\n  server.enableFsCodeEditor();\n\n  // Start server with custom websocket event handler\n  server.begin(webSocketEvent);\n  Serial.print(F(\"ESP Web Server started on IP Address: \"));\n  Serial.println(server.getServerIP());\n  Serial.println(F(\n    \"This is \\\"highcharts.ino\\\" example.\\n\"\n    \"Open /setup page to configure optional parameters.\\n\"\n    \"Open /edit page to view, edit or upload example or your custom webserver source files.\"\n  ));\n}\n\n\nvoid loop() {\n  server.run();  // Handle client requests and websocket events\n\n  // Send ESP system time (epoch) and heap stats to WS client\n  static uint32_t sendToClientTime;\n  if (millis() - sendToClientTime > 1000 ) {\n    sendToClientTime = millis();\n    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));\n\n    time_t now = time(nullptr);\n    CJSON::Json doc;\n    doc.setBool(\"addPoint\", true);\n    doc.setNumber(\"timestamp\", (double)now);\n#ifdef ESP32\n    doc.setNumber(\"totalHeap\", (double)heap_caps_get_free_size(0));\n    doc.setNumber(\"maxBlock\", (double)heap_caps_get_largest_free_block(0));\n#elif defined(ESP8266)\n    uint32_t free;\n    uint32_t max;\n    ESP.getHeapStats(&free, &max, nullptr);\n    doc.setNumber(\"totalHeap\", (double)free);\n    doc.setNumber(\"maxBlock\", (double)max);\n#endif\n    doc.setNumber(\"rssi\", (double)WiFi.RSSI());\n    String msg = doc.serialize();\n    server.broadcastWebSocket(msg);\n  }\n\n}"
  },
  {
    "path": "pio_examples/withWebSocket/.gitignore",
    "content": ".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",
    "content": "# Name,   Type, SubType, Offset,  Size, Flags\nnvs,      data, nvs,     0x9000,  0x5000,\notadata,  data, ota,     0xe000,  0x2000,\napp0,     app,  ota_0,   0x10000, 0x140000,\napp1,     app,  ota_1,   0x150000,0x140000,\nspiffs,   data, spiffs,  0x290000,0x160000,\ncoredump, data, coredump,0x3F0000,0x10000,\n"
  },
  {
    "path": "pio_examples/withWebSocket/platformio.ini",
    "content": "; PlatformIO Project Configuration File\n;\n;   Build options: build flags, source filter\n;   Upload options: custom upload port, speed and extra flags\n;   Library options: dependencies, extra library storages\n;   Advanced options: extra scripting\n;\n; Please visit documentation for the other options and examples\n; https://docs.platformio.org/page/projectconf.html\n\n[env:esp32-s3-devkitc1-n4r2]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32-s3-devkitc1-n4r2\nframework = arduino\nboard_build.partitions = partitions.csv\nlib_extra_dirs = ../../\nlib_ignore = pio_examples\n\n\n[env:esp32dev]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip\nboard = esp32dev\nframework = arduino\nlib_extra_dirs = ../../\nlib_ignore = pio_examples\n\n[env:esp8266-nodemcuv2]\nplatform = espressif8266\nboard = nodemcuv2\nframework = arduino\nupload_speed = 921600\nlib_extra_dirs = ../../"
  },
  {
    "path": "pio_examples/withWebSocket/readme.md",
    "content": "## AsyncFsWebServer – withWebSocket example\n\nThis folder contains a PlatformIO example and a small documentation set for the `AsyncFsWebServer` library.\n\n### Documentation (available methods + usage)\n\n- [docs/README.md](../../docs/README.md)\n- [docs/API.md](../../docs/API.md)\n- [docs/SetupAndWiFi.md](../../docs/SetupAndWiFi.md)\n- [docs/FileEditorAndFS.md](../../docs/FileEditorAndFS.md)\n- [docs/WebSocket.md](../../docs/WebSocket.md)\n\n### Quick start (summary)\n\n```cpp\nAsyncFsWebServer server(80, FILESYSTEM, hostname);\n\nif (FILESYSTEM.begin()) {\n\tserver.printFileList(FILESYSTEM, \"/\", 1); // default -> Serial\n}\n\nif (!server.startWiFi(10000)) {\n\tserver.startCaptivePortal(\"ESP_AP\", \"123456789\", \"/setup\");\n}\n\nserver.enableFsCodeEditor();\nserver.init(onWsEvent);\n```\n\n---\n\n## Original note (example description)\nThis example is a bit more advanced than the [simpleServer](https://github.com/cotestatnt/esp-fs-webserver/tree/main/examples/simpleServer) example.\n\nBasically, it uses the same HTML as simpleServer, but with the addition of a **WebSocket client** in the web page.\n\nWith WebSocket technology, we can send messages from server-to-client or client-to-server in a **full-duplex communication channel over a single TCP connection.**\n\nIn this simple example, it is used only to push a message from the ESP (the NTP-synchronized system time) to connected clients, just to show how to set up a system like this.\n\n![image](https://user-images.githubusercontent.com/27758688/151001497-6468b50f-d4cb-46e1-ab4c-4d7aca3883db.png)\n"
  },
  {
    "path": "pio_examples/withWebSocket/src/index_htm.h",
    "content": "#pragma once\n#include <Arduino.h>\n\ninline const char homepage[] PROGMEM = R\"EOF(\n<!DOCTYPE html>\n<html>\n  <head>\n    <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\">\n    <title>WebSocket test page</title>\n    \n    <style type=\"text/css\" media=\"screen\">\n      body {width: 70%; margin: auto; background-color: black; color: #55ff55; font-family: monospace; text-align: center; }\n      .box {border: 1px solid white; border-radius: 10px; padding: 5px; margin: 10px; }\n      .output {color: white; }\n      #log {text-align: left; margin: 20px; max-height: 500px; overflow: auto; }\n      #input_div, #input_el {line-height: 13px; color: #AAA;}\n      #input_el { width:97%; background-color: rgba(0,0,0,0); border: 0px;}\n      #input_el:focus {outline: none; }\n      #icon-setup{float: left;margin: 20px}\n    </style>\n    \n    <script type=\"text/javascript\">\n      var ws = null;\n      function ge(s){ return document.getElementById(s);} // Shorthand for get element\n      function ce(s){ return document.createElement(s);}  // Shorthand for create element\n      function stb(){ window.scrollTo(0, document.body.scrollHeight || document.documentElement.scrollHeight); } // Scroll to bottom\n      \n      // This function will add a message to the HTML element with id 'log'\n      function addMessage(m){\n        var msg = ce(\"div\");\n        msg.innerText = m;\n        ge(\"log\").appendChild(msg);\n        stb();  \n      }\n      \n      // If msg is a obj data tyep, handle it (just timestamp in this example) otherwise add to log console\n      function parseMessage(msg) {\n        try {\n          const obj = JSON.parse(msg);\n          if (typeof obj === 'object' && obj !== null) {\n            if (obj.esptime !== null) {\n              var date = new Date(0); // The 0 sets the date to epoch\n              if( date.setUTCSeconds(obj.esptime))\n                document.getElementById(\"esp-time\").innerHTML = date;\n            }\n          }\n        } catch {\n          addMessage(msg);\n        }\n      }\n      \n      // Configure and start WebSocket client\n      function startSocket(){\n        ws = new WebSocket('ws://' + location.hostname + ':81/');\n        ws.binaryType = \"arraybuffer\";\n        ws.onopen = function(e){\n          addMessage(\"WebSocket client connected to \" + 'ws://'+document.location.host+'/ws');\n        };\n        ws.onclose = function(e){\n          addMessage(\"WebSocket client disconnected\");\n        };\n        ws.onerror = function(e){\n          console.log(\"ws error\", e);\n          addMessage(\"Error\");\n        };\n        ws.onmessage = function(e){\n          parseMessage(e.data)\n        };\n        // Add event \"keydown\" listener to input box\n        ge(\"input_el\").onkeydown = function(e){\n          stb();\n          if(e.keyCode == 13 && ge(\"input_el\").value != \"\"){\n            ws.send(ge(\"input_el\").value);\n            ge(\"input_el\").value = \"\";\n          }\n        }\n      }\n  \n      // When page is fully loaded start connection\n      function onBodyLoad(){\n        startSocket();\n      }\n    </script>\n  </head>\n  <body id=\"body\" onload=\"onBodyLoad()\">\n    \n    <div class=icon>\n      <a id=icon-setup href=/setup>\n        <svg width=\"48\" height=\"48\" fill=#55ff55 viewBox=\"0 0 24 24\">\n          <path d=\"M12,8A4,4 0 0,1 16,12A4,4 0 0,1 12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8M12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12A2,2 0 0,0 12,10M10,22C9.75,22 9.54,21.82 9.5,21.58L9.13,18.93C8.5,18.68 7.96,18.34 7.44,17.94L4.95,18.95C4.73,19.03 4.46,18.95 4.34,18.73L2.34,15.27C2.21,15.05 2.27,14.78 2.46,14.63L4.57,12.97L4.5,12L4.57,11L2.46,9.37C2.27,9.22 2.21,8.95 2.34,8.73L4.34,5.27C4.46,5.05 4.73,4.96 4.95,5.05L7.44,6.05C7.96,5.66 8.5,5.32 9.13,5.07L9.5,2.42C9.54,2.18 9.75,2 10,2H14C14.25,2 14.46,2.18 14.5,2.42L14.87,5.07C15.5,5.32 16.04,5.66 16.56,6.05L19.05,5.05C19.27,4.96 19.54,5.05 19.66,5.27L21.66,8.73C21.79,8.95 21.73,9.22 21.54,9.37L19.43,11L19.5,12L19.43,13L21.54,14.63C21.73,14.78 21.79,15.05 21.66,15.27L19.66,18.73C19.54,18.95 19.27,19.04 19.05,18.95L16.56,17.95C16.04,18.34 15.5,18.68 14.87,18.93L14.5,21.58C14.46,21.82 14.25,22 14,22H10M11.25,4L10.88,6.61C9.68,6.86 8.62,7.5 7.85,8.39L5.44,7.35L4.69,8.65L6.8,10.2C6.4,11.37 6.4,12.64 6.8,13.8L4.68,15.36L5.43,16.66L7.86,15.62C8.63,16.5 9.68,17.14 10.87,17.38L11.24,20H12.76L13.13,17.39C14.32,17.14 15.37,16.5 16.14,15.62L18.57,16.66L19.32,15.36L17.2,13.81C17.6,12.64 17.6,11.37 17.2,10.2L19.31,8.65L18.56,7.35L16.15,8.39C15.38,7.5 14.32,6.86 13.12,6.62L12.75,4H11.25Z\" />\n        </svg>\n      </a>\n    </div>\n\n    <div class=\"box\">\n      <p>ESP current time, sync with NTP server and sent to this client via <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/\">WebSocket!</a></p>\n        <h4 id='esp-time'>Waiting websocket connection...</h4>\n    </div>\n\n    <div class=\"box\">\n      $<input type=\"text\" value=\"\" id=\"input_el\">\n    </div>\n    \n    <div class=box>\n        <main id=log></main>\n    </div>\n  </body>\n</html>\n)EOF\";"
  },
  {
    "path": "pio_examples/withWebSocket/src/withWebSocket.ino",
    "content": "#include <FS.h>\n#include <LittleFS.h>\n#include \"FSWebServer.h\"\n\n#include \"index_htm.h\"\n\n#define FILESYSTEM LittleFS\nconst char* hostname = \"fsbrowser\";\n\n// If you edit server port, remember to change also websocket port in index_htm.h\n// By default websocket port with F,WebServer library is server port + 1\nFSWebServer server(FILESYSTEM, 80, hostname);\n\n#ifndef LED_BUILTIN\n#define LED_BUILTIN 2\n#endif\n#define BOOT_BUTTON 0\n\n// Log messages both on Serial and WebSocket clients\nvoid wsLogPrintf(bool toSerial, const char* format, ...) {\n  char buffer[128];\n  va_list args;\n  va_start(args, format);\n  vsnprintf(buffer, 128, format, args);\n  va_end(args);\n  server.broadcastWebSocket(buffer);\n  if (toSerial)\n    Serial.println(buffer);\n}\n\n/////////////////////////   WebSocket event callback////////////////////////////////   WebSocket Handler  /////////////////////////////\nvoid webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {\n  switch (type) {\n    case WStype_DISCONNECTED:\n      Serial.printf(\"[%u] Disconnected!\\n\", num);\n      break;\n    case WStype_CONNECTED:{\n        IPAddress ip = server.getWebSocketServer()->remoteIP(num);\n        server.getWebSocketServer()->sendTXT(num, \"{\\\"Connected\\\": true}\");\n\n        // Print welcome message to all clients and to Serial\n        wsLogPrintf(true, \"Hello to client #%d [%s]\\n\", (int)num, ip.toString().c_str());\n      }\n      break;\n    case WStype_TEXT:\n      Serial.printf(\"[%u] got Text: %s\\n\", num, payload);   // Got text message from a client\n      break;\n    case WStype_BIN:\n      Serial.printf(\"[%u] got binary length: %u\\n\", num, length); // Got binary message from a client\n      break;\n    default:\n      break;\n  }\n}\n\n// Test \"config\" values\nString optionString = \"Test option String\";\nuint32_t optionULong = 1234567890;\nuint8_t ledPin = LED_BUILTIN;\n\n// Timezone definition to get properly time from NTP server\n#define MYTZ \"CET-1CEST,M3.5.0,M10.5.0/3\"\nstruct tm Time;\n\n\n////////////////////////////////  NTP Time  /////////////////////////////////////\nvoid getUpdatedtime(const uint32_t timeout) {\n  uint32_t start = millis();\n  Serial.print(\"Sync time...\");\n  while (millis() - start < timeout && Time.tm_year <= (1970 - 1900)) {\n    time_t now = time(nullptr);\n    Time = *localtime(&now);\n    delay(5);\n  }\n  Serial.println(\" done.\");\n}\n\n\n////////////////////////////////  Filesystem  /////////////////////////////////////////\nbool startFilesystem() {\n  if (FILESYSTEM.begin()) {\n    server.printFileList(FILESYSTEM, \"/\", 1, Serial);\n    return true;\n  } else {\n    Serial.println(\"ERROR on mounting filesystem. It will be reformatted!\");\n    FILESYSTEM.format();\n    ESP.restart();\n  }\n  return false;\n}\n\n\n////////////////////  Load and save application configuration from filesystem  ////////////////////\nbool loadApplicationConfig() {\n  if (FILESYSTEM.exists(server.getConfiFileName())) {\n    server.getOptionValue(\"Option 1\", optionString);\n    server.getOptionValue(\"Option 2\", optionULong);\n    server.getOptionValue(\"LED Pin\", ledPin);\n    server.closeSetupConfiguration();  // Close configuration to free resources\n    return true;\n  }\n  return false;\n}\n\n\nvoid setup() {\n  pinMode(LED_BUILTIN, OUTPUT);\n  pinMode(BOOT_BUTTON, INPUT_PULLUP);\n\n  Serial.begin(115200);\n  delay(1000);\n\n  // FILESYSTEM INIT\n  if (startFilesystem()) {\n    // Load configuration (if not present, default will be created when webserver will start)\n    if (loadApplicationConfig()) {\n      Serial.println(F(\"\\nApplication option loaded\"));\n      Serial.printf(\"  LED Pin: %d\\n\", ledPin);\n      Serial.printf(\"  Option 1: %s\\n\", optionString.c_str());   \n      Serial.printf(\"  Option 2: %u\\nn\", optionULong);\n    }\n    else\n      Serial.println(F(\"Application options NOT loaded!\"));\n  }\n\n  // Try to connect to WiFi (will start AP if not connected after timeout)\n  if (!server.startWiFi(10000)) {\n    Serial.println(\"\\nWiFi not connected! Starting AP mode...\");\n    server.startCaptivePortal(\"ESP_AP\", \"123456789\", \"/setup\");\n  }\n\n  // Configure /setup page\n  server.addOptionBox(\"My Options\");\n  server.addOption(\"LED Pin\", ledPin);\n  server.addOption(\"Option 1\", optionString.c_str());\n  server.addOption(\"Option 2\", optionULong);\n\n  // Add custom page handlers\n  server.on(\"/\", HTTP_GET, [](){\n    server.send_P(200, \"text/html\", homepage);\n  });\n\n  // Enable ACE FS file web editor and add FS info callback function\n  server.enableFsCodeEditor();\n  /*\n  * Getting FS info (total and free bytes) is strictly related to\n  * filesystem library used (LittleFS, FFat, SPIFFS etc etc)\n  * (On ESP8266 will be used \"built-in\" fsInfo data type)\n  */\n#ifdef ESP32\n  server.setFsInfoCallback([](fsInfo_t* fsInfo) {\n    fsInfo->fsName = \"LittleFS\";\n    fsInfo->totalBytes = LittleFS.totalBytes();\n    fsInfo->usedBytes = LittleFS.usedBytes();  \n  });\n#endif\n\n  // Init with WebSocket event handler and start server\n  server.begin(webSocketEvent);\n\n  Serial.print(F(\"ESP Web Server started on IP Address: \"));\n  Serial.println(server.getServerIP());\n  Serial.println(F(\n    \"This is \\\"withWebSocket.ino\\\" example.\\n\"\n    \"Open /setup page to configure optional parameters.\\n\"\n    \"Open /edit page to view, edit or upload example or your custom webserver source files.\"\n  ));\n\n  // Set hostname\n  WiFi.setHostname(hostname);\n  configTzTime(MYTZ, \"time.google.com\", \"time.windows.com\", \"pool.ntp.org\");\n}\n\n\nvoid loop() {\n  server.run();  // Handle client requests\n\n  if (digitalRead(BOOT_BUTTON) == LOW) {\n    wsLogPrintf(true, \"Button on GPIO %d clicked\", BOOT_BUTTON);\n    delay(1000);\n  }\n\n  // Send ESP system time (epoch) to WS client\n  static uint32_t sendToClientTime;\n  if (millis() - sendToClientTime > 1000) {\n    sendToClientTime = millis();\n    time_t now = time(nullptr);\n    wsLogPrintf(false, \"{\\\"esptime\\\": %d}\", (int)now);\n  }\n\n  delay(10);\n}"
  },
  {
    "path": "platformio.ini",
    "content": "\n[platformio]\ndefault_envs = ci-arduino-3-latest, ci-esp8266\nlib_dir = .\n\n[env]\nframework = arduino\nupload_protocol = esptool\nmonitor_speed = 115200\nlib_compat_mode = strict\nlib_ldf_mode = chain+\nlib_ignore = examples\nbuild_flags =\n  -Og\n  -Wall -Wextra\n  -Wno-unused-parameter\n\n; -----------------------------------------------------------------------------\n; CI (ESP32) - used by: .github/workflows/Build (ESP32 dev).yml\n; -----------------------------------------------------------------------------\n[env:ci-arduino-3-latest]\nplatform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.37/platform-espressif32.zip\n; NOTE: this env is meant to be driven by CI (matrix). You must set PIO_BOARD.\n; Examples:\n;   Linux/macOS (bash):  PIO_BOARD=esp32dev pio run -e ci-arduino-3-latest\n;   Windows (PowerShell): $env:PIO_BOARD='esp32dev'; pio run -e ci-arduino-3-latest\nboard = ${sysenv.PIO_BOARD}\nmonitor_filters = esp32_exception_decoder, log2file\nbuild_flags =\n  ${env.build_flags}\n  -I$PROJECT_PACKAGES_DIR/framework-arduinoespressif32/libraries/Network/src\n  -I$PROJECT_PACKAGES_DIR/framework-arduinoespressif32/libraries/FS/src\n  -I$PROJECT_PACKAGES_DIR/framework-arduinoespressif32/libraries/WiFi/src\n  -D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE\n  -D CONFIG_ASYNC_TCP_MAX_ACK_TIME=5000\n  -D CONFIG_ASYNC_TCP_PRIORITY=10\n  -D CONFIG_ASYNC_TCP_QUEUE_SIZE=64\n  -D CONFIG_ASYNC_TCP_RUNNING_CORE=1\n  -D CONFIG_ASYNC_TCP_STACK_SIZE=4096\n\nlib_deps =\n  bblanchon/ArduinoJson\n  ${platformio.packages_dir}/framework-arduinoespressif32/libraries/Network\n  https://github.com/cotestatnt/Arduino-MySQL.git\n  https://github.com/OSSLibraries/Arduino_MFRC522v2.git\n  https://github.com/knolleary/pubsubclient.git\nboard_build.partitions = partitions.csv\nboard_build.filesystem = littlefs\n\n; -----------------------------------------------------------------------------\n; CI (ESP8266) - used by: .github/workflows/build-esp8266.yml\n; -----------------------------------------------------------------------------\n[env:ci-esp8266]\nplatform = espressif8266\n; NOTE: this env is meant to be driven by CI. You must set PIO_BOARD.\n; Example (PowerShell): $env:PIO_BOARD='d1_mini'; pio run -e ci-esp8266\nboard = ${sysenv.PIO_BOARD}\nmonitor_filters = esp8266_exception_decoder, log2file\nbuild_flags = ${env.build_flags}\nlib_deps =\n  bblanchon/ArduinoJson\n\n"
  },
  {
    "path": "src/ConfigUpgrader.hpp",
    "content": "#ifndef CONFIG_UPGRADER_HPP\n#define CONFIG_UPGRADER_HPP\n\n#include <FS.h>\n#include \"SerialLog.h\"\n\nextern \"C\" {\n#include \"json/cJSON.h\"\n}\n\n/**\n * @brief ConfigUpgrader handles migration from v1 (flat JSON) to v2 (hierarchical JSON)\n * Uses cJSON directly for reliable key iteration\n */\nclass ConfigUpgrader\n{\npublic:\n    ConfigUpgrader(fs::FS* filesystem, const char* configFile)\n        : m_filesystem(filesystem), m_configFile(configFile) {}\n\n    ~ConfigUpgrader() {}\n\n    /**\n     * @brief Check if upgrade is needed and perform it\n     * @param outputFile Optional: save upgraded config to different file\n     * @return true if upgrade was performed or file is already v2, false on error\n     */\n    bool upgrade(const char* outputFile = nullptr) {\n        if (m_filesystem == nullptr || m_configFile == nullptr) {\n            log_error(\"ConfigUpgrader: Invalid filesystem or config file\");\n            return false;\n        }\n\n        if (!m_filesystem->exists(m_configFile)) {\n            log_debug(\"ConfigUpgrader: Config file does not exist, no upgrade needed\");\n            return true;\n        }\n\n        // Read config file\n        File file = m_filesystem->open(m_configFile, \"r\");\n        if (!file) {\n            log_error(\"ConfigUpgrader: Failed to open config file\");\n            return false;\n        }\n\n        String content = file.readString();\n        file.close();\n\n        // Parse JSON with cJSON\n        cJSON* oldJsonRoot = cJSON_Parse(content.c_str());\n        if (!oldJsonRoot) {\n            log_error(\"ConfigUpgrader: Failed to parse config JSON\");\n            return false;\n        }\n\n        // Check version\n        cJSON* versionItem = cJSON_GetObjectItem(oldJsonRoot, \"_version\");\n        if (versionItem && versionItem->valuestring && String(versionItem->valuestring).equals(\"2.0\")) {\n            log_debug(\"ConfigUpgrader: Config is already v2.0, no upgrade needed\");\n            cJSON_Delete(oldJsonRoot);\n            return true;\n        }\n\n        log_info(\"ConfigUpgrader: Upgrading config from v1 to v2.0\");\n        \n        // Perform upgrade\n        String upgraded = upgradeFromV1(oldJsonRoot);\n        cJSON_Delete(oldJsonRoot);\n\n        if (upgraded.isEmpty()) {\n            log_error(\"ConfigUpgrader: Upgrade failed\");\n            return false;\n        }\n\n        // Determine output file\n        const char* targetFile = (outputFile != nullptr) ? outputFile : m_configFile;\n\n        // Write upgraded config\n        file = m_filesystem->open(targetFile, \"w\");\n        if (!file) {\n            log_error(\"ConfigUpgrader: Failed to open config file for writing\");\n            return false;\n        }\n\n        file.print(upgraded);\n        file.close();\n        \n        log_info(\"ConfigUpgrader: Config upgraded and saved to %s\", targetFile);\n        return true;\n    }\n\n    bool migrateLegacySetupStorage(const char* legacyConfigFile, const char* legacyConfigFolder,\n                                   const char* targetConfigFolder, bool* migrated = nullptr) {\n        if (migrated != nullptr) {\n            *migrated = false;\n        }\n\n        if (m_filesystem == nullptr || m_configFile == nullptr || legacyConfigFile == nullptr ||\n            legacyConfigFolder == nullptr || targetConfigFolder == nullptr) {\n            log_error(\"ConfigUpgrader: Invalid migration arguments\");\n            return false;\n        }\n\n        if (!m_filesystem->exists(legacyConfigFile)) {\n            return true;\n        }\n\n        const String sourceDir = legacyConfigFolder;\n        const String targetDir = targetConfigFolder;\n\n        if (!moveDirectoryContents(sourceDir, targetDir)) {\n            log_error(\"ConfigUpgrader: Legacy setup migration failed while moving %s to %s\", legacyConfigFolder, targetConfigFolder);\n            return false;\n        }\n\n        m_filesystem->rmdir(legacyConfigFolder);\n\n        if (!rewriteSetupPathsInConfigFile(m_configFile, sourceDir, targetDir)) {\n            log_error(\"ConfigUpgrader: Legacy setup migration failed while rewriting asset paths in %s\", m_configFile);\n            return false;\n        }\n\n        log_error(\"Legacy setup storage detected in %s. Migrated to %s; restarting ESP to apply the new paths.\", legacyConfigFolder, targetConfigFolder);\n        if (migrated != nullptr) {\n            *migrated = true;\n        }\n        return true;\n    }\n\nprivate:\n    fs::FS* m_filesystem = nullptr;\n    const char* m_configFile = nullptr;\n\n    String joinPath(const String& base, const String& name) {\n        if (base.endsWith(\"/\")) {\n            return base + name;\n        }\n        return base + \"/\" + name;\n    }\n\n    bool ensureDirectory(const String& path) {\n        return m_filesystem->exists(path.c_str()) || m_filesystem->mkdir(path.c_str());\n    }\n\n    bool copyFile(const String& source, const String& target) {\n        File input = m_filesystem->open(source.c_str(), \"r\");\n        if (!input) {\n            return false;\n        }\n\n        File output = m_filesystem->open(target.c_str(), \"w\");\n        if (!output) {\n            input.close();\n            return false;\n        }\n\n        uint8_t buffer[128];\n        while (input.available()) {\n            size_t read = input.read(buffer, sizeof(buffer));\n            if (read == 0 || output.write(buffer, read) != read) {\n                input.close();\n                output.close();\n                return false;\n            }\n            yield();\n        }\n\n        input.close();\n        output.close();\n        return true;\n    }\n\n    bool moveFile(const String& source, const String& target) {\n        m_filesystem->remove(target.c_str());\n        if (m_filesystem->rename(source.c_str(), target.c_str())) {\n            return true;\n        }\n        if (!copyFile(source, target)) {\n            return false;\n        }\n        return m_filesystem->remove(source.c_str());\n    }\n\n    bool moveDirectoryContents(const String& sourceDir, const String& targetDir) {\n        if (!ensureDirectory(targetDir)) {\n            return false;\n        }\n\n        File dir = m_filesystem->open(sourceDir.c_str(), \"r\");\n        if (!dir || !dir.isDirectory()) {\n            return false;\n        }\n\n        dir.rewindDirectory();\n        while (true) {\n            File entry = dir.openNextFile();\n            if (!entry) {\n                break;\n            }\n\n            const String name = entry.name();\n            const String sourcePath = joinPath(sourceDir, name);\n            const String targetPath = joinPath(targetDir, name);\n\n            if (entry.isDirectory()) {\n                entry.close();\n                if (!moveDirectoryContents(sourcePath, targetPath)) {\n                    dir.close();\n                    return false;\n                }\n                m_filesystem->rmdir(sourcePath.c_str());\n            } else {\n                entry.close();\n                if (!moveFile(sourcePath, targetPath)) {\n                    dir.close();\n                    return false;\n                }\n            }\n            yield();\n        }\n\n        dir.close();\n        return true;\n    }\n\n    String remapLegacySetupPath(const String& value, const String& sourceDir, const String& targetDir) {\n        if (value == sourceDir) {\n            return targetDir;\n        }\n\n        const String sourcePrefix = sourceDir + \"/\";\n        if (value.startsWith(sourcePrefix)) {\n            return targetDir + value.substring(sourceDir.length());\n        }\n\n        return value;\n    }\n\n    void rewriteLegacySetupPaths(cJSON* node, const String& sourceDir, const String& targetDir, bool& changed) {\n        for (cJSON* current = node; current; current = current->next) {\n            if (cJSON_IsString(current) && current->valuestring) {\n                const String rewritten = remapLegacySetupPath(String(current->valuestring), sourceDir, targetDir);\n                if (rewritten != String(current->valuestring)) {\n                    cJSON_SetValuestring(current, rewritten.c_str());\n                    changed = true;\n                }\n            }\n\n            if (current->child) {\n                rewriteLegacySetupPaths(current->child, sourceDir, targetDir, changed);\n            }\n        }\n    }\n\n    bool rewriteSetupPathsInConfigFile(const char* configPath, const String& sourceDir, const String& targetDir) {\n        File file = m_filesystem->open(configPath, \"r\");\n        if (!file) {\n            return false;\n        }\n\n        const String jsonText = file.readString();\n        file.close();\n\n        cJSON* root = cJSON_Parse(jsonText.c_str());\n        if (!root) {\n            return false;\n        }\n\n        bool changed = false;\n        rewriteLegacySetupPaths(root, sourceDir, targetDir, changed);\n        if (!changed) {\n            cJSON_Delete(root);\n            return true;\n        }\n\n        char* raw = cJSON_PrintUnformatted(root);\n        cJSON_Delete(root);\n        if (!raw) {\n            return false;\n        }\n\n        const String serialized(raw);\n        free(raw);\n\n        file = m_filesystem->open(configPath, \"w\");\n        if (!file) {\n            return false;\n        }\n\n        const size_t written = file.print(serialized);\n        file.close();\n        return written == serialized.length();\n    }\n\n    /**\n     * @brief Generate valid ID from label\n     */\n    String generateId(const String& label) {\n        String id = label;\n        id.toLowerCase();\n        id.replace(\" \", \"-\");\n        \n        String cleaned;\n        for (unsigned int i = 0; i < id.length(); i++) {\n            char c = id[i];\n            if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_') {\n                cleaned += c;\n            }\n        }\n        \n        return cleaned.isEmpty() ? String(\"option-\") : cleaned;\n    }\n\n    /**\n     * @brief Escape special characters for JSON\n     */\n    String escapeJson(const String& input) {\n        String result;\n        for (unsigned int i = 0; i < input.length(); i++) {\n            char c = input[i];\n            switch (c) {\n                case '\\\"': result += \"\\\\\\\"\"; break;\n                case '\\\\': result += \"\\\\\\\\\"; break;\n                case '\\b': result += \"\\\\b\"; break;\n                case '\\f': result += \"\\\\f\"; break;\n                case '\\n': result += \"\\\\n\"; break;\n                case '\\r': result += \"\\\\r\"; break;\n                case '\\t': result += \"\\\\t\"; break;\n                default:\n                    if (c < 32) {\n                        // Skip control characters\n                    } else {\n                        result += c;\n                    }\n            }\n        }\n        return result;\n    }\n\n    /**\n     * @brief Upgrade JSON from v1 flat format to v2 hierarchical format\n     */\n    String upgradeFromV1(cJSON* oldRoot) {\n        String result = \"{\\n\";\n        \n        // Add version\n        result += \"  \\\"_version\\\": \\\"2.0\\\",\\n\";\n\n        // Extract metadata\n        result += \"  \\\"_meta\\\": {\\n\";\n        \n        String pageTitle = \"Configuration\";\n        String logoPath = \"\";\n        double port = 80;\n        String host = \"myserver\";\n\n        cJSON* item = nullptr;\n        if ((item = cJSON_GetObjectItem(oldRoot, \"page-title\")) && item->valuestring) {\n            pageTitle = item->valuestring;\n        }\n        if ((item = cJSON_GetObjectItem(oldRoot, \"img-logo\")) && item->valuestring) {\n            logoPath = item->valuestring;\n        }\n        if ((item = cJSON_GetObjectItem(oldRoot, \"port\")) && item->type == cJSON_Number) {\n            port = item->valuedouble;\n        }\n        if ((item = cJSON_GetObjectItem(oldRoot, \"host\")) && item->valuestring) {\n            host = item->valuestring;\n        }\n\n        result += \"    \\\"app_title\\\": \\\"\" + escapeJson(pageTitle) + \"\\\",\\n\";\n        if (!logoPath.isEmpty()) {\n            result += \"    \\\"logo\\\": \\\"\" + escapeJson(logoPath) + \"\\\",\\n\";\n        }\n        result += \"    \\\"port\\\": \" + String((long)port) + \",\\n\";\n        result += \"    \\\"host\\\": \\\"\" + escapeJson(host) + \"\\\"\\n\";\n        result += \"  },\\n\";\n\n        // Add state (empty)\n        result += \"  \\\"_state\\\": {},\\n\";\n\n        // Extract assets (CSS and JS only - HTML is handled as elements)\n        std::vector<String> cssList;\n        std::vector<String> jsList;\n        \n        for (cJSON* assetItem = oldRoot->child; assetItem; assetItem = assetItem->next) {\n            String key = assetItem->string ? String(assetItem->string) : String(\"\");\n            \n            if (key.indexOf(\"raw-css-\") == 0 && assetItem->valuestring) {\n                cssList.push_back(assetItem->valuestring);\n            } else if ((key.indexOf(\"raw-javascript-\") == 0 || key.indexOf(\"raw-js-\") == 0) && assetItem->valuestring) {\n                jsList.push_back(assetItem->valuestring);\n            }\n        }\n\n        // Add assets section (CSS and JS only)\n        result += \"  \\\"_assets\\\": {\\n\";\n        result += \"    \\\"css\\\": [\";\n        for (size_t i = 0; i < cssList.size(); i++) {\n            result += \"\\\"\" + escapeJson(cssList[i]) + \"\\\"\";\n            if (i < cssList.size() - 1) result += \", \";\n        }\n        result += \"],\\n\";\n        \n        result += \"    \\\"js\\\": [\";\n        for (size_t i = 0; i < jsList.size(); i++) {\n            result += \"\\\"\" + escapeJson(jsList[i]) + \"\\\"\";\n            if (i < jsList.size() - 1) result += \", \";\n        }\n        result += \"]\\n\";\n        result += \"  },\\n\";\n\n        // Add sections\n        result += \"  \\\"sections\\\": [\\n\";\n        result += upgradeToSections(oldRoot);\n        result += \"  ]\\n\";\n        result += \"}\\n\";\n\n        return result;\n    }\n\n    /**\n     * @brief Convert v1 elements to v2 sections\n     */\n    String upgradeToSections(cJSON* oldRoot) {\n        String result;\n        String currentSectionId = \"general-options\";\n        String currentSectionTitle = \"Options\";\n        std::vector<String> currentElements;\n        bool firstSection = true;\n\n        // Iterate through all top-level keys\n        for (cJSON* item = oldRoot->child; item; item = item->next) {\n            String key = item->string ? String(item->string) : String(\"\");\n            if (key.isEmpty()) continue;\n\n            // Skip system keys\n            if (key.equals(\"_version\") || key.equals(\"_meta\") || \n                key.equals(\"_state\") || key.equals(\"_assets\") ||\n                key.equals(\"page-title\") || key.equals(\"img-logo\") ||\n                key.equals(\"port\") || key.equals(\"host\")) {\n                continue;\n            }\n\n            // Skip raw-css and raw-javascript (they all go to _assets)\n            if ((key.indexOf(\"raw-css-\") == 0 || key.indexOf(\"raw-javascript-\") == 0 || \n                 key.indexOf(\"raw-js-\") == 0) && \n                key.indexOf(\"raw-html-\") != 0) {\n                continue;\n            }\n\n            // Handle section titles\n            if (key.indexOf(\"param-box\") == 0) {\n                // Save current section if has elements\n                if (currentElements.size() > 0) {\n                    if (!firstSection) result += \",\\n\";\n                    result += buildSection(currentSectionId, currentSectionTitle, currentElements);\n                    firstSection = false;\n                }\n\n                // Start new section\n                String sectionTitle = item->valuestring ? String(item->valuestring) : String(\"Section\");\n                currentSectionId = generateId(sectionTitle);\n                currentSectionTitle = sectionTitle;\n                currentElements.clear();\n                continue;\n            }\n\n            // Skip image and name keys\n            if (key.indexOf(\"img-\") == 0 || key.indexOf(\"name-\") == 0) {\n                continue;\n            }\n\n            // Convert option\n            String elemJson = convertV1Option(key, item);\n            if (!elemJson.isEmpty()) {\n                currentElements.push_back(elemJson);\n            }\n        }\n\n        // Save last section\n        if (currentElements.size() > 0) {\n            if (!firstSection) result += \",\\n\";\n            result += buildSection(currentSectionId, currentSectionTitle, currentElements);\n            result += \"\\n\";\n        }\n\n        return result;\n    }\n\n    /**\n     * @brief Build a section JSON block\n     */\n    String buildSection(const String& id, const String& title, const std::vector<String>& elements) {\n        String result = \"    {\\n\";\n        result += \"      \\\"title\\\": \\\"\" + escapeJson(title) + \"\\\",\\n\";\n        result += \"      \\\"elements\\\": [\\n\";\n        for (size_t i = 0; i < elements.size(); i++) {\n            result += elements[i];\n            if (i < elements.size() - 1) result += \",\";\n            result += \"\\n\";\n        }\n        result += \"      ]\\n\";\n        result += \"    }\";\n        return result;\n    }\n\n    /**\n     * @brief Convert a single v1 option to v2 element\n     */\n    String convertV1Option(const String& key, cJSON* item) {\n        String result = \"        {\\n\";\n        result += \"          \\\"label\\\": \\\"\" + escapeJson(key) + \"\\\",\\n\";\n\n        // Check if it's an object with metadata\n        if (item->type == cJSON_Object) {\n            cJSON* typeItem = cJSON_GetObjectItem(item, \"type\");\n            String typeStr = (typeItem && typeItem->valuestring) ? String(typeItem->valuestring) : String(\"\");\n\n            if (typeStr.equals(\"slider\")) {\n                result += \"          \\\"type\\\": \\\"slider\\\",\\n\";\n                \n                double value = 0, min = 0, max = 100, step = 1;\n                cJSON* v = cJSON_GetObjectItem(item, \"value\");\n                if (v && v->type == cJSON_Number) value = v->valuedouble;\n                v = cJSON_GetObjectItem(item, \"min\");\n                if (v && v->type == cJSON_Number) min = v->valuedouble;\n                v = cJSON_GetObjectItem(item, \"max\");\n                if (v && v->type == cJSON_Number) max = v->valuedouble;\n                v = cJSON_GetObjectItem(item, \"step\");\n                if (v && v->type == cJSON_Number) step = v->valuedouble;\n                \n                result += \"          \\\"value\\\": \" + String(value) + \",\\n\";\n                result += \"          \\\"min\\\": \" + String(min) + \",\\n\";\n                result += \"          \\\"max\\\": \" + String(max) + \",\\n\";\n                result += \"          \\\"step\\\": \" + String(step) + \"\\n\";\n            } \n            else if (typeStr.equals(\"number\")) {\n                result += \"          \\\"type\\\": \\\"number\\\",\\n\";\n                \n                double value = 0, min = -3.4e38, max = 3.4e38, step = 1;\n                cJSON* v = cJSON_GetObjectItem(item, \"value\");\n                if (v && v->type == cJSON_Number) value = v->valuedouble;\n                v = cJSON_GetObjectItem(item, \"min\");\n                if (v && v->type == cJSON_Number) min = v->valuedouble;\n                v = cJSON_GetObjectItem(item, \"max\");\n                if (v && v->type == cJSON_Number) max = v->valuedouble;\n                v = cJSON_GetObjectItem(item, \"step\");\n                if (v && v->type == cJSON_Number) step = v->valuedouble;\n                \n                result += \"          \\\"value\\\": \" + String(value) + \",\\n\";\n                result += \"          \\\"min\\\": \" + String(min) + \",\\n\";\n                result += \"          \\\"max\\\": \" + String(max) + \",\\n\";\n                result += \"          \\\"step\\\": \" + String(step) + \"\\n\";\n            }\n            else if (cJSON_GetObjectItem(item, \"selected\") != nullptr) {\n                // Dropdown\n                result += \"          \\\"type\\\": \\\"select\\\",\\n\";\n                \n                cJSON* selected = cJSON_GetObjectItem(item, \"selected\");\n                if (selected && selected->valuestring) {\n                    result += \"          \\\"value\\\": \\\"\" + escapeJson(selected->valuestring) + \"\\\",\\n\";\n                }\n                \n                // Extract options array\n                result += \"          \\\"options\\\": [\";\n                cJSON* valuesArray = cJSON_GetObjectItem(item, \"values\");\n                if (valuesArray && valuesArray->type == cJSON_Array) {\n                    bool firstOption = true;\n                    for (cJSON* optItem = valuesArray->child; optItem; optItem = optItem->next) {\n                        if (optItem->valuestring) {\n                            if (!firstOption) result += \", \";\n                            result += \"\\\"\" + escapeJson(optItem->valuestring) + \"\\\"\";\n                            firstOption = false;\n                        }\n                    }\n                }\n                result += \"]\\n\";\n            }\n            else {\n                result += \"          \\\"type\\\": \\\"text\\\",\\n\";\n                cJSON* val = cJSON_GetObjectItem(item, \"value\");\n                if (val && val->valuestring) {\n                    result += \"          \\\"value\\\": \\\"\" + escapeJson(val->valuestring) + \"\\\"\\n\";\n                }\n            }\n        } \n        else {\n            // Primitive value - infer type\n            if (item->type == cJSON_True || item->type == cJSON_False) {\n                result += \"          \\\"type\\\": \\\"boolean\\\",\\n\";\n                result += \"          \\\"value\\\": \" + String(item->type == cJSON_True ? \"true\" : \"false\") + \"\\n\";\n            } \n            else if (item->type == cJSON_Number) {\n                result += \"          \\\"type\\\": \\\"number\\\",\\n\";\n                result += \"          \\\"value\\\": \" + String(item->valuedouble) + \"\\n\";\n            } \n            else if (item->type == cJSON_String && item->valuestring) {\n                result += \"          \\\"type\\\": \\\"text\\\",\\n\";\n                result += \"          \\\"value\\\": \\\"\" + escapeJson(item->valuestring) + \"\\\"\\n\";\n            }\n            else {\n                result += \"          \\\"type\\\": \\\"text\\\",\\n\";\n                result += \"          \\\"value\\\": \\\"\\\"\\n\";\n            }\n        }\n\n        result += \"        }\";\n        return result;\n    }\n};\n\n#endif // CONFIG_UPGRADER_HPP\n"
  },
  {
    "path": "src/CredentialManager.cpp",
    "content": "#include \"CredentialManager.h\"\n#include \"SerialLog.h\"\n\n\nCredentialManager::CredentialManager() : m_efuse_initialized(false) {\n  memset(m_encryption_key, 0, ENCRYPTION_KEY_SIZE);\n\n  // Default global options (hostname only)\n  memset(m_hostname, 0, sizeof(m_hostname));\n\n#if defined(ESP8266)\n  m_filesystem = nullptr;\n#endif\n}\n\nCredentialManager::~CredentialManager() {\n  // Clean key from memory\n  memset(m_encryption_key, 0, ENCRYPTION_KEY_SIZE);\n  clearAll();\n}\n\nbool CredentialManager::begin() {\n  initializeEncryptionKey();\n  return true;\n}\n\nvoid CredentialManager::initializeEncryptionKey() {\n#if defined(ESP32)\n  esp_err_t err = esp_efuse_read_block(EFUSE_BLK_KEY0, m_encryption_key, 0,\n                                       ENCRYPTION_KEY_SIZE * 8);\n\n  if (err == ESP_OK) {\n    // Check if truly programmed (not all 0xFF)\n    bool all_ff = true;\n    for (int i = 0; i < ENCRYPTION_KEY_SIZE; i++) {\n      if (m_encryption_key[i] != 0xFF) {\n        all_ff = false;\n        break;\n      }\n    }\n\n    if (!all_ff) {\n      m_efuse_initialized = true;\n      log_info(\"BLOCK_KEY0 initialized from eFuse - SECURE mode\");\n    } else {\n      log_info(\"BLOCK_KEY0 not programmed - using fallback (NOT SECURE)\");\n      m_efuse_initialized = false;\n      // Use fallback key (derived from MAC address or constant)\n      // This obfuscates but is not real security\n      memset(m_encryption_key, 0xAA, ENCRYPTION_KEY_SIZE);\n    }\n  } else {\n    log_error(\"Failed to read BLOCK_KEY0: %s\", esp_err_to_name(err));\n    // Fallback to predefined key\n    memset(m_encryption_key, 0xAA, ENCRYPTION_KEY_SIZE);\n  }\n#else\n  // For ESP8266 or other chips, use fallback\n  memset(m_encryption_key, 0xAA, ENCRYPTION_KEY_SIZE);\n#endif\n}\n\nString CredentialManager::getStatus() const {\n  if (m_efuse_initialized) {\n    return \"SECURE (BLOCK_KEY0 programmed)\";\n  } else {\n    return \"INSECURE (BLOCK_KEY0 not programmed - fallback key used)\";\n  }\n}\n\nvoid CredentialManager::setHostname(const char* hostname) {\n  if (!hostname) {\n    memset(m_hostname, 0, sizeof(m_hostname));\n    return;\n  }\n  strlcpy(m_hostname, hostname, sizeof(m_hostname));\n}\n\nString CredentialManager::getHostname() const {\n  return String(m_hostname);\n}\n\n// Web server port is fixed at construction time in AsyncFsWebServer\n// and is not stored as a shared, mutable option in CredentialManager.\n\nbool CredentialManager::addCredential(const WiFiCredential& credential, const char* plaintext_password) {\n  if (strlen(credential.ssid) == 0) {\n    log_error(\"Invalid SSID\");\n    return false;\n  }\n  if (!plaintext_password || strlen(plaintext_password) == 0) {\n    log_error(\"Invalid password\");\n    return false;\n  }\n\n  if (m_credentials.size() >= MAX_CREDENTIALS) {\n    log_error(\"Maximum credentials reached (%d)\", MAX_CREDENTIALS);\n    return false;\n  }\n\n  if (strlen(plaintext_password) > 63) {\n    log_error(\"Password too long (max 63 characters)\");\n    return false;\n  }\n\n  // Create credential copy with encrypted password\n  WiFiCredential cred = credential;\n\n  // Encrypt password\n  uint16_t encrypted_len = 0;\n  if (!encryptPassword(plaintext_password, cred.pwd_encrypted, encrypted_len)) {\n    log_error(\"Failed to encrypt password\");\n    return false;\n  }\n\n  cred.pwd_len = encrypted_len;\n\n  m_credentials.push_back(cred);\n  log_debug(\"Credential added: %s.\", cred.ssid);\n\n  return true;\n}\n\nbool CredentialManager::removeCredential(uint8_t index) {\n  if (index >= m_credentials.size()) {\n    log_error(\"Invalid credential index %d\", index);\n    return false;\n  }\n\n  log_debug(\"Removing credential: %s\", m_credentials[index].ssid);\n  memset(m_credentials[index].pwd_encrypted, 0, sizeof(m_credentials[index].pwd_encrypted));\n  memset(m_credentials[index].ssid, 0, sizeof(m_credentials[index].ssid));\n  m_credentials.erase(m_credentials.begin() + index);\n  return true;\n}\n\nbool CredentialManager::removeCredential(const char* ssid) {\n  if (!ssid) {\n    return false;\n  }\n\n  for (size_t i = 0; i < m_credentials.size(); i++) {\n    if (strcmp(m_credentials[i].ssid, ssid) == 0) {\n      return removeCredential(static_cast<uint8_t>(i));\n    }\n  }\n\n  log_error(\"Credential not found: %s\", ssid);\n  return false;\n}\n\nbool CredentialManager::updateCredential(const WiFiCredential& credential, const char* plaintext_password) {\n  uint8_t index = 255;\n  for (size_t i = 0; i < m_credentials.size(); i++) {\n    if (strcmp(m_credentials[i].ssid, credential.ssid) == 0) {\n      index = i;\n      break;\n    }\n  }\n  \n  if (index >= m_credentials.size()) {\n    log_error(\"Credential not found: %s\", credential.ssid);\n    return false;\n  }\n\n  if (!plaintext_password || strlen(plaintext_password) == 0) {\n    log_error(\"Invalid password\");\n    return false;\n  }\n\n  if (strlen(plaintext_password) > 63) {\n    log_error(\"Password too long (max 63 characters)\");\n    return false;\n  }\n\n  WiFiCredential &cred = m_credentials[index];\n\n  // Encrypt new password\n  uint16_t encrypted_len = 0;\n  if (!encryptPassword(plaintext_password, cred.pwd_encrypted, encrypted_len)) {\n    log_error(\"Failed to encrypt password\");\n    return false;\n  }\n\n  cred.pwd_len = encrypted_len;\n\n  // Update IP configuration from credential parameter\n  cred.gateway = credential.gateway;\n  cred.subnet = credential.subnet;\n  cred.local_ip = credential.local_ip;\n  cred.dns1 = credential.dns1;\n  cred.dns2 = credential.dns2;\n  \n  log_debug(\"Credential updated: %s.\", credential.ssid);\n  return true;\n}\n\nconst char *CredentialManager::getSSID(uint8_t index) const {\n  if (index >= m_credentials.size()) {\n    return nullptr;\n  }\n  return m_credentials[index].ssid;\n}\n\nString CredentialManager::getPassword(uint8_t index) {\n  if (index >= m_credentials.size()) {\n    return \"\";\n  }\n\n  const WiFiCredential &cred = m_credentials[index];\n  char plaintext[65] = {0};\n\n  if (decryptPassword(cred.pwd_encrypted, cred.pwd_len, plaintext,\n                      64)) {\n    String result = plaintext;\n    // Clean password from memory\n    memset(plaintext, 0, 65);\n    return result;\n  }\n\n  return \"\";\n}\n\nbool CredentialManager::setIPConfiguration(uint8_t index, IPAddress ip, IPAddress gateway, IPAddress subnet) {\n  if (index >= m_credentials.size()) {\n    log_error(\"Invalid credential index %d\", index);\n    return false;\n  }\n\n  m_credentials[index].local_ip = ip;\n  m_credentials[index].gateway = gateway;\n  m_credentials[index].subnet = subnet;\n\n  log_debug(\"IP configuration updated for credential %d: IP=%s, GW=%s, SN=%s\", \n            index, ip.toString().c_str(), gateway.toString().c_str(), subnet.toString().c_str());\n  return true;\n}\n\nbool CredentialManager::getIPConfiguration(uint8_t index, IPAddress& ip, IPAddress& gateway, IPAddress& subnet) const {\n  if (index >= m_credentials.size()) {\n    log_error(\"Invalid credential index %d\", index);\n    return false;\n  }\n\n  ip = m_credentials[index].local_ip;\n  gateway = m_credentials[index].gateway;\n  subnet = m_credentials[index].subnet;\n\n  return true;\n}\n\nvoid CredentialManager::clearAll() {\n  // Clean each credential\n  for (auto &cred : m_credentials) {\n    memset(cred.pwd_encrypted, 0, sizeof(cred.pwd_encrypted));\n    memset(cred.ssid, 0, sizeof(cred.ssid));\n  }\n  m_credentials.clear();\n  #if defined(ESP8266)\n    saveToFS();\n  #elif defined(ESP32)\n    saveToNVS();\n  #endif\n  log_debug(\"All credentials cleared\");\n}\n\nbool CredentialManager::encryptPassword(const char *plaintext, uint8_t *ciphertext, uint16_t &cipher_len) {\n  if (!plaintext || !ciphertext) {\n    return false;\n  }\n\n  size_t plaintext_len = strlen(plaintext);\n  if (plaintext_len > 63) {\n    log_error(\"Password too long\");\n    return false;\n  }\n\n  // Apply PKCS7 padding\n  uint8_t *padded = new uint8_t[64];\n  uint16_t padded_len = 0;\n  applyPKCS7Padding((uint8_t *)plaintext, plaintext_len, padded, padded_len);\n\n  // AES-256-CBC encryption\n  mbedtls_aes_context aes_ctx;\n  mbedtls_aes_init(&aes_ctx);\n\n  int ret = mbedtls_aes_setkey_enc(&aes_ctx, m_encryption_key, 256);\n  if (ret != 0) {\n    log_error(\"AES setkey failed: %d\", ret);\n    mbedtls_aes_free(&aes_ctx);\n    delete[] padded;\n    return false;\n  }\n\n  // Fixed IV (deterministic, not randomized)\n  // In case of randomized IV, save it in the first block\n  uint8_t iv[AES_BLOCK_SIZE] = {0};\n\n  ret = mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_ENCRYPT, padded_len, iv,\n                              padded, ciphertext);\n\n  mbedtls_aes_free(&aes_ctx);\n  delete[] padded;\n\n  if (ret != 0) {\n    log_error(\"AES encryption failed: %d\", ret);\n    return false;\n  }\n\n  cipher_len = padded_len;\n  return true;\n}\n\nbool CredentialManager::decryptPassword(const uint8_t *ciphertext, uint16_t cipher_len, char *plaintext, uint16_t max_len) {\n  if (!ciphertext || !plaintext || cipher_len == 0 ||\n      cipher_len % AES_BLOCK_SIZE != 0) {\n    log_error(\"Invalid decryption parameters\");\n    return false;\n  }\n\n  if (cipher_len > max_len) {\n    log_error(\"Buffer too small for decryption\");\n    return false;\n  }\n\n  // AES-256-CBC decryption\n  mbedtls_aes_context aes_ctx;\n  mbedtls_aes_init(&aes_ctx);\n\n  int ret = mbedtls_aes_setkey_dec(&aes_ctx, m_encryption_key, 256);\n  if (ret != 0) {\n    log_error(\"AES setkey failed: %d\", ret);\n    mbedtls_aes_free(&aes_ctx);\n    return false;\n  }\n\n  uint8_t iv[AES_BLOCK_SIZE] = {0};\n  uint8_t *decrypted = new uint8_t[cipher_len];\n\n  ret = mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_DECRYPT, cipher_len, iv,\n                              ciphertext, decrypted);\n\n  mbedtls_aes_free(&aes_ctx);\n\n  if (ret != 0) {\n    log_error(\"AES decryption failed: %d\", ret);\n    delete[] decrypted;\n    return false;\n  }\n\n  // Remove PKCS7 padding\n  uint16_t plaintext_len = removePKCS7Padding(decrypted, cipher_len);\n  if (plaintext_len == 0) {\n    log_error(\"Invalid padding in decrypted data\");\n    delete[] decrypted;\n    return false;\n  }\n\n  if (plaintext_len >= max_len) {\n    log_error(\"Plaintext too long\");\n    delete[] decrypted;\n    return false;\n  }\n\n  memcpy(plaintext, decrypted, plaintext_len);\n  plaintext[plaintext_len] = '\\0';\n\n  // Clean sensitive data from memory\n  memset(decrypted, 0, cipher_len);\n  delete[] decrypted;\n\n  return true;\n}\n\nvoid CredentialManager::applyPKCS7Padding(const uint8_t *data, uint16_t data_len, uint8_t *padded, uint16_t &padded_len) {\n  // Calculate length with padding (multiple of 16)\n  uint16_t total_len = ((data_len / AES_BLOCK_SIZE) + 1) * AES_BLOCK_SIZE;\n  uint8_t pad_value = total_len - data_len;\n\n  memcpy(padded, data, data_len);\n  for (int i = data_len; i < total_len; i++) {\n    padded[i] = pad_value;\n  }\n\n  padded_len = total_len;\n}\n\nuint16_t CredentialManager::removePKCS7Padding(uint8_t *data, uint16_t data_len) {\n  if (data_len < AES_BLOCK_SIZE || data_len % AES_BLOCK_SIZE != 0) {\n    return 0;\n  }\n\n  uint8_t pad_value = data[data_len - 1];\n\n  // Validate padding\n  if (pad_value > AES_BLOCK_SIZE || pad_value == 0) {\n    return 0;\n  }\n\n  // Verify that all padding bytes have the correct value\n  for (int i = data_len - pad_value; i < data_len; i++) {\n    if (data[i] != pad_value) {\n      return 0;\n    }\n  }\n\n  return data_len - pad_value;\n}\n\n#if defined(ESP8266)\n// Attach a filesystem instance used for persisting credentials on ESP8266.\n// This must be called before saveToFS()/loadFromFS().\nvoid CredentialManager::setFilesystem(fs::FS* fs) {\n  m_filesystem = fs;\n}\n\n// Save credentials to FS in a simple binary format:\n// [uint8_t count][char hostname[33]] then for each credential:\n// [char ssid[33]][uint16_t pwd_len][uint8_t pwd_encrypted[64]]\n// [uint32_t gateway][uint32_t subnet][uint32_t local_ip]\n// [uint32_t dns1][uint32_t dns2]\nbool CredentialManager::saveToFS(const char* filepath) {\n  if (!m_filesystem) {\n    log_error(\"Filesystem not set\");\n    return false;\n  }\n\n  File file = m_filesystem->open(filepath, \"w\");\n  if (!file) {\n    log_error(\"Failed to open %s for write\", filepath);\n    return false;\n  }\n\n  uint8_t count = m_credentials.size();\n  if (count > MAX_CREDENTIALS) {\n    count = MAX_CREDENTIALS;\n  }\n\n  // New simple format (breaking change is acceptable):\n  // [count][hostname][credentials...]\n  file.write(&count, 1);\n  file.write((const uint8_t*)m_hostname, sizeof(m_hostname));\n\n  for (uint8_t i = 0; i < count; i++) {\n    const WiFiCredential &cred = m_credentials[i];\n\n    file.write((const uint8_t*)cred.ssid, sizeof(cred.ssid));\n    file.write((const uint8_t*)&cred.pwd_len, sizeof(cred.pwd_len));\n    file.write((const uint8_t*)cred.pwd_encrypted, sizeof(cred.pwd_encrypted));\n\n    uint32_t gw_addr = cred.gateway;\n    uint32_t sn_addr = cred.subnet;\n    uint32_t ip_addr = cred.local_ip;\n    uint32_t dns1_addr_cred = cred.dns1;\n    uint32_t dns2_addr_cred = cred.dns2;\n    file.write((const uint8_t*)&gw_addr, sizeof(gw_addr));\n    file.write((const uint8_t*)&sn_addr, sizeof(sn_addr));\n    file.write((const uint8_t*)&ip_addr, sizeof(ip_addr));\n    file.write((const uint8_t*)&dns1_addr_cred, sizeof(dns1_addr_cred));\n    file.write((const uint8_t*)&dns2_addr_cred, sizeof(dns2_addr_cred));\n  }\n\n  file.close();\n  log_info(\"Credentials saved to FS (%d entries)\", count);\n  return true;\n}\n\n// Load credentials from FS written by saveToFS().\n// Uses the same binary layout as described above.\nbool CredentialManager::loadFromFS(const char* filepath) {\n  if (!m_filesystem) {\n    log_error(\"Filesystem not set\");\n    return false;\n  }\n\n  if (!m_filesystem->exists(filepath)) {\n    log_info(\"No credentials file found: %s\", filepath);\n    return false;\n  }\n\n  File file = m_filesystem->open(filepath, \"r\");\n  if (!file) {\n    log_error(\"Failed to open %s for read\", filepath);\n    return false;\n  }\n\n  m_credentials.clear();\n\n  uint8_t count = 0;\n  if (file.read(&count, 1) != 1 || count == 0) {\n    file.close();\n    log_debug(\"No credentials in FS\");\n    return false;\n  }\n\n  if (count > MAX_CREDENTIALS) {\n    count = MAX_CREDENTIALS;\n  }\n\n  // Simple new format: hostname directly after count\n  if (file.read((uint8_t*)m_hostname, sizeof(m_hostname)) != sizeof(m_hostname)) {\n    memset(m_hostname, 0, sizeof(m_hostname));\n  }\n\n  for (uint8_t i = 0; i < count; i++) {\n    WiFiCredential cred{};\n\n    if (file.read((uint8_t*)cred.ssid, sizeof(cred.ssid)) != sizeof(cred.ssid)) break;\n    if (file.read((uint8_t*)&cred.pwd_len, sizeof(cred.pwd_len)) != sizeof(cred.pwd_len)) break;\n    if (cred.pwd_len == 0 || cred.pwd_len > sizeof(cred.pwd_encrypted)) break;\n    if (file.read((uint8_t*)cred.pwd_encrypted, sizeof(cred.pwd_encrypted)) != sizeof(cred.pwd_encrypted)) break;\n\n    uint32_t gw_addr = 0;\n    uint32_t sn_addr = 0;\n    uint32_t ip_addr = 0;\n    if (file.read((uint8_t*)&gw_addr, sizeof(gw_addr)) != sizeof(gw_addr)) break;\n    if (file.read((uint8_t*)&sn_addr, sizeof(sn_addr)) != sizeof(sn_addr)) break;\n    if (file.read((uint8_t*)&ip_addr, sizeof(ip_addr)) != sizeof(ip_addr)) break;\n\n    cred.gateway = IPAddress(gw_addr);\n    cred.subnet = IPAddress(sn_addr);\n    cred.local_ip = IPAddress(ip_addr);\n\n    // Optional per-credential DNS (backward compatible)\n    uint32_t dns1_addr_cred = 0;\n    uint32_t dns2_addr_cred = 0;\n    if ((unsigned int)file.available() >= sizeof(dns1_addr_cred) + sizeof(dns2_addr_cred)) {\n      if (file.read((uint8_t*)&dns1_addr_cred, sizeof(dns1_addr_cred)) == sizeof(dns1_addr_cred) &&\n          file.read((uint8_t*)&dns2_addr_cred, sizeof(dns2_addr_cred)) == sizeof(dns2_addr_cred)) {\n        cred.dns1 = IPAddress(dns1_addr_cred);\n        cred.dns2 = IPAddress(dns2_addr_cred);\n      }\n    } else {\n      cred.dns1 = IPAddress(0, 0, 0, 0);\n      cred.dns2 = IPAddress(0, 0, 0, 0);\n    }\n\n    m_credentials.push_back(cred);\n  }\n\n  file.close();\n\n  if (m_credentials.size() > 0) {\n    log_info(\"Loaded %d credentials from FS\", m_credentials.size());\n    return true;\n  }\n\n  return false;\n}\n#endif\n\n\n#if defined(ESP32)\n// Save credentials to ESP32 NVS in a key/value layout.\n// Global:\n//   host  -> hostname (null-terminated string)\n//   count -> number of stored credentials (uint8_t)\n// Per credential i (0..count-1):\n//   ssid{i}   -> SSID string\n//   pass{i}   -> encrypted password blob (length len{i})\n//   len{i}    -> encrypted password length (uint16_t)\n//   gw{i}     -> gateway IPv4 as uint32_t\n//   sn{i}     -> subnet mask IPv4 as uint32_t\n//   ip{i}     -> local IP IPv4 as uint32_t\n//   dns1_{i}  -> primary DNS IPv4 as uint32_t\n//   dns2_{i}  -> secondary DNS IPv4 as uint32_t\nbool CredentialManager::saveToNVS(const char *nvs_namespace) {\n\n  nvs_handle_t nvs_handle;\n  esp_err_t err = nvs_open(nvs_namespace, NVS_READWRITE, &nvs_handle);\n\n  if (err != ESP_OK) {\n    log_error(\"Failed to open NVS: %s\", esp_err_to_name(err));\n    return false;\n  }\n\n  // Save global hostname (no DNS/port globals anymore)\n  err = nvs_set_str(nvs_handle, \"host\", m_hostname);\n  if (err != ESP_OK) {\n    log_error(\"Failed to save hostname\");\n    nvs_close(nvs_handle);\n    return false;\n  }\n\n  // Save number of credentials\n  err = nvs_set_u8(nvs_handle, \"count\", m_credentials.size());\n  if (err != ESP_OK) {\n    log_error(\"Failed to save credentials count\");\n    nvs_close(nvs_handle);\n    return false;\n  }\n\n  // Save each credential\n  for (size_t i = 0; i < m_credentials.size(); i++) {\n    const WiFiCredential &cred = m_credentials[i];\n\n    // SSID key\n    String key_ssid = \"ssid\" + String(i);\n    err = nvs_set_str(nvs_handle, key_ssid.c_str(), cred.ssid);\n    if (err != ESP_OK) {\n      log_error(\"Failed to save SSID %d\", i);\n      nvs_close(nvs_handle);\n      return false;\n    }\n\n    // Encrypted password key (as blob)\n    String key_pass = \"pass\" + String(i);\n    err = nvs_set_blob(nvs_handle, key_pass.c_str(), cred.pwd_encrypted, cred.pwd_len);\n    if (err != ESP_OK) {\n      log_error(\"Failed to save password %d\", i);\n      nvs_close(nvs_handle);\n      return false;\n    }\n\n    // Password length\n    String key_len = \"len\" + String(i);\n    err = nvs_set_u16(nvs_handle, key_len.c_str(), cred.pwd_len);\n    if (err != ESP_OK) {\n      log_error(\"Failed to save password length %d\", i);\n      nvs_close(nvs_handle);\n      return false;\n    }\n\n    // Static IP Configuration - Gateway\n    String key_gw = \"gw\" + String(i);\n    uint32_t gw_addr = cred.gateway;\n    err = nvs_set_u32(nvs_handle, key_gw.c_str(), gw_addr);\n    if (err != ESP_OK) {\n      log_error(\"Failed to save gateway %d\", i);\n      nvs_close(nvs_handle);\n      return false;\n    }\n\n    // Static IP Configuration - Subnet\n    String key_sn = \"sn\" + String(i);\n    uint32_t sn_addr = cred.subnet;\n    err = nvs_set_u32(nvs_handle, key_sn.c_str(), sn_addr);\n    if (err != ESP_OK) {\n      log_error(\"Failed to save subnet %d\", i);\n      nvs_close(nvs_handle);\n      return false;\n    }\n\n    // Static IP Configuration - Local IP\n    String key_ip = \"ip\" + String(i);\n    uint32_t ip_addr = cred.local_ip;\n    err = nvs_set_u32(nvs_handle, key_ip.c_str(), ip_addr);\n    if (err != ESP_OK) {\n      log_error(\"Failed to save local IP %d\", i);\n      nvs_close(nvs_handle);\n      return false;\n    }\n\n    // Static DNS configuration (per credential, optional)\n    String key_dns1 = \"dns1_\" + String(i);\n    uint32_t dns1_addr_cred = cred.dns1;\n    err = nvs_set_u32(nvs_handle, key_dns1.c_str(), dns1_addr_cred);\n    if (err != ESP_OK) {\n      log_error(\"Failed to save DNS1 %d\", i);\n      nvs_close(nvs_handle);\n      return false;\n    }\n\n    String key_dns2 = \"dns2_\" + String(i);\n    uint32_t dns2_addr_cred = cred.dns2;\n    err = nvs_set_u32(nvs_handle, key_dns2.c_str(), dns2_addr_cred);\n    if (err != ESP_OK) {\n      log_error(\"Failed to save DNS2 %d\", i);\n      nvs_close(nvs_handle);\n      return false;\n    }\n  }\n\n  err = nvs_commit(nvs_handle);\n  nvs_close(nvs_handle);\n\n  if (err == ESP_OK) {\n    log_info(\"Credentials saved to NVS (%d entries)\", m_credentials.size());\n    return true;\n  } else {\n    log_error(\"Failed to commit NVS\");\n    return false;\n  }\n}\n\n// Load credentials from ESP32 NVS using the layout documented\n// in saveToNVS(). If any entry is missing, sensible defaults\n// (e.g. 0.0.0.0 for IP/DNS) are applied.\nbool CredentialManager::loadFromNVS(const char *nvs_namespace) {\n  m_credentials.clear();\n  nvs_handle_t nvs_handle;\n  esp_err_t err = nvs_open(nvs_namespace, NVS_READONLY, &nvs_handle);\n\n  if (err != ESP_OK) {\n    log_info(\"No NVS data found for namespace: %s\", nvs_namespace);\n    return false;\n  }\n\n  size_t host_len = sizeof(m_hostname);\n  if (nvs_get_str(nvs_handle, \"host\", m_hostname, &host_len) != ESP_OK) {\n    memset(m_hostname, 0, sizeof(m_hostname));\n  }\n\n  // Read number of credentials\n  uint8_t count = 0;\n  err = nvs_get_u8(nvs_handle, \"count\", &count);\n  if (err != ESP_OK || count == 0) {\n    log_debug(\"No credentials in NVS\");\n    nvs_close(nvs_handle);\n    return false;\n  }\n\n  if (count > MAX_CREDENTIALS) {\n    count = MAX_CREDENTIALS;\n  }\n\n  // Read each credential\n  for (int i = 0; i < count; i++) {\n    WiFiCredential cred;\n    memset(&cred, 0, sizeof(cred));\n\n    // Read SSID\n    String key_ssid = \"ssid\" + String(i);\n    size_t ssid_len = 33;\n    err = nvs_get_str(nvs_handle, key_ssid.c_str(), cred.ssid, &ssid_len);\n    if (err != ESP_OK) {\n      log_error(\"Failed to load SSID %d\", i);\n      continue;\n    }\n\n    // Read password length\n    String key_len = \"len\" + String(i);\n    uint16_t pass_len = 0;\n    err = nvs_get_u16(nvs_handle, key_len.c_str(), &pass_len);\n    if (err != ESP_OK || pass_len == 0 || pass_len > 64) {\n      log_error(\"Invalid password length for credential %d\", i);\n      continue;\n    }\n\n    // Read encrypted password\n    String key_pass = \"pass\" + String(i);\n    size_t blob_len = pass_len;\n    err = nvs_get_blob(nvs_handle, key_pass.c_str(), cred.pwd_encrypted, &blob_len);\n    if (err != ESP_OK) {\n      log_error(\"Failed to load password %d\", i);\n      continue;\n    }\n\n    cred.pwd_len = pass_len;\n\n    // Read Static IP Configuration - Gateway\n    String key_gw = \"gw\" + String(i);\n    uint32_t gw_addr = 0;\n    err = nvs_get_u32(nvs_handle, key_gw.c_str(), &gw_addr);\n    if (err == ESP_OK) {\n      cred.gateway = IPAddress(gw_addr);\n    } else {\n      cred.gateway = IPAddress(0, 0, 0, 0);\n    }\n\n    // Read Static IP Configuration - Subnet\n    String key_sn = \"sn\" + String(i);\n    uint32_t sn_addr = 0;\n    err = nvs_get_u32(nvs_handle, key_sn.c_str(), &sn_addr);\n    if (err == ESP_OK) {\n      cred.subnet = IPAddress(sn_addr);\n    } else {\n      cred.subnet = IPAddress(0, 0, 0, 0);\n    }\n\n    // Read Static IP Configuration - Local IP\n    String key_ip = \"ip\" + String(i);\n    uint32_t ip_addr = 0;\n    err = nvs_get_u32(nvs_handle, key_ip.c_str(), &ip_addr);\n    if (err == ESP_OK) {\n      cred.local_ip = IPAddress(ip_addr);\n    } else {\n      cred.local_ip = IPAddress(0, 0, 0, 0);\n    }\n\n    // Read Static DNS configuration (per credential, optional)\n    String key_dns1 = \"dns1_\" + String(i);\n    uint32_t dns1_addr_cred = 0;\n    if (nvs_get_u32(nvs_handle, key_dns1.c_str(), &dns1_addr_cred) == ESP_OK) {\n      cred.dns1 = IPAddress(dns1_addr_cred);\n    } else {\n      cred.dns1 = IPAddress(0, 0, 0, 0);\n    }\n\n    String key_dns2 = \"dns2_\" + String(i);\n    uint32_t dns2_addr_cred = 0;\n    if (nvs_get_u32(nvs_handle, key_dns2.c_str(), &dns2_addr_cred) == ESP_OK) {\n      cred.dns2 = IPAddress(dns2_addr_cred);\n    } else {\n      cred.dns2 = IPAddress(0, 0, 0, 0);\n    }\n\n    m_credentials.push_back(cred);\n  }\n\n  nvs_close(nvs_handle);\n\n  if (m_credentials.size() > 0) {\n    log_info(\"Loaded %d credentials from NVS\", m_credentials.size());\n    return true;\n  }\n\n  return false;\n}\n#endif\n\nString CredentialManager::getDebugInfo() const {\n  String info = \"\\n=== Credential Manager Debug Info ===\\n\";\n\n  info += \"Encryption Status: \" + getStatus() + \"\\n\";\n  info += \"Credentials Count: \" + String(m_credentials.size()) + \"/\" +\n          String(MAX_CREDENTIALS) + \"\\n\";\n\n#if defined(ESP32)\n  info += \"BLOCK_KEY0 Status: \";\n\n  // Read BLOCK_KEY0 status\n  uint8_t efuse_key[32];\n  esp_err_t err = esp_efuse_read_block(EFUSE_BLK_KEY0, efuse_key, 0, 256);\n\n  if (err == ESP_OK) {\n    bool all_ff = true;\n    bool all_00 = true;\n    for (int i = 0; i < 32; i++) {\n      if (efuse_key[i] != 0xFF)\n        all_ff = false;\n      if (efuse_key[i] != 0x00)\n        all_00 = false;\n    }\n\n    if (all_ff) {\n      info += \"NOT PROGRAMMED (all 0xFF)\\n\";\n    } else if (all_00) {\n      info += \"INVALID (all 0x00)\\n\";\n    } else {\n      info += \"PROGRAMMED (bytes present)\\n\";\n    }\n  } else {\n    info += \"ERROR: \" + String(esp_err_to_name(err)) + \"\\n\";\n  }\n#else\n  info += \"BLOCK_KEY0 Status: N/A (not ESP32)\\n\";\n#endif\n\n  info += \"\\nCredentials:\\n\";\n  for (size_t i = 0; i < m_credentials.size(); i++) {\n    info += \"  [\" + String(i) + \"] SSID: \" + String(m_credentials[i].ssid);\n    \n    // Show IP configuration if not all zeros\n    if (m_credentials[i].local_ip != IPAddress(0, 0, 0, 0)) {\n      info += \" | IP: \" + m_credentials[i].local_ip.toString();\n      info += \" | GW: \" + m_credentials[i].gateway.toString();\n      info += \" | SN: \" + m_credentials[i].subnet.toString();\n    } else {\n      info += \" | IP: DHCP\";\n    }\n    \n    info += \" | Encrypted len: \" + String(m_credentials[i].pwd_len) + \"\\n\";\n  }\n\n  info += \"=====================================\\n\";\n  return info;\n}\n"
  },
  {
    "path": "src/CredentialManager.h",
    "content": "#ifndef CREDENTIAL_MANAGER_H\n#define CREDENTIAL_MANAGER_H\n\n#include <Arduino.h>\n#include <IPAddress.h>\n#include <vector>\n#include <cstring>\n\n#if defined(ESP32)\n    #include <mbedtls/aes.h>\n    #include \"esp_efuse.h\"\n    #include \"nvs_flash.h\"\n#elif defined(ESP8266)\n    // Use local compatibility shim for AES on ESP8266 (BearSSL-backed)\n    #include \"compat/mbedtls_aes.h\"\n    #include \"LittleFS.h\"\n#endif\n\n/**\n * @brief Default encryption key (32 bytes for AES-256)\n * \n * IMPORTANT: Modify this with your own 32-byte key!\n * ESP32: This is used as fallback if BLOCK_KEY0 is not programmed\n * ESP8266: This is always used (no eFuse available)\n * \n * Example generation:\n * $ python3 -c \"import os; key = os.urandom(32); print('0x' + ',0x'.join(f'{b:02x}' for b in key))\"\n */\n#ifndef CREDENTIAL_MANAGER_ENCRYPTION_KEY\n#define CREDENTIAL_MANAGER_ENCRYPTION_KEY \\\n    0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, \\\n    0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99\n#endif\n\n/**\n * @class CredentialManager\n * @brief Manages WiFi credentials with transparent AES-256 encryption\n * \n * Features:\n * - Automatic encryption with AES-256-CBC\n * - ESP32: Support for BLOCK_KEY0 (eFuse) with secure fallback\n * - ESP8266: User-defined encryption key (no eFuse available)\n * - Persistent storage in NVS (ESP32) or SPIFFS (ESP8266)\n * - Multiple SSID/password management\n * - Transparent: works seamlessly across platforms\n * - Passwords always encrypted in memory and storage\n */\n\nstruct WiFiCredential {\n    char ssid[33];                  // Max 32 char SSID + null\n    uint8_t pwd_encrypted[64];      // Encrypted password\n    uint16_t pwd_len;               // Length of encrypted data\n    IPAddress gateway;              // Optional static IP config\n    IPAddress subnet;               // Optional static IP config    \n    IPAddress local_ip;             // Optional static IP config\n    IPAddress dns1;                 // Optional primary DNS (per SSID)\n    IPAddress dns2;                 // Optional secondary DNS (per SSID)    \n};\n\nclass CredentialManager {\npublic:\n    friend class FSWebServer;\n    static const uint8_t MAX_CREDENTIALS = 5;\n    static const uint8_t ENCRYPTION_KEY_SIZE = 32; // 256-bit AES\n    static const uint16_t AES_BLOCK_SIZE = 16;\n\n    /**\n     * @brief Constructor - initializes the manager\n     */\n    CredentialManager();\n\n    /**\n     * @brief Destructor - frees resources\n     */\n    ~CredentialManager();\n\n    /**\n     * @brief Initialize encryption key from eFuse (ESP32) or predefined constant (ESP8266)\n     * @return true if successful, false on error\n     */\n    bool begin();\n\n    /**\n     * @brief Return encryption security status\n     * @return true if encryption is properly configured\n     */\n    bool isSecure() const { return m_efuse_initialized; }\n\n    /**\n     * @brief Return status description\n     * @return String with security status and platform info\n     */\n    String getStatus() const;\n\n    /**\n     * @brief Add a WiFi credential (automatically encrypted)\n     * @param credential WiFiCredential struct containing SSID and optional IP config\n     * @param plaintext_password Plaintext password (will be encrypted internally)\n     * @return true if successfully added\n     */\n    bool addCredential(const WiFiCredential& credential, const char* plaintext_password);\n\n    /**\n     * @brief Update a WiFi credential (automatically encrypted)\n     * @param credential WiFiCredential struct with IP config and SSID to identify\n     * @param plaintext_password Plaintext password (will be encrypted internally)\n     * @return true if successfully updated\n     */\n    bool updateCredential(const WiFiCredential& credential, const char* plaintext_password);\n\n    /**\n     * @brief Get number of stored credentials\n     * @return Credential count\n     */\n    uint8_t getCredentialCount() const { return m_credentials.size(); }\n\n    /**\n     * @brief Get SSID of a credential\n     * @param index Credential index\n     * @return SSID (nullptr if index invalid)\n     */\n    const char* getSSID(uint8_t index) const;\n\n    /**\n     * @brief Get decrypted password of a credential\n     * @param index Credential index\n     * @return Password in plaintext (automatic removal from memory after use)\n     */\n    String getPassword(uint8_t index);\n\n    String getPassword(const char* ssid) {\n        for (size_t i = 0; i < m_credentials.size(); i++) {\n            if (strcmp(m_credentials[i].ssid, ssid) == 0) {\n                return getPassword(i);\n            }\n        }\n        return \"\";\n    }\n\n\n    /**\n     * @brief Set static IP configuration for a credential\n     * @param index Credential index\n     * @param ip Local IP address (0.0.0.0 for DHCP)\n     * @param gateway Gateway IP address\n     * @param subnet Subnet mask\n     * @return true if successfully set\n     */\n    bool setIPConfiguration(uint8_t index, IPAddress ip, IPAddress gateway, IPAddress subnet);\n\n    /**\n     * @brief Get static IP configuration for a credential\n     * @param index Credential index\n     * @param ip Output: Local IP address\n     * @param gateway Output: Gateway IP address\n     * @param subnet Output: Subnet mask\n     * @return true if valid configuration exists\n     */\n    bool getIPConfiguration(uint8_t index, IPAddress& ip, IPAddress& gateway, IPAddress& subnet) const;\n\n    /**\n     * @brief Clear all credentials from memory\n     */\n    void clearAll();\n\n    /**\n     * @brief Remove a single credential by index\n     * @param index Credential index\n     * @return true if removed\n     */\n    bool removeCredential(uint8_t index);\n\n    /**\n     * @brief Remove a single credential by SSID\n     * @param ssid Network SSID\n     * @return true if removed\n     */\n    bool removeCredential(const char* ssid);\n\n    #if defined(ESP8266)\n    /**\n     * @brief Set filesystem reference for persistence (ESP8266/ESP32)\n     * @param fs Pointer to FS object (LittleFS or SPIFFS)\n     * @note Required for saveToFS() and loadFromFS() to work on ESP8266\n     */\n    void setFilesystem(fs::FS* fs);\n\n    \n    /**\n     * @brief Save credentials to filesystem (ESP8266/SPIFFS)\n     * @param filepath Path to save file\n     * @return true if successful\n     */\n    bool saveToFS(const char* filepath = \"/credentials.bin\");\n\n    /**\n     * @brief Load credentials from filesystem (ESP8266/SPIFFS)\n     * @param filepath Path to load file from\n     * @return true if at least one credential loaded\n     */\n    bool loadFromFS(const char* filepath = \"/credentials.bin\");\n    #endif\n\n    #if defined(ESP32)\n    /**\n     * @brief Save credentials to NVS (ESP32 only)\n     * @param nvs_namespace NVS namespace to use\n     * @return true if successful\n     */\n    bool saveToNVS(const char* nvs_namespace = \"wifi_creds\");\n\n    /**\n     * @brief Load credentials from NVS (ESP32 only)\n     * @param nvs_namespace NVS namespace to use\n     * @return true if at least one credential loaded\n     */\n    bool loadFromNVS(const char* nvs_namespace = \"wifi_creds\");\n    #endif\n\n    /**\n     * @brief Get BLOCK_KEY0 status details\n     * @return Technical status details\n     */\n    String getDebugInfo() const;\n\n    std::vector<WiFiCredential>* getCredentials() {\n        return &m_credentials;\n    }\n\n    bool checkSSIDExists(const char* ssid) const {\n        for (const auto& cred : m_credentials) {\n            if (strcmp(cred.ssid, ssid) == 0) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * @brief Set HTTP webserver hostname (shared across libraries)\n     */\n    void setHostname(const char* hostname);\n\n    /**\n     * @brief Get HTTP webserver hostname (shared across libraries)\n     */\n    String getHostname() const;\n\n    /**\n     * @brief Set HTTP webserver port (shared across libraries)\n     */\n    // Port is fixed at construction time in AsyncFsWebServer, so it is not\n    // exposed as a configurable shared option here.\n\nprotected:\n    std::vector<WiFiCredential> m_credentials;\n    uint8_t m_encryption_key[ENCRYPTION_KEY_SIZE];\n    bool m_efuse_initialized;\n\n    // Global options shared across libraries (hostname only)\n    char m_hostname[33];            // HTTP server hostname (max 32 chars + null)\n\n    #if defined(ESP8266)\n    fs::FS* m_filesystem;\n    #endif\n\n    /**\n     * @brief Load key from eFuse (ESP32) or predefined constant (ESP8266)\n     */\n    void initializeEncryptionKey();\n\n    /**\n     * @brief Encrypt password with AES-256-CBC\n     * @param plaintext Password in plaintext\n     * @param ciphertext Output encrypted buffer\n     * @param cipher_len Output: length of encrypted data\n     * @return true if successful\n     */\n    bool encryptPassword(const char* plaintext, uint8_t* ciphertext, uint16_t& cipher_len);\n\n    /**\n     * @brief Decrypt password with AES-256-CBC\n     * @param ciphertext Encrypted data\n     * @param cipher_len Length of encrypted data\n     * @param plaintext Output password buffer\n     * @param max_len Maximum buffer size\n     * @return true if successful\n     */\n    bool decryptPassword(const uint8_t* ciphertext, uint16_t cipher_len,\n                        char* plaintext, uint16_t max_len);\n\n    /**\n     * @brief Apply PKCS7 padding\n     * @param data Input buffer\n     * @param data_len Length of original data\n     * @param padded Output buffer with padding\n     * @param padded_len Output: total length with padding\n     */\n    void applyPKCS7Padding(const uint8_t* data, uint16_t data_len,\n                           uint8_t* padded, uint16_t& padded_len);\n\n    /**\n     * @brief Remove PKCS7 padding\n     * @param data Input buffer\n     * @param data_len Buffer length\n     * @return Length of data without padding (0 if error)\n     */\n    uint16_t removePKCS7Padding(uint8_t* data, uint16_t data_len);\n};\n\n#endif // CREDENTIAL_MANAGER_H\n"
  },
  {
    "path": "src/FSWebServer.cpp",
    "content": "#include \"FSWebServer.h\"\n\n#if defined(ESP32)\n    #include \"mimetable/mimetable.h\"\n#endif\n\nnamespace {\nString serializeJsonDocument(cJSON *root) {\n    if (!root) {\n        return \"{}\";\n    }\n\n    char *raw = cJSON_PrintUnformatted(root);\n    String out = raw ? String(raw) : String(\"{}\");\n    if (raw) {\n        free(raw);\n    }\n    cJSON_Delete(root);\n    return out;\n}\n}\n\n\nvoid FSWebServer::begin(WebSocketsServer::WebSocketServerEvent wsEventHandler) {\n\n    // Set build date as default firmware version (YYMMDDHHmm) from Version.h constexprs\n    if (m_version.length() == 0)\n        m_version = String(BUILD_TIMESTAMP);\n\n//////////////////////    BUILT-IN HANDLERS    ///////////////////////////\n#if ESP_FS_WS_SETUP\n    ConfigUpgrader upgrader(m_filesystem, ESP_FS_WS_CONFIG_FILE);\n    bool migratedLegacySetupStorage = false;\n    upgrader.migrateLegacySetupStorage(\"/config/config.json\", \"/config\", ESP_FS_WS_CONFIG_FOLDER, &migratedLegacySetupStorage);\n    if (migratedLegacySetupStorage) {\n        ESP.restart();\n        return;\n    }\n\n    m_filesystem_ok = getSetupConfigurator()->checkConfigFile();\n    if (!m_filesystem_ok) {\n        log_error(\"Filesystem not available. Setup page will not work.\");\n    }\n\n    // Close config file if it was opened during setup (will be reopened on demand when accessing config options)\n    if (getSetupConfigurator()->isOpened()) {\n        log_debug(\"Config file %s closed\", ESP_FS_WS_CONFIG_FILE);\n        getSetupConfigurator()->closeConfiguration();\n    }   \n         \n    // Apply port override from config.json if present   \n    uint16_t port = 0;\n    if (getSetupConfigurator()->getOptionValue(\"port\", port)) {\n        log_debug(\"Port value %u read from config file\", port);\n        if (port != m_port && port != 0) {\n            log_debug(\"Overriding server port to %u from config file\", port);\n            m_port = port;\n            getSetupConfigurator()->closeConfiguration();\n        }\n    }     \n\n    // Register the setup websocket before serving /setup to avoid a first-load race.\n    initSetupWebSocket();\n\n    // Setup page handlers\n    on(\"*\", HTTP_HEAD, [this]() { this->handleFileName(); });\n    on(\"/\", HTTP_GET, [this]() { this->handleIndex(); });\n    on(\"/setup\", HTTP_GET, [this]() { this->handleSetup(); });\n    onNotFound([this]() { this->handleFileRequest(); });\n\n    // Serve default logo from PROGMEM when no custom logo exists on filesystem\n    on(ESP_FS_WS_CONFIG_FOLDER \"/logo.svg\", HTTP_GET, [this]() {\n        const String logoBase = String(ESP_FS_WS_CONFIG_FOLDER) + \"/logo\";\n        if (!m_filesystem->exists(logoBase + \".svg\") && \n            !m_filesystem->exists(logoBase + \".svg.gz\") &&\n            !m_filesystem->exists(logoBase + \".png\") &&\n            !m_filesystem->exists(logoBase + \".jpg\") &&\n            !m_filesystem->exists(logoBase + \".gif\")) {\n                this->sendHeader(PSTR(\"Content-Encoding\"), \"gzip\");\n                this->sendHeader(PSTR(\"Cache-Control\"), \"public, max-age=86400\");\n                this->send_P(200, \"image/svg+xml\", (const char*)_aclogo_svg, sizeof(_aclogo_svg));\n        } else {\n           this->handleFileRequest();\n        }\n    });\n\n    // WiFi info handler\n    on(\"/wifi\", HTTP_GET, [this]() {\n        CJSON::Json doc;\n        doc.setString(\"ssid\", WiFi.SSID());\n        doc.setNumber(\"rssi\", WiFi.RSSI());\n        this->send(200, \"application/json\", doc.serialize());\n    });\n#endif\n\n#if ESP_FS_WS_WEBSOCKET\n    if (wsEventHandler) {\n        m_websocket = new WebSocketsServer(m_port + 1);\n        m_websocket->begin();\n        m_websocket->onEvent(wsEventHandler);\n        log_debug(\"WebSocket server started on port %u\", m_port + 1);\n    }\n#endif\n\n#ifdef ESP32\n    this->enableCrossOrigin(true);    \n#endif\n    WebServerClass::begin(m_port);\n    log_debug(\"HTTP server started on port %u\", m_port);\n    \n#if ESP_FS_WS_SETUP\n    // Free SetupConfigurator memory after initialization (will be recreated lazily if needed)\n    freeSetupConfigurator();\n#endif\n}\n\n#if ESP_FS_WS_SETUP\nvoid FSWebServer::initSetupWebSocket() {\n    if (m_setupWebSocket) {\n        return;\n    }\n\n    m_setupWebSocket = new WebSocketsServer(m_port + 2);\n    m_setupWebSocket->begin();\n    m_setupWebSocket->onEvent([this](uint8_t clientId, WStype_t type, uint8_t *payload, size_t length) {\n        this->handleSetupWebSocket(clientId, type, payload, length);\n    });\n    log_debug(\"Setup WebSocket server started on port %u\", m_port + 2);\n}\n\nvoid FSWebServer::releaseSetupWebSocketIfIdle() {\n    m_releaseSetupWebSocketPending = false;\n    if (!m_setupWebSocket || m_setupWebSocket->connectedClients() > 0) {\n        return;\n    }\n\n    // Keep the setup websocket listener alive across page reloads.\n    // Tearing it down on every disconnect creates a race where the browser's\n    // next reload connects while the server is being closed and recreated.\n    log_debug(\"Setup WebSocket idle; listener kept active\");\n}\n\nvoid FSWebServer::handleSetupWebSocket(uint8_t clientId, WStype_t type, uint8_t *payload, size_t length) {\n    switch (type) {\n        case WStype_CONNECTED:\n            sendSetupWsEvent(clientId, \"setup.socket.ready\");\n            break;\n        case WStype_TEXT:\n            handleSetupWebSocketMessage(clientId, payload, length);\n            break;\n        case WStype_DISCONNECTED:\n            if (m_setupWebSocket && m_setupWebSocket->connectedClients() == 0) {\n                m_releaseSetupWebSocketPending = true;\n            }\n            break;\n        default:\n            break;\n    }\n}\n\nvoid FSWebServer::handleSetupWebSocketMessage(uint8_t clientId, const uint8_t *data, size_t len) {\n    if (!data || len == 0) {\n        return;\n    }\n\n    cJSON *root = cJSON_ParseWithLengthOpts(reinterpret_cast<const char *>(data), len, nullptr, 0);\n    if (!root) {\n        sendSetupWsResponse(clientId, String(), false, \"invalid\", String(), \"Invalid JSON\");\n        return;\n    }\n\n    cJSON *type = cJSON_GetObjectItemCaseSensitive(root, \"type\");\n    cJSON *name = cJSON_GetObjectItemCaseSensitive(root, \"name\");\n    cJSON *payload = cJSON_GetObjectItemCaseSensitive(root, \"payload\");\n    cJSON *reqId = cJSON_GetObjectItemCaseSensitive(root, \"reqId\");\n\n    String reqIdStr = cJSON_IsString(reqId) && reqId->valuestring ? String(reqId->valuestring) : String();\n    String nameStr = cJSON_IsString(name) && name->valuestring ? String(name->valuestring) : String();\n\n    if (!cJSON_IsString(type) || strcmp(type->valuestring, \"cmd\") != 0 || nameStr.length() == 0) {\n        cJSON_Delete(root);\n        sendSetupWsResponse(clientId, reqIdStr, false, \"invalid\", String(), \"Invalid command envelope\");\n        return;\n    }\n\n    if (nameStr == \"status.get\") {\n        cJSON_Delete(root);\n        sendSetupWsResponse(clientId, reqIdStr, true, nameStr.c_str(), buildSetupStatusPayload());\n        return;\n    }\n\n    if (nameStr == \"config.get\") {\n        cJSON_Delete(root);\n        sendSetupWsResponse(clientId, reqIdStr, true, nameStr.c_str(), buildSetupConfigPayload());\n        return;\n    }\n\n    if (nameStr == \"credentials.get\") {\n        cJSON_Delete(root);\n        sendSetupWsResponse(clientId, reqIdStr, true, nameStr.c_str(), buildSetupCredentialsPayload());\n        return;\n    }\n\n    if (nameStr == \"credentials.delete\") {\n        bool ok = false;\n        if (m_credentialManager && cJSON_IsObject(payload)) {\n            cJSON *index = cJSON_GetObjectItemCaseSensitive(payload, \"index\");\n            if (cJSON_IsNumber(index)) {\n                ok = m_credentialManager->removeCredential(static_cast<uint8_t>(index->valuedouble));\n            }\n        }\n        if (ok) {\n#if defined(ESP32)\n            m_credentialManager->saveToNVS();\n#elif defined(ESP8266)\n            m_credentialManager->saveToFS();\n#endif\n        }\n        cJSON_Delete(root);\n        sendSetupWsResponse(clientId, reqIdStr, ok, nameStr.c_str(), buildSetupCredentialsPayload(), ok ? String() : String(\"Invalid credential index\"));\n        return;\n    }\n\n    if (nameStr == \"credentials.clear\") {\n        bool ok = m_credentialManager != nullptr;\n        if (m_credentialManager) {\n            m_credentialManager->clearAll();\n#if defined(ESP32)\n            m_credentialManager->saveToNVS();\n#elif defined(ESP8266)\n            m_credentialManager->saveToFS();\n#endif\n        }\n        cJSON_Delete(root);\n        sendSetupWsResponse(clientId, reqIdStr, ok, nameStr.c_str(), buildSetupCredentialsPayload(), ok ? String() : String(\"Credential manager not available\"));\n        return;\n    }\n\n    if (nameStr == \"wifi.scan\") {\n        WiFiScanResult scan = WiFiService::scanNetworks();\n        cJSON *scanPayload = cJSON_CreateObject();\n        if (scan.reload) {\n            cJSON_AddBoolToObject(scanPayload, \"reload\", true);\n        } else {\n            cJSON *networks = cJSON_Parse(scan.json.c_str());\n            if (!networks) {\n                networks = cJSON_CreateArray();\n            }\n            cJSON_AddItemToObject(scanPayload, \"networks\", networks);\n        }\n        cJSON_Delete(root);\n        sendSetupWsResponse(clientId, reqIdStr, true, nameStr.c_str(), serializeJsonDocument(scanPayload));\n        return;\n    }\n\n    if (nameStr == \"config.save\") {\n        bool ok = false;\n        if (cJSON_IsObject(payload)) {\n            cJSON *configNode = cJSON_GetObjectItemCaseSensitive(payload, \"config\");\n            if (configNode) {\n                char *rawConfig = cJSON_Print(configNode);\n                if (rawConfig) {\n                    ok = saveSetupConfigJson(String(rawConfig));\n                    free(rawConfig);\n                }\n            }\n        }\n        cJSON_Delete(root);\n        sendSetupWsResponse(clientId, reqIdStr, ok, nameStr.c_str(), buildSetupConfigPayload(), ok ? String() : String(\"Failed to save config\"));\n        return;\n    }\n\n    if (nameStr == \"device.restart\") {\n        cJSON_Delete(root);\n        sendSetupWsResponse(clientId, reqIdStr, true, nameStr.c_str());\n        m_pendingSetupRestartAt = millis() + 200;\n        return;\n    }\n\n    if (nameStr == \"wifi.connect\") {\n        WiFiConnectParams params;\n        bool persistent = true;\n        bool confirmed = false;\n\n        if (cJSON_IsObject(payload)) {\n            cJSON *ssid = cJSON_GetObjectItemCaseSensitive(payload, \"ssid\");\n            cJSON *password = cJSON_GetObjectItemCaseSensitive(payload, \"password\");\n            cJSON *host = cJSON_GetObjectItemCaseSensitive(payload, \"hostname\");\n            cJSON *dhcp = cJSON_GetObjectItemCaseSensitive(payload, \"dhcp\");\n            cJSON *persistentNode = cJSON_GetObjectItemCaseSensitive(payload, \"persistent\");\n            cJSON *confirmedNode = cJSON_GetObjectItemCaseSensitive(payload, \"confirmed\");\n            cJSON *ip = cJSON_GetObjectItemCaseSensitive(payload, \"ip_address\");\n            cJSON *gateway = cJSON_GetObjectItemCaseSensitive(payload, \"gateway\");\n            cJSON *subnet = cJSON_GetObjectItemCaseSensitive(payload, \"subnet\");\n            cJSON *dns1 = cJSON_GetObjectItemCaseSensitive(payload, \"dns1\");\n            cJSON *dns2 = cJSON_GetObjectItemCaseSensitive(payload, \"dns2\");\n\n            if (cJSON_IsString(ssid) && ssid->valuestring) {\n                strlcpy(params.config.ssid, ssid->valuestring, sizeof(params.config.ssid));\n            }\n            if (cJSON_IsString(password) && password->valuestring) {\n                params.password = password->valuestring;\n            }\n            if (cJSON_IsString(host) && host->valuestring) {\n                String requestedHost = String(host->valuestring);\n                requestedHost.trim();\n                if (requestedHost.length() > 32) {\n                    requestedHost.remove(32);\n                }\n                if (requestedHost.length()) {\n                    m_host = requestedHost;\n                    if (m_credentialManager) {\n                        m_credentialManager->setHostname(m_host.c_str());\n                    }\n                }\n            }\n\n            params.dhcp = !cJSON_IsBool(dhcp) || cJSON_IsTrue(dhcp);\n            persistent = !cJSON_IsBool(persistentNode) || cJSON_IsTrue(persistentNode);\n            confirmed = cJSON_IsBool(confirmedNode) && cJSON_IsTrue(confirmedNode);\n\n            if (!params.dhcp) {\n                if (cJSON_IsString(ip) && ip->valuestring) params.config.local_ip.fromString(ip->valuestring);\n                if (cJSON_IsString(gateway) && gateway->valuestring) params.config.gateway.fromString(gateway->valuestring);\n                if (cJSON_IsString(subnet) && subnet->valuestring) params.config.subnet.fromString(subnet->valuestring);\n                if (cJSON_IsString(dns1) && dns1->valuestring) params.config.dns1.fromString(dns1->valuestring);\n                if (cJSON_IsString(dns2) && dns2->valuestring) params.config.dns2.fromString(dns2->valuestring);\n            }\n        }\n\n        if (params.password.length() == 0 && strlen(params.config.ssid) && m_credentialManager &&\n            m_credentialManager->checkSSIDExists(params.config.ssid)) {\n            String stored = m_credentialManager->getPassword(params.config.ssid);\n            if (stored.length()) {\n                params.password = stored;\n            }\n        }\n\n        bool wasStaConnected = (WiFi.status() == WL_CONNECTED && WiFi.getMode() != WIFI_AP);\n        params.fromApClient = m_isApMode;\n        params.host = m_host;\n        params.timeout = m_timeout;\n        params.wdtLongTimeout = AWS_LONG_WDT_TIMEOUT;\n        params.wdtTimeout = AWS_WDT_TIMEOUT;\n\n        if (!params.fromApClient && !confirmed && WiFi.status() == WL_CONNECTED) {\n            cJSON *confirmPayload = cJSON_CreateObject();\n            cJSON_AddBoolToObject(confirmPayload, \"confirmRequired\", true);\n            cJSON_AddStringToObject(confirmPayload, \"ssid\", params.config.ssid);\n            cJSON_Delete(root);\n            sendSetupWsResponse(clientId, reqIdStr, true, nameStr.c_str(), serializeJsonDocument(confirmPayload));\n            return;\n        }\n\n        cJSON_Delete(root);\n        sendSetupWsResponse(clientId, reqIdStr, true, nameStr.c_str(), String(\"{\\\"accepted\\\":true}\"));\n        queueSetupWifiConnect(clientId, params, persistent, wasStaConnected && !params.fromApClient, params.fromApClient);\n        return;\n    }\n\n    cJSON_Delete(root);\n    sendSetupWsResponse(clientId, reqIdStr, false, nameStr.c_str(), String(), \"Unknown command\");\n}\n\nvoid FSWebServer::sendSetupWsResponse(uint8_t clientId, const String &reqId, bool ok, const char *name, const String &payload, const String &error) {\n    if (!m_setupWebSocket || !m_setupWebSocket->clientIsConnected(clientId)) {\n        return;\n    }\n\n    cJSON *root = cJSON_CreateObject();\n    cJSON_AddStringToObject(root, \"type\", \"res\");\n    cJSON_AddStringToObject(root, \"name\", name ? name : \"unknown\");\n    if (reqId.length()) {\n        cJSON_AddStringToObject(root, \"reqId\", reqId.c_str());\n    }\n    cJSON_AddBoolToObject(root, \"ok\", ok);\n    if (payload.length()) {\n        cJSON *payloadNode = cJSON_Parse(payload.c_str());\n        if (payloadNode) {\n            cJSON_AddItemToObject(root, \"payload\", payloadNode);\n        }\n    }\n    if (error.length()) {\n        cJSON_AddStringToObject(root, \"error\", error.c_str());\n    }\n\n    String message = serializeJsonDocument(root);\n    m_setupWebSocket->sendTXT(clientId, message.c_str(), message.length());\n}\n\nvoid FSWebServer::sendSetupWsEvent(uint8_t clientId, const char *name, const String &payload) {\n    if (!m_setupWebSocket || !m_setupWebSocket->clientIsConnected(clientId)) {\n        return;\n    }\n\n    cJSON *root = cJSON_CreateObject();\n    cJSON_AddStringToObject(root, \"type\", \"evt\");\n    cJSON_AddStringToObject(root, \"name\", name ? name : \"unknown\");\n    if (payload.length()) {\n        cJSON *payloadNode = cJSON_Parse(payload.c_str());\n        if (payloadNode) {\n            cJSON_AddItemToObject(root, \"payload\", payloadNode);\n        }\n    }\n\n    String message = serializeJsonDocument(root);\n    m_setupWebSocket->sendTXT(clientId, message.c_str(), message.length());\n}\n\nString FSWebServer::buildSetupStatusPayload() const {\n    cJSON *payload = cJSON_CreateObject();\n    cJSON *status = cJSON_CreateObject();\n    cJSON_AddStringToObject(status, \"firmware\", m_version.c_str());\n\n    String mode;\n    if (WiFi.status() == WL_CONNECTED) {\n        mode = \"Station (\";\n        mode += WiFi.SSID();\n        mode += \")\";\n    } else {\n        mode = \"Access Point\";\n    }\n    cJSON_AddStringToObject(status, \"mode\", mode.c_str());\n    String ip = (WiFi.status() == WL_CONNECTED) ? WiFi.localIP().toString() : WiFi.softAPIP().toString();\n    cJSON_AddStringToObject(status, \"ip\", ip.c_str());\n    cJSON_AddStringToObject(status, \"hostname\", m_host.c_str());\n    cJSON_AddStringToObject(status, \"path\", String(ESP_FS_WS_CONFIG_FILE).substring(1).c_str());\n    cJSON_AddStringToObject(status, \"liburl\", LIB_URL);\n\n    String logoPath;\n    if (const_cast<FSWebServer *>(this)->getSetupConfigurator()->getOptionValue(\"img-logo\", logoPath) && logoPath.length() > 0) {\n        cJSON_AddStringToObject(status, \"img-logo\", logoPath.c_str());\n    }\n\n    String pageTitle;\n    if (const_cast<FSWebServer *>(this)->getSetupConfigurator()->getOptionValue(\"page-title\", pageTitle) && pageTitle.length() > 0) {\n        cJSON_AddStringToObject(status, \"page-title\", pageTitle.c_str());\n    }\n\n    cJSON_AddItemToObject(payload, \"status\", status);\n    return serializeJsonDocument(payload);\n}\n\nString FSWebServer::buildSetupConfigPayload() const {\n    cJSON *payload = cJSON_CreateObject();\n    cJSON *config = nullptr;\n    if (m_filesystem && m_filesystem->exists(ESP_FS_WS_CONFIG_FILE)) {\n        File file = m_filesystem->open(ESP_FS_WS_CONFIG_FILE, \"r\");\n        if (file) {\n            String content = file.readString();\n            file.close();\n            config = cJSON_Parse(content.c_str());\n        }\n    }\n    if (!config) {\n        config = cJSON_CreateObject();\n    }\n    cJSON_AddItemToObject(payload, \"config\", config);\n    return serializeJsonDocument(payload);\n}\n\nString FSWebServer::buildSetupCredentialsPayload() const {\n    cJSON *payload = cJSON_CreateObject();\n    cJSON *credentials = cJSON_CreateArray();\n\n    if (m_credentialManager) {\n        std::vector<WiFiCredential> *creds = m_credentialManager->getCredentials();\n        if (creds) {\n            for (size_t i = 0; i < creds->size(); ++i) {\n                const WiFiCredential &cred = (*creds)[i];\n                cJSON *item = cJSON_CreateObject();\n                cJSON_AddNumberToObject(item, \"index\", static_cast<double>(i));\n                cJSON_AddStringToObject(item, \"ssid\", cred.ssid);\n                if (cred.local_ip != IPAddress(0, 0, 0, 0)) {\n                    cJSON_AddStringToObject(item, \"ip\", cred.local_ip.toString().c_str());\n                    cJSON_AddStringToObject(item, \"gateway\", cred.gateway.toString().c_str());\n                    cJSON_AddStringToObject(item, \"subnet\", cred.subnet.toString().c_str());\n                    if (cred.dns1 != IPAddress(0, 0, 0, 0)) {\n                        cJSON_AddStringToObject(item, \"dns1\", cred.dns1.toString().c_str());\n                    }\n                    if (cred.dns2 != IPAddress(0, 0, 0, 0)) {\n                        cJSON_AddStringToObject(item, \"dns2\", cred.dns2.toString().c_str());\n                    }\n                }\n                cJSON_AddBoolToObject(item, \"hasPassword\", cred.pwd_len > 0);\n                cJSON_AddItemToArray(credentials, item);\n            }\n        }\n    }\n\n    cJSON_AddItemToObject(payload, \"credentials\", credentials);\n    return serializeJsonDocument(payload);\n}\n\nbool FSWebServer::saveSetupConfigJson(const String &jsonText) {\n    if (!m_filesystem) {\n        return false;\n    }\n\n    File file = m_filesystem->open(ESP_FS_WS_CONFIG_FILE, \"w\");\n    if (!file) {\n        return false;\n    }\n\n    size_t written = file.print(jsonText);\n    file.close();\n    if (written == 0) {\n        return false;\n    }\n\n    if (m_configSavedCallback) {\n        m_configSavedCallback(ESP_FS_WS_CONFIG_FILE);\n    }\n    return true;\n}\n\nvoid FSWebServer::queueSetupWifiConnect(uint8_t clientId, const WiFiConnectParams &params, bool persistent, bool allowApFallback, bool fromApClient) {\n    m_pendingSetupClientId = clientId;\n    m_pendingSetupParams = params;\n    m_pendingSetupPersistent = persistent;\n    m_pendingSetupAllowApFallback = allowApFallback;\n    m_pendingSetupFromApClient = fromApClient;\n    m_pendingSetupWifiConnect = true;\n\n    cJSON *payload = cJSON_CreateObject();\n    cJSON_AddStringToObject(payload, \"ssid\", params.config.ssid);\n    sendSetupWsEvent(clientId, \"wifi.connect.started\", serializeJsonDocument(payload));\n}\n\nvoid FSWebServer::processPendingSetupWifiConnection() {\n    m_pendingSetupWifiConnect = false;\n\n    WiFiConnectParams params = m_pendingSetupParams;\n    bool persistent = m_pendingSetupPersistent;\n    bool allowApFallback = m_pendingSetupAllowApFallback;\n    bool fromApClient = m_pendingSetupFromApClient;\n    uint8_t clientId = m_pendingSetupClientId;\n\n    WiFiConnectResult result = WiFiService::connectWithParams(params);\n\n    if (result.connected && persistent && strlen(params.config.ssid) && params.password.length() && m_credentialManager) {\n        if (params.dhcp) {\n            params.config.local_ip = IPAddress(0, 0, 0, 0);\n            params.config.gateway = IPAddress(0, 0, 0, 0);\n            params.config.subnet = IPAddress(0, 0, 0, 0);\n            params.config.dns1 = IPAddress(0, 0, 0, 0);\n            params.config.dns2 = IPAddress(0, 0, 0, 0);\n        }\n        if (!m_credentialManager->updateCredential(params.config, params.password.c_str())) {\n            m_credentialManager->addCredential(params.config, params.password.c_str());\n        }\n#if defined(ESP32)\n        m_credentialManager->saveToNVS();\n#elif defined(ESP8266)\n        m_credentialManager->saveToFS();\n#endif\n    }\n\n    if (result.connected) {\n        m_serverIp = result.ip;\n    } else if (allowApFallback && strlen(m_apSSID) > 0) {\n        startCaptivePortal(m_apSSID, m_apPassword, \"/setup\");\n    }\n\n    cJSON *payload = cJSON_CreateObject();\n    cJSON_AddBoolToObject(payload, \"connected\", result.connected);\n    cJSON_AddNumberToObject(payload, \"status\", result.status);\n    cJSON_AddStringToObject(payload, \"body\", result.body.c_str());\n    cJSON_AddStringToObject(payload, \"ip\", result.ip.toString().c_str());\n    cJSON_AddStringToObject(payload, \"hostname\", m_host.c_str());\n    cJSON_AddBoolToObject(payload, \"fromApClient\", fromApClient);\n    sendSetupWsEvent(clientId, result.connected ? \"wifi.connect.success\" : \"wifi.connect.error\", serializeJsonDocument(payload));\n}\n#endif\n\n\nvoid FSWebServer::handleIndex(){\n    log_debug(\"handleIndex\");\n    if (m_filesystem->exists(\"/index.htm\")) {\n        File dataFile = m_filesystem->open(\"/index.htm\", \"r\");\n        this->streamFile(dataFile, \"text/html\");\n        dataFile.close();\n        log_debug(\"Serving /index.htm\");\n    } \n    else if (m_filesystem->exists(\"/index.html\")) {\n        File dataFile = m_filesystem->open(\"/index.html\", \"r\");\n        this->streamFile(dataFile, \"text/html\");\n        dataFile.close();\n        log_debug(\"Serving /index.html\");\n    }\n\n    #if ESP_FS_WS_SETUP \n    else {\n        handleSetup();\n        log_debug(\"No index file found, redirecting to /setup\");\n    }\n    #endif\n}\n\nvoid FSWebServer::printFileList(fs::FS &fs, const char * dirname, uint8_t levels) {\n    printFileList(fs, dirname, levels, Serial);\n}\n\nvoid FSWebServer::printFileList(fs::FS &fs, const char * dirname, uint8_t levels, Print& out) {\n    out.print(\"\\nListing directory: \");\n    out.println(dirname);\n    File root = fs.open(dirname, \"r\");\n    if (!root) {\n        out.println(\"- failed to open directory\");\n        return;\n    }\n    if (!root.isDirectory()) {\n        out.println(\" - not a directory\");\n        return;\n    }\n    File file = root.openNextFile();\n    while (file) {\n        if (file.isDirectory()) {\n            if (levels) {\n                #ifdef ESP32\n                printFileList(fs, file.path(), levels - 1, out);\n                #elif defined(ESP8266)\n                printFileList(fs, file.fullName(), levels - 1, out);\n                #endif\n            }\n        } else {\n            String line = \"|__ FILE: \";\n            if (typeName == \"SPIFFS\") {\n                #ifdef ESP32\n                line += file.path();\n                #elif defined(ESP8266)\n                line += file.fullName();\n                #endif\n            } else {\n                line += file.name();\n            }            \n            line += \" (\";\n            line += (unsigned long)file.size();\n            line += \" bytes)\";\n            out.println(line);\n        }\n        file = root.openNextFile();\n    }\n}\n\n#if ESP_FS_WS_EDIT\nvoid FSWebServer::enableFsCodeEditor() {\n    on(\"/status\", HTTP_GET, [this]() { this->handleFsStatus(); });\n    on(\"/list\", HTTP_GET, [this]() { this->handleFileList(); });\n    on(\"/edit\", HTTP_PUT, [this]() { this->handleFileCreate(); });\n    on(\"/edit\", HTTP_GET, [this]() { this->handleFileEdit(); });\n    on(\"/edit\", HTTP_DELETE, [this]() { this->handleFileDelete(); });\n    on(\"/edit\", HTTP_POST,\n        [this]() { this->sendOK(); },\n        [this]() { this->handleFileUpload(); }\n    );\n}\n#endif\n\nvoid FSWebServer::setAuthentication(const char* user, const char* pswd) {\n    // Free previous allocations if they exist\n    if (m_pageUser) {\n        free(m_pageUser);\n        m_pageUser = nullptr;\n    }\n    if (m_pagePswd) {\n        free(m_pagePswd);\n        m_pagePswd = nullptr;\n    }\n    \n    // Validate input parameters\n    if (!user || !pswd || strlen(user) == 0 || strlen(pswd) == 0) {\n        log_error(\"Invalid authentication credentials\");\n        return;\n    }\n    \n    // Allocate with proper size (+1 for null terminator)\n    size_t userLen = strlen(user) + 1;\n    size_t pswnLen = strlen(pswd) + 1;\n    \n    m_pageUser = (char*) malloc(userLen);\n    m_pagePswd = (char*) malloc(pswnLen);\n    \n    if (m_pageUser && m_pagePswd) {\n        strncpy(m_pageUser, user, userLen - 1);\n        strncpy(m_pagePswd, pswd, pswnLen - 1);\n        m_pageUser[userLen - 1] = '\\0';\n        m_pagePswd[pswnLen - 1] = '\\0';\n        log_debug(\"Authentication credentials set successfully\");\n    } \n    else {\n        log_error(\"Failed to allocate memory for authentication credentials\");\n        if (m_pageUser) {\n            free(m_pageUser);\n            m_pageUser = nullptr;\n        }\n        if (m_pagePswd) {\n            free(m_pagePswd);\n            m_pagePswd = nullptr;\n        }\n    }\n}\n\nvoid FSWebServer::handleFileName() {\n    if (m_filesystem->exists(this->uri()))\n        this->send(301, \"text/plain\", \"OK\");\n    this->send(404, \"text/plain\", \"File not found\");\n}\n\nvoid FSWebServer::sendOK() {\n  this->send(200, \"text/plain\", \"OK\");\n}\n\n\n// First try to find and return the requested file from the filesystem,\n// and if it fails, return a 404 page with debug information        \nvoid FSWebServer::handleFileRequest() {\n    String _url = WebServerClass::urlDecode(this->uri());\n    log_debug(\"handleFileRequest for %s\", _url.c_str());\n\n    // Check if authentication for all routes is turned on, and credentials are present:\n    if (m_authAll && m_pageUser != nullptr) {\n        if(!this->authenticate(m_pageUser, m_pagePswd))\n            return this->requestAuthentication();\n    }\n\n#if defined(ESP8266)\n    // ESP8266WebServer has its own mime namespace\n    using namespace mime;\n    String contentType = getContentType(_url);\n#elif defined(ESP32)\n    // Use local mimetable from library\n    String contentType = mimetype::getContentType(_url);\n#endif\n\n    if (!m_filesystem->exists(_url)) {\n        // Requested file not found, check if gzipped version exists\n        log_debug(\"File %s not found, checking for gzipped version\", this->uri().c_str());\n        _url += \".gz\";  \n    }\n    \n    if (m_filesystem->exists(_url)) {\n        File file = m_filesystem->open(_url , \"r\");\n        if (file) {               \n            this->streamFile(file, contentType);\n            file.close();\n            return; // If file was served, skip the rest\n        } \n        else {\n            log_debug(\"Failed to open file %s\", _url.c_str());\n            this->send(500, \"text/plain\", \"FsWebServer: Failed to open file resource\");\n        } \n    }\n    else {\n        log_debug(\"File %s not found, checking for index redirection\", this->uri().c_str());\n        \n        // File not found\n        if (this->uri() == \"/\" && !m_filesystem->exists(\"/index.htm\") && !m_filesystem->exists(\"/index.html\")) {\n            this->sendHeader(PSTR(\"Location\"), \"/\");\n            this->send(302, \"text/plain\", \"\");\n            log_debug(\"Redirecting \\\"/\\\" to \\\"/setup\\\" (no index file found)\");\n            return;\n        }\n    }\n\n    this->send(404, \"text/plain\", \"FsWebServer: resource not found\");\n    log_debug(\"Resource %s not found\", this->uri().c_str());\n}\n\n\n#if ESP_FS_WS_SETUP\nvoid FSWebServer::handleSetup() {\n    if (m_pageUser != nullptr) {\n        if(!this->authenticate(m_pageUser, m_pagePswd))\n            return this->requestAuthentication();\n    }\n    initSetupWebSocket();\n    this->sendHeader(PSTR(\"Content-Encoding\"), \"gzip\");\n    this->sendHeader(PSTR(\"X-Config-File\"), ESP_FS_WS_CONFIG_FILE);\n    this->sendHeader(PSTR(\"Set-Cookie\"), \"esp_fs_ws_mode=dedicated; Path=/; SameSite=Lax\");\n    // Changed array name to match SEGGER Bin2C output\n    this->send_P(200, \"text/html\", (const char*)_acsetup_min_htm, sizeof(_acsetup_min_htm));\n}\n#endif\n\n#if ESP_FS_WS_SETUP\nbool FSWebServer::createDirFromPath(const String& path) {\n    String dir;\n    dir.reserve(path.length());\n    int p1 = 0;  int p2 = 0;\n    while (p2 != -1) {\n        p2 = path.indexOf(\"/\", p1 + 1);\n        dir += path.substring(p1, p2);\n        // Check if its a valid dir\n        if (dir.indexOf(\".\") == -1) {\n            if (!m_filesystem->exists(dir)) {\n                if (m_filesystem->mkdir(dir)) {\n                    log_info(\"Folder %s created\\n\", dir.c_str());\n                } else {\n                    log_info(\"Error. Folder %s not created\\n\", dir.c_str());\n                    return false;\n                }\n            }\n        }\n        p1 = p2;\n    }\n    return true;\n}\n\n\n/*\n    Checks filename for character combinations that are not supported by FSBrowser (alhtough valid on SPIFFS).\n    Returns an empty String if supported, or detail of error(s) if unsupported\n*/\nvoid FSWebServer::checkForUnsupportedPath(String& filename, String& error)\n{\n    if (!filename.startsWith(\"/\")) {\n        error += PSTR(\" !! NO_LEADING_SLASH !! \");\n    }\n    if (filename.indexOf(\"//\") != -1) {\n        error += PSTR(\" !! DOUBLE_SLASH !! \");\n    }\n    if (filename.endsWith(\"/\")) {\n        error += PSTR(\" ! TRAILING_SLASH ! \");\n    }\n    log_debug(\"%s: %s\", filename.c_str(), error.c_str());\n}\n\nvoid FSWebServer::handleFileUpload()\n{\n    HTTPUpload& upload = this->upload();\n    if (upload.status == UPLOAD_FILE_START) {\n        String filename = upload.filename;\n        String result;\n        // Make sure paths always start with \"/\"\n        if (!filename.startsWith(\"/\")) {\n            filename = \"/\" + filename;\n        }\n        checkForUnsupportedPath(filename, result);\n        createDirFromPath(filename);\n\n        if (result.length() > 0) {\n            this->send(500, \"text/plain\", \"INVALID FILENAME\");\n            return;\n        }\n\n        log_debug(\"handleFileUpload Name: %s\\n\", filename.c_str());\n        m_uploadFile = m_filesystem->open(filename, \"w\");\n        if (!m_uploadFile) {\n            this->send(500, \"text/plain\", \"CREATE FAILED\");\n            return;\n        }\n        log_debug(\"Upload: START, filename: %s\\n\", filename.c_str());\n    } \n    else if (upload.status == UPLOAD_FILE_WRITE) {\n        if (m_uploadFile) {\n            size_t bytesWritten = m_uploadFile.write(upload.buf, upload.currentSize);\n            if (bytesWritten != upload.currentSize) {\n                this->send(500, \"text/plain\", \"WRITE FAILED\");\n                return;\n            }\n        }\n        log_debug(\"Upload: WRITE, Bytes: %d\\n\", upload.currentSize);\n    } \n    else if (upload.status == UPLOAD_FILE_END) {\n        #if defined(ESP32)\n            const char* filepath = m_uploadFile.path();            \n        #elif defined(ESP8266)\n            const char* filepath = m_uploadFile.fullName();   \n        #endif\n\n        // Call config saved callback if this is the config file\n        if (strcmp(filepath, ESP_FS_WS_CONFIG_FILE) == 0 && m_configSavedCallback) {\n            log_debug(\"Config file saved, calling callback\");\n            m_configSavedCallback(filepath);\n        }\n\n        if (m_uploadFile) { \n            m_uploadFile.close();\n        }\n        log_debug(\"Upload: END, Size: %d\\n\", upload.totalSize);\n    }\n}\n\nvoid FSWebServer::update_first()\n{\n    size_t fsize;\n    if (this->hasArg(\"size\"))\n        fsize = this->arg(\"size\").toInt();\n    else {\n        this->send(500, \"text/plain\", F(\"Request malformed: missing file size\"));\n        return;\n    }\n\n    HTTPUpload& upload = this->upload();\n    if (upload.status == UPLOAD_FILE_START) {\n        log_info(\"Receiving Update: %s, Size: %d\", upload.filename.c_str(), fsize);\n        if (!Update.begin(fsize)) {\n            otaDone = 0;\n            Update.printError(Serial);\n        }\n    }\n    else if (upload.status == UPLOAD_FILE_WRITE) {\n        if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {\n            Update.printError(Serial);\n        }\n        else {\n            static uint32_t pTime = millis();\n            if (millis() - pTime > 500) {\n                pTime = millis();\n                otaDone = 100 * Update.progress() / Update.size();\n                log_info(\"OTA progress: %d%%\", otaDone);\n            }\n        }\n    }\n    else if (upload.status == UPLOAD_FILE_END) {\n        if (Update.end(true)) {\n            log_info(\"Update Success: %u bytes\\nRebooting...\", upload.totalSize);\n        }\n        else {\n#if defined(ESP8266)\n            log_error(\"%s\\n\", Update.getErrorString().c_str());\n#elif defined(ESP32)\n            log_error(\"%s\\n\", Update.errorString());\n#endif\n            otaDone = 0;\n        }\n    }\n}\n\n\n// the request handler is triggered after the upload has finished...\n// create the response, add header, and send response\nvoid FSWebServer::update_second()\n{\n    String txt;\n    if (Update.hasError()) {\n        txt = \"Error! \";\n#if defined(ESP8266)\n        txt += Update.getErrorString().c_str();\n#elif defined(ESP32)\n        txt += Update.errorString();\n#endif\n    } else {\n        txt = F(\"Update completed successfully. The ESP32 will restart\");\n    }\n    log_info(\"%s\", txt.c_str());\n    this->send((Update.hasError()) ? 500 : 200, \"text/plain\", txt);\n    if (!Update.hasError()) {\n        delay(500);\n        ESP.restart();\n    }\n}\n\n\n#endif //ESP_FS_WS_SETUP\n\nbool FSWebServer::startWiFi(uint32_t timeout, CallbackF fn) {\n#ifdef ESP32\n    if (m_credentialManager && m_credentialManager->loadFromNVS()) {\n        log_debug(\"Credentials loaded from NVS\");\n        log_debug(\"%s\", m_credentialManager->getDebugInfo().c_str());\n    }\n#else\n    if (m_credentialManager && m_credentialManager->loadFromFS()) {\n        log_debug(\"Credentials loaded from filesystem\");\n        log_debug(\"%s\", m_credentialManager->getDebugInfo().c_str());\n    }\n#endif\n\n    WiFiStartResult result = WiFiService::startWiFi(m_credentialManager, m_filesystem, ESP_FS_WS_CONFIG_FILE, timeout);\n    if (result.action == WiFiStartAction::Connected) {\n        m_serverIp = result.ip;\n        m_isApMode = false;\n    #if ESP_FS_WS_MDNS\n        WiFiService::startMDNSOnly(m_host, m_port);\n        log_info(\"mDNS started on http://%s.local\", m_host.c_str());\n    #endif\n        return true;\n    }\n\n    if (result.action == WiFiStartAction::StartAp && strlen(m_apSSID) > 0) {\n        log_info(\"Starting AP mode: SSID=%s\", m_apSSID);\n        startCaptivePortal(m_apSSID, m_apPassword, \"/setup\");\n        return true;\n    }\n    return false;\n}\n\n\nbool FSWebServer::startMDNSResponder() {\n#if ESP_FS_WS_MDNS\n    return WiFiService::startMDNSResponder(m_dnsServer, m_host, m_port, m_serverIp);\n#else\n    return true;\n#endif\n}\n\nvoid FSWebServer::redirect(const char* url) {\n    if (strcmp(url, \"/setup\") == 0) {\n        return this->handleSetup();\n    }\n\n    this->sendHeader(PSTR(\"Location\"), url);\n    this->send(302, \"text/plain\", \"\");\n    log_debug(\"Redirecting %s\", url);\n}\n\nbool FSWebServer::startCaptivePortal(const char* ssid, const char* pass, const char* redirectTargetURL) {\n    WiFiConnectParams params;\n    strncpy(params.config.ssid, ssid, sizeof(params.config.ssid) - 1);\n    params.config.ssid[sizeof(params.config.ssid) - 1] = '\\0';\n    params.password = pass;\n    return startCaptivePortal(params, redirectTargetURL);\n}\n\n\nbool FSWebServer::startCaptivePortal(WiFiConnectParams& params, const char *redirectTargetURL) {\n    // Captive portal OS connectivity probes\n    this->on(\"/generate_204\", HTTP_GET, [this, redirectTargetURL]() { this->redirect(redirectTargetURL); }); // Android probe\n    this->on(\"/hotspot-detect.html\", HTTP_GET, [this, redirectTargetURL]() { this->redirect(redirectTargetURL); }); // iOS/macOS probe\n    this->on(\"/ncsi.txt\", HTTP_GET,  [this, redirectTargetURL]() { this->redirect(redirectTargetURL); });  // Windows NCSI probe\n    this->on(\"/connecttest.txt\", HTTP_GET,  [this, redirectTargetURL]() { this->redirect(redirectTargetURL); }); // Windows 8/10 probe\n    this->on(\"/success.txt\", HTTP_GET,  [this, redirectTargetURL]() { this->redirect(redirectTargetURL); }); \n    this->on(\"/library/test/success.html\", HTTP_GET,  [this, redirectTargetURL]() { this->redirect(redirectTargetURL); }); // ChromeOS probe\n    this->on(\"/redirect\", HTTP_GET,  [this, redirectTargetURL]() { this->redirect(redirectTargetURL); });\n    if (!WiFiService::startAccessPoint(params, m_serverIp)) {\n        return false;\n    }\n    m_isApMode = true;\n    this->startMDNSResponder();\n    log_info(\"Captive portal started. Redirecting all requests to %s\", redirectTargetURL);\n    return true;\n}\n\n\n// edit page, in usefull in some situation, but if you need to provide only a web interface, you can disable\n#if ESP_FS_WS_EDIT\n\nvoid FSWebServer::handleFileEdit() {\n  \n    if (m_pageUser != nullptr) {\n        if(!this->authenticate(m_pageUser, m_pagePswd))\n            return this->requestAuthentication();\n    }\n    this->sendHeader(PSTR(\"Content-Encoding\"), \"gzip\");\n    this->send_P(200, \"text/html\", (const char*)_acedit_htm, sizeof(_acedit_htm));\n}\n\n/*\n    Return the list of files in the directory specified by the \"dir\" query string parameter.\n    Also demonstrates the use of chunked responses.\n*/\nvoid FSWebServer::handleFileList()\n{\n    if (!this->hasArg(\"dir\")) {\n        return this->send(400, \"DIR ARG MISSING\");\n    }\n\n    String path = this->arg(\"dir\");\n    log_debug(\"handleFileList: %s\", path.c_str());\n    if (path != \"/\" && !m_filesystem->exists(path)) {\n        return this->send(400, \"BAD PATH\");\n    }\n\n    File root = m_filesystem->open(path, \"r\");\n    CJSON::Json json_array;\n    json_array.createArray();\n    if (root.isDirectory()) {\n        File file = root.openNextFile();\n        while (file) {\n            String filename;\n            if (typeName.equals(\"SPIFFS\")) {\n                // SPIFFS returns full path and subfolders are unsupported, remove leading '/'                \n                #ifdef ESP32\n                filename += file.path();\n                #elif defined(ESP8266)\n                filename += file.fullName();\n                #endif\n                filename.remove(0, 1);\n            } \n            else {\n                filename = file.name();\n                if (filename.lastIndexOf(\"/\") > -1) {\n                    filename.remove(0, filename.lastIndexOf(\"/\") + 1);\n                }\n            }                    \n            CJSON::Json item;\n            item.setString(\"type\", (file.isDirectory()) ? \"dir\" : \"file\");\n            item.setNumber(\"size\", file.size());\n            item.setString(\"name\", filename);\n            json_array.add(item);\n            file = root.openNextFile();\n        }\n    }\n    this->send(200, \"text/json\", json_array.serialize());\n}\n\n/*\n    Handle the creation/rename of a new file\n    Operation      | req.responseText\n    ---------------+--------------------------------------------------------------\n    Create file    | parent of created file\n    Create folder  | parent of created folder\n    Rename file    | parent of source file\n    Move file      | parent of source file, or remaining ancestor\n    Rename folder  | parent of source folder\n    Move folder    | parent of source folder, or remaining ancestor\n*/\nvoid FSWebServer::handleFileCreate()\n{\n    String path = this->arg(\"path\");\n    if (path.isEmpty()) {\n        return this->send(400, \"PATH ARG MISSING\");\n    }\n    if (path == \"/\") {\n        return this->send(400, \"BAD PATH\");\n    }\n\n    String src = this->arg(\"src\");\n    if (src.isEmpty())  {\n        // No source specified: creation\n        log_debug(\"handleFileCreate: %s\\n\", path.c_str());\n        if (path.endsWith(\"/\")) {\n            // Create a folder\n            path.remove(path.length() - 1);\n            if (!m_filesystem->mkdir(path)) {\n                return this->send(500, \"MKDIR FAILED\");\n            }\n        }\n        else  {\n            // Create a file\n            File file = m_filesystem->open(path, \"w\");\n            if (file) {\n                file.write(0);\n                file.close();\n            }\n            else {\n                return this->send(500, \"CREATE FAILED\");\n            }\n        }\n        this->send(200,  path.c_str());\n    }\n    else  {\n        // Source specified: rename\n        if (src == \"/\") {\n            return this->send(400, \"BAD SRC\");\n        }\n        if (!m_filesystem->exists(src)) {\n            return this->send(400,  \"FILE NOT FOUND\");\n        }\n\n        log_debug(\"handleFileCreate: %s from %s\\n\", path.c_str(), src.c_str());\n        if (path.endsWith(\"/\")) {\n            path.remove(path.length() - 1);\n        }\n        if (src.endsWith(\"/\")) {\n            src.remove(src.length() - 1);\n        }\n        if (!m_filesystem->rename(src, path))  {\n            return this->send(500, \"RENAME FAILED\");\n        }\n        this->sendOK();\n    }\n}\n\n/*\n    Handle a file deletion request\n    Operation      | req.responseText\n    ---------------+--------------------------------------------------------------\n    Delete file    | parent of deleted file, or remaining ancestor\n    Delete folder  | parent of deleted folder, or remaining ancestor\n*/\n\nvoid FSWebServer::deleteContent(String& path) {\n  File file = m_filesystem->open(path.c_str(), \"r\");\n  if (!file.isDirectory()) {\n    file.close();\n    m_filesystem->remove(path.c_str());\n    log_info(\"File %s deleted\", path.c_str());\n    return;\n  }\n\n  file.rewindDirectory();\n  while (true) {\n    File entry = file.openNextFile();\n    if (!entry) {\n      break;\n    }\n    String entryPath = path;\n    entryPath += \"/\";\n    entryPath += entry.name();\n    if (entry.isDirectory()) {\n      entry.close();\n      deleteContent(entryPath);\n    }\n    else {\n      entry.close();\n      m_filesystem->remove(entryPath.c_str());\n      log_info(\"File %s deleted\", path.c_str());\n    }\n    yield();\n  }\n  m_filesystem->rmdir(path.c_str());\n  log_info(\"Folder %s removed\", path.c_str());\n  file.close();\n}\n\n\n\nvoid FSWebServer::handleFileDelete() {\n    String path = this->arg((size_t)0);\n    if (path.isEmpty() || path == \"/\")  {\n        return this->send(400, \"BAD PATH\");\n    }\n    if (!m_filesystem->exists(path))  {\n        return this->send(400, \"File Not Found\");\n    }\n    deleteContent(path);\n    this->sendOK();\n}\n\n/*\n    Return the FS type, status and size info\n*/\nvoid FSWebServer::handleFsStatus()\n{\n    log_debug(\"handleStatus\");\n    fsInfo_t info = {1024, 1024, \"ESP Filesystem\"};\n#ifdef ESP8266\n    FSInfo fs_info;\n    m_filesystem->info(fs_info);\n    info.totalBytes = fs_info.totalBytes;\n    info.usedBytes = fs_info.usedBytes;\n#endif\n    if (getFsInfo != nullptr) {\n        getFsInfo(&info);\n    }\n    CJSON::Json doc;\n    doc.setString(\"type\", info.fsName);\n    doc.setString(\"isOk\", m_filesystem_ok ? \"true\" : \"false\");\n\n    if (m_filesystem_ok)  {\n        IPAddress ip = (WiFi.status() == WL_CONNECTED) ? WiFi.localIP() : WiFi.softAPIP();\n        doc.setString(\"totalBytes\", String(info.totalBytes));\n        doc.setString(\"usedBytes\", String(info.usedBytes));\n        doc.setString(\"mode\", WiFi.status() == WL_CONNECTED ? \"Station\" : \"Access Point\");\n        doc.setString(\"ssid\", WiFi.SSID());\n        doc.setString(\"ip\", ip.toString());\n    }\n    doc.setString(\"unsupportedFiles\", \"\");\n    this->send(200, \"application/json\", doc.serialize());\n}\n#endif // ESP_FS_WS_EDIT\n\n"
  },
  {
    "path": "src/FSWebServer.h",
    "content": "#ifndef FS_WEBSERVER_H\n#define FS_WEBSERVER_H\n\n\n#include \"WiFiService.h\"\n#include \"Json.h\"\n#include \"SerialLog.h\"\n#include \"Version.h\"\n#include \"websocket/WebSocketsServer.h\"\n#include <type_traits>\n#include <DNSServer.h>\n#include <FS.h>\n\n#if defined(ESP8266)\n#include <ESP8266HTTPUpdateServer.h> // from Arduino core, OTA update via webbrowser\n#include <ESP8266WebServer.h>\n#include <ESP8266WiFi.h>\n#include <ESP8266mDNS.h>\nusing WebServerClass = ESP8266WebServer;\n#elif defined(ESP32)\n#include \"esp_task_wdt.h\"\n#include \"sys/stat.h\"\n#include <Update.h>\n#include <WebServer.h>\n#include <WiFi.h>\n#include <esp_wifi.h>\n#include <mdns.h>\nusing WebServerClass = WebServer;\n#endif\n\nclass Print;\n\n#ifdef ESP32\n// Arduino-ESP32 v3 splits networking primitives into a dedicated core library.\n// PlatformIO's dependency finder doesn't always pull it in via transitive\n// includes, so include it explicitly to ensure it gets compiled and linked.\n#if defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 3)\n#include <Network.h>\n#endif\n#include \"esp_task_wdt.h\"\n#include \"esp_wifi.h\"\n#include \"sys/stat.h\"\n#include <ESPmDNS.h>\n#include <Update.h>\n#include <WiFi.h>\n#include <WiFiAP.h>\n#elif defined(ESP8266)\n#include <ESP8266mDNS.h>\n#include <Updater.h>\n#else\n#error Platform not supported\n#endif\n\n\n#ifndef ESP_FS_WS_EDIT\n#define ESP_FS_WS_EDIT 1 // Library has edit methods\n#ifndef ESP_FS_WS_EDIT_HTM\n// Disable if you provide your own edit page\n#define ESP_FS_WS_EDIT_HTM 1 // Library serve /edit webpage from progmem\n#endif\n#endif\n\n#ifndef ESP_FS_WS_SETUP\n#define ESP_FS_WS_SETUP 1 // Library has setup methods\n#ifndef ESP_FS_WS_SETUP_HTM\n// Disable if you provide your own setup page\n#define ESP_FS_WS_SETUP_HTM 1 // Library serve /setup webpage from progmem\n#endif\n#endif\n\n#if ESP_FS_WS_EDIT_HTM\n#include \"assets/edit_htm.h\"\n#endif\n\n#if ESP_FS_WS_SETUP_HTM\n#define ESP_FS_WS_CONFIG_FOLDER \"/setup\"\n#define ESP_FS_WS_CONFIG_FILE ESP_FS_WS_CONFIG_FOLDER \"/config.json\"\n#include \"CredentialManager.h\"\n#include \"SetupConfig.hpp\"\n#include \"assets/setup_htm.h\"\n#include \"assets/logo_svg.h\"\n#endif\n\n#ifndef ESP_FS_WS_MDNS\n#define ESP_FS_WS_MDNS 1\n#endif\n\n#ifndef ESP_FS_WS_WEBSOCKET\n#define ESP_FS_WS_WEBSOCKET 1\n#endif\n\n#define LIB_URL \"https://github.com/cotestatnt/esp-fs-webserver/\"\n#define MIN_F -3.4028235E+38\n#define MAX_F 3.4028235E+38\n\n// Watchdog timeout utility\n#if defined(ESP32)\n#define AWS_WDT_TIMEOUT (CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000)\n#define AWS_LONG_WDT_TIMEOUT (AWS_WDT_TIMEOUT * 4)\n#else\n#define AWS_WDT_TIMEOUT 5000\n#define AWS_LONG_WDT_TIMEOUT 15000\n#endif\n\ntypedef struct {\n  size_t totalBytes;\n  size_t usedBytes;\n  String fsName;\n} fsInfo_t;\n\nusing FsInfoCallbackF = std::function<void(fsInfo_t *)>;\nusing CallbackF = std::function<void(void)>;\nusing ConfigSavedCallbackF = std::function<void(const char *)>; // Callback for config file saves\n\nclass FSWebServer : public WebServerClass {\nprotected:\n#if ESP_FS_WS_WEBSOCKET\n  WebSocketsServer *m_websocket;\n#endif\n#if ESP_FS_WS_SETUP\n  WebSocketsServer *m_setupWebSocket = nullptr;\n#endif\n  DNSServer *m_dnsServer = nullptr;\n  bool m_isApMode = false;\n  bool m_authAll = false; // Flag to require authentication for all pages\n  char m_apSSID[33] = {0};\n  char m_apPassword[65] = {0};\n\n  String typeName = \"FileSystem\";\n\n  void handleFileRequest();\n  void handleFileName();\n  void handleIndex();\n\n#if ESP_FS_WS_SETUP\n  File m_uploadFile;\n  uint8_t otaDone = 0;\n  void handleSetup();\n  void handleFileUpload();\n  void checkForUnsupportedPath(String &filename, String &error);\n  void update_second();\n  void update_first();\n#endif\n\n  // edit page, in useful in some situation, but if you need to provide only a\n  // web interface, you can disable\n#if ESP_FS_WS_EDIT_HTM\n  void deleteContent(String &path);\n  void handleFileDelete();\n  void handleFileCreate();\n  void handleFsStatus();\n  void handleFileList();\n  void handleFileEdit();\n#endif\n\n  /*\n    Create a dir if not exist on uploading files\n  */\n  bool createDirFromPath(const String &path);\n\nprivate:\n  char *m_pageUser = nullptr;\n  char *m_pagePswd = nullptr;\n  String m_host = \"esphost\";\n  String m_captiveUrl = \"/setup\";\n\n  uint16_t m_port;\n  uint32_t m_timeout = AWS_LONG_WDT_TIMEOUT;\n\n  // Firmware version buffer (expanded to accommodate custom version strings)\n  String m_version;\n  bool m_filesystem_ok = false;\n\n  fs::FS *m_filesystem = nullptr;\n  FsInfoCallbackF getFsInfo = nullptr;\n  ConfigSavedCallbackF m_configSavedCallback = nullptr; // Callback for config file saves\n  IPAddress m_serverIp = IPAddress(192, 168, 4, 1);\n\n  CredentialManager *m_credentialManager = nullptr;\n\n#if ESP_FS_WS_SETUP\n  SetupConfigurator *setup = nullptr;\n\n  bool m_pendingSetupWifiConnect = false;\n  WiFiConnectParams m_pendingSetupParams;\n  bool m_pendingSetupPersistent = true;\n  bool m_pendingSetupAllowApFallback = false;\n  bool m_pendingSetupFromApClient = false;\n  uint8_t m_pendingSetupClientId = 0;\n  bool m_releaseSetupWebSocketPending = false;\n  unsigned long m_pendingSetupRestartAt = 0;\n\n  void initSetupWebSocket();\n  void releaseSetupWebSocketIfIdle();\n  void handleSetupWebSocket(uint8_t clientId, WStype_t type, uint8_t *payload, size_t length);\n  void handleSetupWebSocketMessage(uint8_t clientId, const uint8_t *data, size_t len);\n  void sendSetupWsResponse(uint8_t clientId, const String &reqId, bool ok, const char *name, const String &payload = String(), const String &error = String());\n  void sendSetupWsEvent(uint8_t clientId, const char *name, const String &payload = String());\n  String buildSetupStatusPayload() const;\n  String buildSetupConfigPayload() const;\n  String buildSetupCredentialsPayload() const;\n  bool saveSetupConfigJson(const String &jsonText);\n  void queueSetupWifiConnect(uint8_t clientId, const WiFiConnectParams &params, bool persistent, bool allowApFallback, bool fromApClient);\n  void processPendingSetupWifiConnection();\n\n  // Lazy initialization: create setup object only when first needed\n  SetupConfigurator *getSetupConfigurator() {\n    if (!setup) {\n      setup = new SetupConfigurator(m_filesystem, m_port, m_host);\n    }\n    return setup;\n  }\n  \n  // Free setup configurator memory (will be recreated lazily if needed)\n  void freeSetupConfigurator() {\n    if (setup) {\n      delete setup;\n      setup = nullptr;\n      log_debug(\"SetupConfigurator freed from memory\");\n    }\n  }\n#endif\n\npublic:\n  // Template Constructor for derived filesystem classes (LittleFS, SPIFFS, etc)\n  template <typename T>\n  FSWebServer(T &fs, uint16_t port = 80, const char *hostname = \"\") : WebServerClass(port), m_filesystem(&fs) {\n    m_port = port;\n    // Set hostname if provided from constructor\n    if (strlen(hostname))\n      m_host = hostname;\n\n#ifdef ESP32\n    // Auto-configure getFsInfo for ESP32 filesystems\n    // Try to infer filesystem name from template type\n    String pretty = __PRETTY_FUNCTION__;\n    int start = pretty.indexOf(\"T = \");\n    if (start != -1) {\n      start += 4;\n      int end = pretty.indexOf(\"]\", start);\n      int semi = pretty.indexOf(\";\", start);\n      if (semi != -1 && semi < end) {\n        end = semi;\n      }\n      if (end != -1) {\n        typeName = pretty.substring(start, end);\n        // Clean up common prefixes/suffixes\n        typeName.replace(\"fs::\", \"\");\n        if (typeName.endsWith(\"FS\")) {\n          typeName = typeName.substring(0, typeName.length() - 2);\n        }\n      }\n    }\n\n    getFsInfo = [&fs, this](fsInfo_t *info) {\n      info->totalBytes = fs.totalBytes();\n      info->usedBytes = fs.usedBytes();\n      info->fsName = this->typeName;\n    };\n#endif\n\n    // Start credential manager\n    m_credentialManager = new CredentialManager();\n    m_credentialManager->begin();\n\n  #ifdef ESP8266\n    // Set FS for persistence\n    m_credentialManager->setFilesystem(&fs);\n  #endif\n\n  }\n\n  // Class destructor\n  ~FSWebServer() {\n    stop();\n\n    // Deallocate all dynamically allocated resources\n    if (m_pageUser)\n      free(m_pageUser);\n    if (m_pagePswd)\n      free(m_pagePswd);\n\n    if (m_credentialManager)\n      delete m_credentialManager;\n\n#if ESP_FS_WS_WEBSOCKET\n    if (m_websocket)\n      delete m_websocket;\n#endif\n#if ESP_FS_WS_SETUP\n    if (m_setupWebSocket)\n      delete m_setupWebSocket;\n#endif\n    if (m_dnsServer)\n      delete m_dnsServer;\n#if ESP_FS_WS_SETUP\n    if (setup)\n      delete setup; // Only delete if it was lazily initialized\n#endif\n  }\n\n  inline void run() {\n    this->handleClient();\n    // Handle websocket events\n#if ESP_FS_WS_WEBSOCKET\n    if (m_websocket)\n      m_websocket->loop();\n#endif\n#if ESP_FS_WS_SETUP\n    if (m_setupWebSocket)\n      m_setupWebSocket->loop();\n#endif\n    // Handle DNS requests\n#if ESP_FS_WS_MDNS\n    if (m_dnsServer)\n      m_dnsServer->processNextRequest();\n\n    // ESP8266 mDNS requires periodic update calls while running\n    #if !defined(ESP32)\n    MDNS.update();\n    #endif\n#endif\n\n#if ESP_FS_WS_SETUP\n    if (m_pendingSetupWifiConnect) {\n      processPendingSetupWifiConnection();\n    }\n\n    if (m_releaseSetupWebSocketPending) {\n      releaseSetupWebSocketIfIdle();\n    }\n\n    if (m_pendingSetupRestartAt != 0 && millis() >= m_pendingSetupRestartAt) {\n      m_pendingSetupRestartAt = 0;\n      ESP.restart();\n    }\n#endif\n}\n\n  /*\n    Redirect to a given URL\n  */\n  void redirect(const char *url);\n\n  /*\n    Get the webserver IP address\n  */\n  inline IPAddress getServerIP() { return m_serverIp; }\n  /*\n    Return true if the device is currently running in Access Point mode\n  */\n  inline bool isAccessPointMode() const { return m_isApMode; }\n\n  /*\n    Start webserver and bind a websocket event handler (optional)\n  */\n  using WebServerClass::begin;\n  virtual void\n  begin(WebSocketsServerCore::WebSocketServerEvent wsEventHandler = nullptr);\n\n#if ESP_FS_WS_EDIT\n\n  /*\n    Enable the built-in ACE web file editor\n  */\n  void enableFsCodeEditor();\n\n  // Backward compatibility method\n  [[deprecated(\"Use enableFsCodeEditor() instead (use built-in callback to provide FS info).\")]]\n  void enableFsCodeEditor(FsInfoCallbackF fsCallback) {\n    if (fsCallback)\n      getFsInfo = fsCallback;\n    enableFsCodeEditor();\n  }\n\n  /*\n   * Set callback function to provide updated FS info to library\n   * This it is necessary due to the different implementation of\n   * libraries for the filesystem (LittleFS, FFat, SPIFFS etc etc)\n   */\n  [[deprecated(\"Use enableFsCodeEditor() instead (use built-in callback to provide FS info)\")]]\n  inline void setFsInfoCallback(FsInfoCallbackF fsCallback) {\n    getFsInfo = fsCallback;\n  }\n#endif\n\n  /*\n    Enable authenticate for /setup webpage\n  */\n  void setAuthentication(const char *user, const char *pswd);\n\n  /*\n  Enable the flag which turns on basic authentication for all pages\n  */\n  inline void requireAuthentication(bool require) { m_authAll = require; }\n\n  /*\n    List FS content\n  */\n  void printFileList(fs::FS &fs, const char *dirname, uint8_t levels);\n\n  /*\n    List FS content to a destination stream (e.g. Serial, WiFiClient)\n  */\n  void printFileList(fs::FS &fs, const char *dirname, uint8_t levels, Print &out);\n\n  /*\n      Old API for backward compatibility\n  */\n  void printFileList(fs::FS &fs, Print &out, const char *dirname,  uint8_t levels) {\n    printFileList(fs, dirname, levels, out);\n  }\n  /*\n    Send a default \"OK\" reply to client\n  */\n  void sendOK();\n\n  /*\n  Set wifi event callback to handle captive portal fallback on wifi disconnect    \n  */\n  #if defined(ESP32) || defined(ESP8266)\n  inline void setWiFiConnectionCallbacks(WiFiConnectedCallbackF connectedCallback, WiFiDisconnectedCallbackF disconnectedCallback) {\n    WiFiService::setWiFiConnectionCallbacks(connectedCallback, disconnectedCallback);          \n  }\n  #endif\n\n  /*\n    Start WiFi connection, callback function is called when trying to connect\n  */\n  bool startWiFi(uint32_t timeout, CallbackF fn = nullptr);\n\n  /*\n   * Redirect to captive portal if we got a request for another domain.\n   */\n  bool startCaptivePortal(const char *ssid, const char *pass, const char *redirectTargetURL = \"/setup\");\n  bool startCaptivePortal(WiFiConnectParams& params, const char *redirectTargetURL = \"/setup\"); \n\n  /*\n    Set AP SSID and Password (backward compatibility)\n  */\n  void setAP(const char *ssid, const char *pass) {\n    strlcpy(m_apSSID, ssid, sizeof(m_apSSID));\n    strlcpy(m_apPassword, pass, sizeof(m_apPassword));\n  }\n\n  /*\n   * Setup and start mDNS responder\n   */\n  bool startMDNSResponder();\n\n  /*\n   * Need to be run in loop to handle DNS requests\n   */\n  inline void updateDNS() { m_dnsServer->processNextRequest(); }\n\n  /*\n   * Set current firmware version (shown in /setup webpage)\n   */\n  inline void setFirmwareVersion(const char *version) {\n    m_version = String(version);\n  }\n\n  inline void setFirmwareVersion(const String &version) { m_version = version; }\n\n  /*\n   * Set hostmane\n   */\n  inline void setHostname(const char *host) { m_host = host; }\n\n  /////////////////////////////////////////////////////////////////////////////////////////////////\n  ////////////////////////////////////   WEBSOCKET  ///////////////////////////////////////////////\n  /////////////////////////////////////////////////////////////////////////////////////////////////\n#if ESP_FS_WS_WEBSOCKET\n  /*\n    Enable built-in websocket server. Events like connect/disconnect or\n    messages can be handled using callback function\n  */\n\n  inline WebSocketsServer *getWebSocketServer() { return m_websocket; }\n\n  inline bool broadcastWebSocket(const String &payload) {\n    if (m_websocket)\n      return m_websocket->broadcastTXT(payload.c_str());\n    return false;\n  }\n\n  inline bool broadcastWebSocket(const uint8_t *payload, size_t length) {\n    if (m_websocket)\n      return m_websocket->broadcastBIN(payload, length);\n    return false;\n  }\n\n  inline bool sendWebSocket(uint8_t num, const String &payload) {\n    if (m_websocket)\n      return m_websocket->sendTXT(num, payload.c_str());\n    return false;\n  }\n#endif\n\n#if ESP_FS_WS_SETUP\n  /////////////////////////////////////////////////////////////////////////////////////////////////\n  ////////////////////////////   SETUP PAGE CONFIGURATION /////////////////////////////////////////\n  /////////////////////////////////////////////////////////////////////////////////////////////////\n\n  // Public alias to dropdown definition type (available only when /setup is\n  // enabled)\n  using DropdownList = SetupConfig::DropdownList;\n  // Public alias to slider definition type\n  using Slider = SetupConfig::Slider;\n\n  /*\n   * Set callback function to be called when configuration file is saved via\n   * /edit POST The callback receives the filename path as parameter\n   */\n  inline void setConfigSavedCallback(ConfigSavedCallbackF callback) {\n    m_configSavedCallback = callback;\n  }\n\n  /*\n   * Get reference to current config.json file\n   */\n  inline File getConfigFile(const char *mode) {\n    File file = m_filesystem->open(ESP_FS_WS_CONFIG_FILE, mode);\n    return file;\n  }\n\n  /*\n   * Clear the saved configuration options by removing config.json.\n   * Returns true if the file was removed or did not exist.\n   */\n  inline bool clearConfigFile() {\n    if (m_filesystem->exists(ESP_FS_WS_CONFIG_FILE)) {\n      return m_filesystem->remove(ESP_FS_WS_CONFIG_FILE);\n    }\n    return true;\n  }\n\n  inline bool clearAll() {\n    m_credentialManager->clearAll();\n    if (m_filesystem->exists(ESP_FS_WS_CONFIG_FILE)) {\n      return m_filesystem->remove(ESP_FS_WS_CONFIG_FILE);\n    }\n    return false;\n  }\n\n  /*\n   * Get complete path of config.json file\n   */\n  inline const char *getConfiFileName() { return ESP_FS_WS_CONFIG_FILE; }\n\n  void setSetupPageTitle(const char *title) {\n    getSetupConfigurator()->setSetupPageTitle(title);\n  }\n  void addHTML(const char *html, const char *id, bool ow = false) {\n    getSetupConfigurator()->addHTML(html, id, ow);\n  }\n  void addCSS(const char *css, const char *id, bool ow = false) {\n    getSetupConfigurator()->addCSS(css, id, ow);\n  }\n  void addJavascript(const char *script, const char *id, bool ow = false) {\n    getSetupConfigurator()->addJavascript(script, id, ow);\n  }\n  void addDropdownList(const char *lbl, const char **a, size_t size) {\n    getSetupConfigurator()->addDropdownList(lbl, a, size);\n  }\n  void addDropdownList(DropdownList &def) {\n    getSetupConfigurator()->addDropdownList(def);\n  }\n  void addSlider(Slider &def) { getSetupConfigurator()->addSlider(def); }\n  void addOptionBox(const char *title) {\n    getSetupConfigurator()->addOptionBox(title);\n  }\n  void setSetupPageLogo(const uint8_t* imageData, size_t imageSize, const char* mimeType = \"image/png\", bool ow = false) {\n    getSetupConfigurator()->setSetupPageLogo(imageData, imageSize, mimeType, ow);\n  }\n  void setSetupPageLogo(const char* svgText, bool ow = false) {\n    getSetupConfigurator()->setSetupPageLogo(svgText, ow);\n  }\n  \n  // attach a comment string to an existing option element\n  void addComment(const char *lbl, const char *comment) { getSetupConfigurator()->addComment(lbl, comment); }\n\n  // boolean option overload with per-option grouping control\n  void addOption(const char *lbl, bool val, bool hidden = false, bool grouped = true) {\n    getSetupConfigurator()->addOption(lbl, val, hidden, grouped);\n  }\n  // bool variant with comment\n  void addOption(const char *lbl, bool val, const char *comment,\n                 bool hidden = false, bool grouped = false) {\n    getSetupConfigurator()->addOption(lbl, val, hidden, grouped);\n    addComment(lbl, comment);\n  }\n\n  template <typename T>\n  void addOption(const char *lbl, T val, double min, double max, double st) {\n    getSetupConfigurator()->addOption(lbl, val, false, min, max, st);\n  }\n  template <typename T>\n  void addOption(const char *lbl, T val, bool hidden = false, double min = MIN_F, double max = MAX_F, double st = 1.0) {\n    getSetupConfigurator()->addOption(lbl, val, hidden, min, max, st);\n  }\n  // generic comment overload (excluding bool)\n  template <typename T, typename std::enable_if<!std::is_same<T,bool>::value, int>::type = 0>\n  void addOption(const char *lbl, T val, const char *comment) {\n    getSetupConfigurator()->addOption(lbl, val, false, MIN_F, MAX_F, 1.0);\n    addComment(lbl, comment);\n  }\n  template <typename T> bool getOptionValue(const char *lbl, T &var) {\n    return getSetupConfigurator()->getOptionValue(lbl, var);\n  }\n  template <typename T> bool saveOptionValue(const char *lbl, T val) {\n    return getSetupConfigurator()->saveOptionValue(lbl, val);\n  }\n\n  // Update a dropdown definition's selectedIndex from persisted config\n  bool getDropdownSelection(DropdownList &def) {\n    return getSetupConfigurator()->getDropdownSelection(def);\n  }\n  // Read slider value back into struct\n  bool getSliderValue(Slider &def) {\n    return getSetupConfigurator()->getSliderValue(def);\n  }\n\n  void closeSetupConfiguration() {\n    getSetupConfigurator()->closeConfiguration();\n  }\n  /////////////////////////////////////////////////////////////////////////////////////////////////\n#endif\n};\n\n#endif\n"
  },
  {
    "path": "src/Json.cpp",
    "content": "#include \"Json.h\"\n#include <cstring>\n#include <stdlib.h>\n\nusing namespace CJSON;\n\nJson::Json() : root(nullptr) {}\nJson::~Json()\n{\n    if (root)\n        cJSON_Delete(root);\n}\n\nbool Json::parse(const String &text)\n{\n    if (root)\n        cJSON_Delete(root);\n    root = cJSON_Parse(text.c_str());\n    return root != nullptr;\n}\n\nstatic void jsonEscapeString(const char* in, String& out) {\n    if (!in) { out += \"\"; return; }\n    out.reserve(out.length() + strlen(in) + 4);\n    for (const char* p = in; *p; ++p) {\n        char c = *p;\n        switch (c) {\n            case '\"': out += \"\\\\\\\"\"; break;\n            case '\\\\': out += \"\\\\\\\\\"; break;\n            case '\\b': out += \"\\\\b\"; break;\n            case '\\f': out += \"\\\\f\"; break;\n            case '\\n': out += \"\\\\n\"; break;\n            case '\\r': out += \"\\\\r\"; break;\n            case '\\t': out += \"\\\\t\"; break;\n            default:\n                if ((unsigned char)c < 0x20) {\n                    // Control chars -> skip or encode minimally\n                    // Minimal approach: skip\n                } else {\n                    out += c;\n                }\n        }\n    }\n}\n\nstatic void serializeNode(const cJSON* item, String& out, bool pretty, int indent);\n\nstatic void addIndent(String& out, int indent) {\n    for (int i = 0; i < indent; i++) out += \"  \";\n}\n\nstatic void serializeArray(const cJSON* array, String& out, bool pretty, int indent) {\n    out.reserve(out.length() + 64);\n    out += '[';\n    const cJSON* child = array->child;\n    bool first = true;\n    if (pretty && child) out += '\\n';\n    \n    while (child) {\n        if (!first) {\n            out += ',';\n            if (pretty) out += '\\n';\n        }\n        first = false;\n        if (pretty) addIndent(out, indent + 1);\n        serializeNode(child, out, pretty, indent + 1);\n        child = child->next;\n    }\n    if (pretty && array->child) {\n        out += '\\n';\n        addIndent(out, indent);\n    }\n    out += ']';\n}\n\nstatic void serializeObject(const cJSON* obj, String& out, bool pretty, int indent) {\n    out.reserve(out.length() + 64);\n    out += '{';\n    const cJSON* child = obj->child;\n    bool first = true;\n    if (pretty && child) out += '\\n';\n\n    while (child) {\n        if (!first) {\n            out += ',';\n            if (pretty) out += '\\n';\n        }\n        first = false;\n        if (pretty) addIndent(out, indent + 1);\n        out += '\"';\n        jsonEscapeString(child->string, out);\n        out += '\"';\n        out += ':';\n        if (pretty) out += ' ';\n        serializeNode(child, out, pretty, indent + 1);\n        child = child->next;\n    }\n    if (pretty && obj->child) {\n        out += '\\n';\n        addIndent(out, indent);\n    }\n    out += '}';\n}\n\nstatic void serializeNumber(const cJSON* item, String& out) {\n    // Prefer integer when representable\n    double d = item->valuedouble;\n    // Use valueint if it matches\n    if ((double)item->valueint == d) {\n        out += String(item->valueint);\n        return;\n    }\n    // Fallback: limited precision to reduce code size\n    // Using String(double, digits) avoids heavy printf linkage\n    out += String(d, 6);\n}\n\nstatic void serializeNode(const cJSON* item, String& out, bool pretty, int indent) {\n    if (!item) { out += \"null\"; return; }\n    switch (item->type & 0xFF) {\n        case cJSON_False: out += \"false\"; break;\n        case cJSON_True: out += \"true\"; break;\n        case cJSON_NULL: out += \"null\"; break;\n        case cJSON_Number: serializeNumber(item, out); break;\n        case cJSON_String:\n            out += '\"';\n            jsonEscapeString(item->valuestring, out);\n            out += '\"';\n            break;\n        case cJSON_Array: serializeArray(item, out, pretty, indent); break;\n        case cJSON_Object: serializeObject(item, out, pretty, indent); break;\n        default: out += \"null\"; break;\n    }\n}\n\nString Json::serialize(bool pretty) const\n{\n    if (!root)\n        return String();\n    String s;\n    s.reserve(256);\n    serializeNode(root, s, pretty, 0);\n    return s;\n}\n\n// --------- Construction helpers for nested structures ---------\nbool Json::createObject()\n{\n    if (root) cJSON_Delete(root);\n    root = cJSON_CreateObject();\n    return root != nullptr;\n}\n\nbool Json::createArray()\n{\n    if (root) cJSON_Delete(root);\n    root = cJSON_CreateArray();\n    return root != nullptr;\n}\n\nbool Json::add(const Json& child)\n{\n    if (!root || !cJSON_IsArray(root)) return false;\n    // Deep copy child root into this array\n    cJSON* copy = nullptr;\n    if (child.root) {\n        copy = cJSON_Duplicate(child.root, /*recurse*/1);\n    } else {\n        copy = cJSON_CreateNull();\n    }\n    if (!copy) return false;\n    cJSON_AddItemToArray(root, copy);\n    return true;\n}\n\nbool Json::set(const String& key, const Json& child)\n{\n    if (!root || !cJSON_IsObject(root)) return false;\n    cJSON_DeleteItemFromObjectCaseSensitive(root, key.c_str());\n    cJSON* copy = nullptr;\n    if (child.root) {\n        copy = cJSON_Duplicate(child.root, /*recurse*/1);\n    } else {\n        copy = cJSON_CreateNull();\n    }\n    if (!copy) return false;\n    cJSON_AddItemToObject(root, key.c_str(), copy);\n    return true;\n}\n\nbool Json::hasObject(const String &key) const\n{\n    if (!root)\n        return false;\n    cJSON *obj = cJSON_GetObjectItemCaseSensitive(root, key.c_str());\n    return obj && cJSON_IsObject(obj);\n}\n\nvoid Json::ensureObject(const String &key)\n{\n    if (!root)\n        root = cJSON_CreateObject();\n    cJSON *obj = cJSON_GetObjectItemCaseSensitive(root, key.c_str());\n    if (!(obj && cJSON_IsObject(obj)))\n    {\n        cJSON *n = cJSON_CreateObject();\n        cJSON_AddItemToObject(root, key.c_str(), n);\n    }\n}\n\n// --------- Top-level helpers ---------\n\nbool Json::hasKey(const String &key) const\n{\n    if (!root) return false;\n    cJSON *item = cJSON_GetObjectItemCaseSensitive(root, key.c_str());\n    return item != nullptr;\n}\n\n\nbool Json::hasKey(const String &objName, const String &key) const\n{\n    if (!root)\n        return false;\n    cJSON *scope = cJSON_GetObjectItemCaseSensitive(root, objName.c_str());\n    if (!scope)\n        return false;\n    cJSON *item = cJSON_GetObjectItemCaseSensitive(scope, key.c_str());\n    return item != nullptr;\n}\n\nbool Json::setString(const String &key, const String &value)\n{\n    if (!root) root = cJSON_CreateObject();\n    cJSON_DeleteItemFromObjectCaseSensitive(root, key.c_str());\n    cJSON_AddItemToObject(root, key.c_str(), cJSON_CreateString(value.c_str()));\n    return true;\n}\n\nbool Json::setNumber(const String &key, double value)\n{\n    if (!root) root = cJSON_CreateObject();\n    cJSON_DeleteItemFromObjectCaseSensitive(root, key.c_str());\n    cJSON_AddItemToObject(root, key.c_str(), cJSON_CreateNumber(value));\n    return true;\n}\n\nbool Json::setBool(const String &key, bool value)\n{\n    if (!root) root = cJSON_CreateObject();\n    cJSON_DeleteItemFromObjectCaseSensitive(root, key.c_str());\n    cJSON_AddItemToObject(root, key.c_str(), cJSON_CreateBool(value));\n    return true;\n}\n\nbool Json::setArray(const String &key, const std::vector<String> &values)\n{\n    if (!root) root = cJSON_CreateObject();\n    cJSON *arr = cJSON_CreateArray();\n    for (auto &v : values) cJSON_AddItemToArray(arr, cJSON_CreateString(v.c_str()));\n    cJSON_DeleteItemFromObjectCaseSensitive(root, key.c_str());\n    cJSON_AddItemToObject(root, key.c_str(), arr);\n    return true;\n}\n\n\nbool Json::setString(const String &objName, const String &key, const String &value)\n{\n    ensureObject(objName);\n    cJSON *target = cJSON_GetObjectItemCaseSensitive(root, objName.c_str());\n    if (!target)\n        return false;\n    cJSON_DeleteItemFromObjectCaseSensitive(target, key.c_str());\n    cJSON_AddItemToObject(target, key.c_str(), cJSON_CreateString(value.c_str()));\n    return true;\n}\n\nbool Json::setNumber(const String &objName, const String &key, double value)\n{\n    ensureObject(objName);\n    cJSON *target = cJSON_GetObjectItemCaseSensitive(root, objName.c_str());\n    if (!target)\n        return false;\n    cJSON_DeleteItemFromObjectCaseSensitive(target, key.c_str());\n    cJSON_AddItemToObject(target, key.c_str(), cJSON_CreateNumber(value));\n    return true;\n}\n\nbool Json::setBool(const String &objName, const String &key, bool value)\n{\n    ensureObject(objName);\n    cJSON *target = cJSON_GetObjectItemCaseSensitive(root, objName.c_str());\n    if (!target)\n        return false;\n    cJSON_DeleteItemFromObjectCaseSensitive(target, key.c_str());\n    cJSON_AddItemToObject(target, key.c_str(), cJSON_CreateBool(value));\n    return true;\n}\n\nbool Json::setArray(const String &objName, const String &key, const std::vector<String> &values)\n{\n    ensureObject(objName);\n    cJSON *target = cJSON_GetObjectItemCaseSensitive(root, objName.c_str());\n    if (!target)\n        return false;\n    cJSON *arr = cJSON_CreateArray();\n    for (auto &v : values)\n        cJSON_AddItemToArray(arr, cJSON_CreateString(v.c_str()));\n    cJSON_DeleteItemFromObjectCaseSensitive(target, key.c_str());\n    cJSON_AddItemToObject(target, key.c_str(), arr);\n    return true;\n}\n\nbool Json::getString(const String &key, String &out) const\n{\n    if (!root) return false;\n    cJSON *item = cJSON_GetObjectItemCaseSensitive(root, key.c_str());\n    if (!item || !cJSON_IsString(item)) return false;\n    out = item->valuestring ? String(item->valuestring) : String();\n    return true;\n}\n\nbool Json::getBool(const String& key, bool& out) const {\n    if (!root) return false;\n    cJSON *item = cJSON_GetObjectItemCaseSensitive(root, key.c_str());\n    if (!item) return false;\n    if (cJSON_IsBool(item)) {\n        out = cJSON_IsTrue(item);\n        return true;\n    }\n    if (cJSON_IsNumber(item)) {\n        out = (item->valuedouble != 0.0);\n        return true;\n    }\n    return false;\n}\n\nbool Json::getNumber(const String &key, double &out) const\n{\n    if (!root) return false;\n    cJSON *item = cJSON_GetObjectItemCaseSensitive(root, key.c_str());\n    if (!item) return false;\n    if (cJSON_IsNumber(item)) {\n        out = item->valuedouble;\n        return true;\n    }\n    if (cJSON_IsString(item) && item->valuestring) {\n        out = atof(item->valuestring);\n        return true;\n    }\n    return false;\n}\n\n\n// Object-scoped key helpers\nbool Json::getString(const String &objName, const String &key, String &out) const\n{\n    if (!root)\n        return false;\n    cJSON *scope = cJSON_GetObjectItemCaseSensitive(root, objName.c_str());\n    if (!scope)\n        return false;\n    cJSON *item = cJSON_GetObjectItemCaseSensitive(scope, key.c_str());\n    if (!item || !cJSON_IsString(item))\n        return false;\n    out = item->valuestring ? String(item->valuestring) : String();\n    return true;\n}\n\nbool Json::getNumber(const String &objName, const String &key, double &out) const\n{\n    if (!root)\n        return false;\n    cJSON *scope = cJSON_GetObjectItemCaseSensitive(root, objName.c_str());\n    if (!scope)\n        return false;\n    cJSON *item = cJSON_GetObjectItemCaseSensitive(scope, key.c_str());\n    if (!item)\n        return false;\n    out = item->valuedouble;\n    return true;\n}\n\nbool Json::getBool(const String &objName, const String &key, bool &out) const\n{\n    if (!root)\n        return false;\n    cJSON *scope = cJSON_GetObjectItemCaseSensitive(root, objName.c_str());\n    if (!scope)\n        return false;\n    cJSON *item = cJSON_GetObjectItemCaseSensitive(scope, key.c_str());\n    if (!item)\n        return false;\n    if (cJSON_IsBool(item)) {\n        out = cJSON_IsTrue(item);\n        return true;\n    }\n    if (cJSON_IsNumber(item)) {\n        out = (item->valuedouble != 0.0);\n        return true;\n    }\n    return false;\n}\n\n\n"
  },
  {
    "path": "src/Json.h",
    "content": "#pragma once\n#include <Arduino.h>\n#include <vector>\n#include <stdint.h>\nextern \"C\" {\n#include \"json/cJSON.h\"\n}\n\nnamespace CJSON {\nclass Json {\npublic:\n    Json();\n    ~Json();\n\n    bool parse(const String& text);\n    String serialize(bool pretty=false) const;\n\n    // Construction helpers for nested structures\n    // Initialize the root as an empty object or array\n    bool createObject();\n    bool createArray();\n    // Append a child to the root array\n    bool add(const Json& child);\n    // Set a nested child under a key in the root object\n    bool set(const String& key, const Json& child);\n\n    bool hasObject(const String& key) const;\n    void ensureObject(const String& key);\n\n    // Top-level key helpers\n    bool hasKey(const String& key) const;\n    bool setString(const String& key, const String& value);\n    bool setNumber(const String& key, double value);\n    bool setBool(const String& key, bool value);\n    bool setArray(const String& key, const std::vector<String>& values);\n    bool getString(const String& key, String& out) const;\n    bool getBool(const String& key, bool& out) const;\n    bool getNumber(const String& key, double& out) const;\n\n    // Object-scoped key helpers\n    bool hasKey(const String& obj, const String& key) const;\n    bool setString(const String& obj, const String& key, const String& value);\n    bool setNumber(const String& obj, const String& key, double value);\n    bool setBool(const String& obj, const String& key, bool value);\n    bool setArray(const String& obj, const String& key, const std::vector<String>& values);\n    bool getString(const String& obj, const String& key, String& out) const;\n    bool getBool(const String& obj, const String& key, bool& out) const;\n    bool getNumber(const String& obj, const String& key, double& out) const;\n\n    // Low-level accessor: expose underlying cJSON root for advanced operations\n    // (e.g. iterating arrays/objects in higher-level helpers).\n    // Caller must NOT free or modify the returned pointer directly.\n    cJSON* getRoot() { return root; }\n    const cJSON* getRoot() const { return root; }\n\nprivate:\n    cJSON* root;\n};\n}\n"
  },
  {
    "path": "src/SerialLog.h",
    "content": "#ifndef __SERIALLOG_H__\n#define __SERIALLOG_H__\n\n#include <stdio.h>\n#include <string.h>\n\n#ifdef __cplusplus\nextern \"C\"\n{\n#endif\n\n#define DBG_OUTPUT_PORT     Serial\n// Allow external override; default to verbose debug if not provided\n#ifndef LOG_LEVEL\n#define LOG_LEVEL           1    // (0 disable, 1 error, 2 info, 3 debug)\n#endif\n\n#define __SOURCE_FILE_NAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : (strrchr(__FILE__, '\\\\') ? strrchr(__FILE__, '\\\\') + 1 : __FILE__))\n#define _LOG_FORMAT(letter, format)  \"\\n[\"#letter\"][%s:%u] %s():\\t\" format, __SOURCE_FILE_NAME__, __LINE__, __FUNCTION__\n\n#if defined(LOG_LEVEL) && (LOG_LEVEL == 0)\n#define log_error(format, ...)\n#define log_info(format, ...)\n#define log_debug(format, ...)\n#endif\n\n#if defined(LOG_LEVEL) && (LOG_LEVEL > 0)\n#define log_error(format, ...) DBG_OUTPUT_PORT.printf(_LOG_FORMAT(E, format), ##__VA_ARGS__)\n#define log_info(format, ...)\n#define log_debug(format, ...)\n#endif\n\n#if defined(LOG_LEVEL) && (LOG_LEVEL > 1)\n#undef log_info\n#define log_info(format, ...) DBG_OUTPUT_PORT.printf(_LOG_FORMAT(I, format), ##__VA_ARGS__)\n#endif\n\n#if defined(LOG_LEVEL) && (LOG_LEVEL > 2)\n#undef log_debug\n#define log_debug(format, ...) DBG_OUTPUT_PORT.printf(_LOG_FORMAT(D, format), ##__VA_ARGS__)\n#endif\n\n\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif"
  },
  {
    "path": "src/SetupConfig.hpp",
    "content": "#ifndef CONFIGURATOR_HPP\n#define CONFIGURATOR_HPP\n#include <type_traits>\n#include <FS.h>\n#if defined(ESP8266)\n#include <pgmspace.h>\n#endif\n\n#include \"Json.h\"\n#include \"SerialLog.h\"\n#include \"ConfigUpgrader.hpp\"\n\n#define MIN_F -3.4028235E+38\n#define MAX_F 3.4028235E+38\n\n// Public dropdown definition type, available only when /setup is enabled\nnamespace SetupConfig {\n    struct DropdownList {\n        const char* label;                 // JSON key / UI label id\n        const char* const* values;         // Static array of values (null-terminated strings)\n        size_t size;                       // Number of items in values\n        size_t selectedIndex;              // Currently selected item index\n    };\n\n    struct Slider {\n        const char* label;                 // JSON key / UI label id\n        double min;                        // Minimum value\n        double max;                        // Maximum value\n        double step;                       // Step increment\n        double value;                      // Current value\n    };\n}\n\nclass SetupConfigurator\n{\n    protected:\n        uint8_t numOptions = 0;\n        fs::FS* m_filesystem = nullptr;\n        CJSON::Json* m_doc = nullptr;\n        CJSON::Json* m_savedDoc = nullptr;  // Temporary storage for saved file values\n\n        // Builders for v2 hierarchical schema (sections / elements)\n        CJSON::Json m_sectionsArray;        // Root array of sections for current session\n        CJSON::Json m_currentSection;       // Currently open section\n        CJSON::Json m_currentElements;      // Elements array for current section\n        bool m_hasCurrentSection = false;   // True if a section is open\n\n        // (previously grouping settings were global; now per-option)\n\n        uint16_t& m_port;         \n        String& m_host;\n        bool m_opened = false;\n\n        uint8_t readBinaryByte(const uint8_t* data, size_t offset) const {\n#if defined(ESP8266)\n            return pgm_read_byte(data + offset);\n#else\n            return data[offset];\n#endif\n        }\n\n        bool writeBinaryFile(File& file, const uint8_t* data, size_t len) {\n#if defined(ESP8266)\n            uint8_t chunk[64];\n            size_t totalWritten = 0;\n            while (totalWritten < len) {\n                size_t chunkLen = len - totalWritten;\n                if (chunkLen > sizeof(chunk)) {\n                    chunkLen = sizeof(chunk);\n                }\n\n                for (size_t i = 0; i < chunkLen; ++i) {\n                    chunk[i] = readBinaryByte(data, totalWritten + i);\n                }\n\n                size_t written = file.write(chunk, chunkLen);\n                totalWritten += written;\n                if (written != chunkLen) {\n                    return false;\n                }\n            }\n            return true;\n#else\n            return file.write(data, len) == len;\n#endif\n        }\n\n        // --------- Helpers for v2 hierarchical schema ---------\n\n        // Ensure there is an open section to which options can be added\n        void ensureActiveSection() {\n            if (m_hasCurrentSection) return;\n            // Default section when user doesn't call addOptionBox explicitly\n            m_currentSection.createObject();\n            m_currentSection.setString(\"title\", \"General Options\");\n            m_currentElements.createArray();\n            m_hasCurrentSection = true;\n        }\n\n        // Start a new named section (used by addOptionBox)\n        void startNewSection(const char* title) {\n            // Finalize previous section first\n            if (m_hasCurrentSection) {\n                m_currentSection.set(\"elements\", m_currentElements);\n                m_sectionsArray.add(m_currentSection);\n                m_currentSection.createObject();\n                m_currentElements.createArray();\n            }\n            String t = String(title);\n            m_currentSection.setString(\"title\", t);\n            m_hasCurrentSection = true;\n        }\n\n        // Finalize any open section and attach sections array to root document\n        void finalizeSectionsToRoot() {\n            if (m_doc == nullptr) return;\n\n            cJSON* root = m_doc->getRoot();\n            cJSON* existingSections = root ? cJSON_GetObjectItemCaseSensitive(root, \"sections\") : nullptr;\n\n            if (m_hasCurrentSection) {\n                m_currentSection.set(\"elements\", m_currentElements);\n                m_sectionsArray.add(m_currentSection);\n                m_hasCurrentSection = false;\n            }\n\n            cJSON* builtSections = m_sectionsArray.getRoot();\n            const bool hasBuiltSections = builtSections && cJSON_IsArray(builtSections) && builtSections->child != nullptr;\n\n            if (!hasBuiltSections && existingSections != nullptr) {\n                return;\n            }\n\n            m_doc->set(\"sections\", m_sectionsArray);\n        }\n\n        cJSON* findElementByLabel(cJSON* root, const char *label) {\n            if (!root || label == nullptr) return nullptr;\n\n            cJSON* sections = cJSON_GetObjectItemCaseSensitive(root, \"sections\");\n            if (!sections || !cJSON_IsArray(sections)) return nullptr;\n\n            for (cJSON* sec = sections->child; sec; sec = sec->next) {\n                cJSON* elems = cJSON_GetObjectItemCaseSensitive(sec, \"elements\");\n                if (!elems || !cJSON_IsArray(elems)) continue;\n\n                for (cJSON* el = elems->child; el; el = el->next) {\n                    cJSON* lblNode = cJSON_GetObjectItemCaseSensitive(el, \"label\");\n                    if (lblNode && cJSON_IsString(lblNode) && lblNode->valuestring && String(lblNode->valuestring).equals(label)) {\n                        return el;\n                    }\n                }\n            }\n\n            return nullptr;\n        }\n\n        bool adoptSavedConfigurationAsSessionDoc() {\n            if (m_savedDoc == nullptr) return false;\n\n            String savedContent = m_savedDoc->serialize(false);\n\n            if (m_doc != nullptr) {\n                delete m_doc;\n                m_doc = nullptr;\n            }\n\n            m_doc = new CJSON::Json();\n            if (!m_doc->parse(savedContent)) {\n                log_error(\"Failed to clone saved configuration into session document\");\n                delete m_doc;\n                m_doc = nullptr;\n                return false;\n            }\n\n            m_sectionsArray.createArray();\n            m_currentSection.createObject();\n            m_currentElements.createArray();\n            m_hasCurrentSection = false;\n            return true;\n        }\n\n        bool isOpened() {\n            return m_opened;\n        }\n\n        bool openConfiguration() {\n            if (checkConfigFile()) {\n                // Check if config needs upgrade from v1 to v2\n                upgradeConfigIfNeeded();\n                \n                // Read existing file into m_savedDoc (background copy for value lookup)\n                if (m_filesystem->exists(ESP_FS_WS_CONFIG_FILE)) {\n                    File file = m_filesystem->open(ESP_FS_WS_CONFIG_FILE, \"r\");\n                    if (file) {\n                        String content = file.readString();\n                        file.close();\n                        \n                        m_savedDoc = new CJSON::Json();\n                        if (!m_savedDoc->parse(content)) {\n                            log_error(\"Failed to parse existing configuration\");\n                            delete m_savedDoc;\n                            m_savedDoc = nullptr;\n                            // Don't continue if parsing fails\n                            return false;\n                        }\n                    }\n                }\n                \n                // Create fresh v2 root document for this session\n                m_doc = new CJSON::Json();\n                m_doc->createObject();\n\n                // Version tag\n                m_doc->setString(\"_version\", \"2.0\");\n\n                // Metadata section\n                m_doc->ensureObject(\"_meta\");\n                String appTitle = \"Custom HTML Web Server\";\n                String logoPath = String(ESP_FS_WS_CONFIG_FOLDER) + \"/logo.svg\";\n                if (m_savedDoc) {\n                    String tmp;\n                    if (m_savedDoc->getString(\"_meta\", \"app_title\", tmp)) appTitle = tmp;\n                    if (m_savedDoc->getString(\"_meta\", \"logo\", tmp)) logoPath = tmp;\n                }\n                m_doc->setString(\"_meta\", \"app_title\", appTitle);\n                m_doc->setString(\"_meta\", \"logo\", logoPath);\n                m_doc->setNumber(\"_meta\", \"port\", static_cast<double>(m_port));\n                m_doc->setString(\"_meta\", \"host\", m_host);\n\n                // State section (object; can be extended externally if needed)\n                m_doc->ensureObject(\"_state\");\n\n                // Assets section: carry over existing lists when present\n                m_doc->ensureObject(\"_assets\");\n                std::vector<String> cssList;\n                std::vector<String> jsList;\n                std::vector<String> htmlList;\n                if (m_savedDoc) {\n                    auto copyArray = [](CJSON::Json* src, const char* obj, const char* key, std::vector<String>& out) {\n                        if (!src) return;\n                        const cJSON* root = src->getRoot();\n                        if (!root) return;\n                        const cJSON* scope = cJSON_GetObjectItemCaseSensitive(root, obj);\n                        if (!scope || !cJSON_IsObject(scope)) return;\n                        const cJSON* arr = cJSON_GetObjectItemCaseSensitive(scope, key);\n                        if (!arr || !cJSON_IsArray(arr)) return;\n                        for (const cJSON* it = arr->child; it; it = it->next) {\n                            if (cJSON_IsString(it) && it->valuestring) {\n                                out.emplace_back(String(it->valuestring));\n                            }\n                        }\n                    };\n                    copyArray(m_savedDoc, \"_assets\", \"css\", cssList);\n                    // Prefer new key \"js\" but also accept legacy \"javascript\" for backward compatibility\n                    copyArray(m_savedDoc, \"_assets\", \"js\", jsList);\n                    if (jsList.empty()) {\n                        copyArray(m_savedDoc, \"_assets\", \"javascript\", jsList);\n                    }\n                    copyArray(m_savedDoc, \"_assets\", \"html\", htmlList);\n                }\n                std::vector<String> empty;\n                m_doc->setArray(\"_assets\", \"css\", cssList.empty() ? empty : cssList);\n                m_doc->setArray(\"_assets\", \"js\", jsList.empty() ? empty : jsList);\n                m_doc->setArray(\"_assets\", \"html\", htmlList.empty() ? empty : htmlList);\n\n                // Initialize sections builder (will be attached to m_doc on close)\n                m_sectionsArray.createArray();\n                m_currentSection.createObject();\n                m_currentElements.createArray();\n                m_hasCurrentSection = false;\n                \n                m_opened = true;\n                return true;\n            }\n            return false;\n        }\n\n        /**\n         * @brief Check if config needs upgrade and perform it if necessary\n         * Uses ConfigUpgrader to migrate from v1 to v2 format\n         */\n        void upgradeConfigIfNeeded() {\n            if (m_filesystem == nullptr) return;\n            \n            ConfigUpgrader upgrader(m_filesystem, ESP_FS_WS_CONFIG_FILE);\n            if (!upgrader.upgrade()) {\n                log_debug(\"Config upgrade check completed\");\n            }\n        }\n\n        // If config file or folder doesn't exist, create them. If config file exists, do nothing.\n        // Returns true if config file is ready for use (exists or created successfully), false on failure.\n        // Some keys might be necessary for the setup page to work properly, so this function ensures that \n        // the config file exists and is initialized with a valid JSON object if it was missing.\n        bool checkConfigFile() {\n            File file = m_filesystem->open(ESP_FS_WS_CONFIG_FOLDER, \"r\");\n            if (!file) {\n                log_error(\"Failed to open /setup directory. Create new folder\\n\");\n                if (!m_filesystem->mkdir(ESP_FS_WS_CONFIG_FOLDER)) {\n                    log_error(\"Error. Folder %s not created\", ESP_FS_WS_CONFIG_FOLDER);\n                    return false;\n                }\n            }\n\n            // Check if config file exist, and create if necessary\n            if (!m_filesystem->exists(ESP_FS_WS_CONFIG_FILE)) {\n                file = m_filesystem->open(ESP_FS_WS_CONFIG_FILE, \"w\");\n                if (!file) {\n                    log_error(\"Error. File %s not created\", ESP_FS_WS_CONFIG_FILE);\n                    return false;\n                }\n                // Create pure v2 config (no legacy flat keys)\n                CJSON::Json initDoc;\n                initDoc.createObject();\n                initDoc.setString(\"_version\", \"2.0\");\n\n                // Metadata\n                initDoc.ensureObject(\"_meta\");\n                initDoc.setString(\"_meta\", \"app_title\", String(\"Custom HTML Web Server\"));\n                initDoc.setString(\"_meta\", \"logo\", String(ESP_FS_WS_CONFIG_FOLDER) + \"/logo.svg\");\n                initDoc.setNumber(\"_meta\", \"port\", static_cast<double>(m_port));\n                initDoc.setString(\"_meta\", \"host\", m_host);\n\n                // Empty _state object\n                initDoc.ensureObject(\"_state\");\n\n                // _assets with empty lists\n                initDoc.ensureObject(\"_assets\");\n                {\n                    std::vector<String> empty;\n                    initDoc.setArray(\"_assets\", \"css\", empty);\n                    initDoc.setArray(\"_assets\", \"js\", empty);\n                    initDoc.setArray(\"_assets\", \"html\", empty);\n                }\n\n                // Empty sections array (as real array node)\n                {\n                    CJSON::Json sections;\n                    sections.createArray();\n                    initDoc.set(\"sections\", sections);\n                }\n\n                String json = initDoc.serialize(true);\n                file.print(json);\n                file.close();\n            }\n            log_debug(\"Config file %s OK\", ESP_FS_WS_CONFIG_FILE);\n            return true;\n        }\n\n    public:\n        friend class FSWebServer;\n        friend class AsyncFsWebServer;\n        SetupConfigurator(fs::FS *fs, uint16_t& port, String& host) \n            : m_filesystem(fs), m_port(port), m_host(host) { ; }\n\n        bool closeConfiguration() {            \n\n            // If no options were added in this session, skip writing to avoid overwriting\n            if (numOptions == 0) {\n                log_debug(\"No options added; skipping config write\");\n                if (m_doc) { delete m_doc; m_doc = nullptr; }\n                if (m_savedDoc) { delete m_savedDoc; m_savedDoc = nullptr; }\n                return true;\n            }\n         \n            // Finalize sections into root _v2 schema\n            finalizeSectionsToRoot();\n\n            // Write configuration to file only if content has changed\n            // Serialize the new content\n            String newContent = m_doc->serialize(true);\n            \n            // Read existing file content\n            String oldContent;\n            if (m_filesystem->exists(ESP_FS_WS_CONFIG_FILE)) {\n                File readFile = m_filesystem->open(ESP_FS_WS_CONFIG_FILE, \"r\");\n                if (readFile) {\n                    oldContent = readFile.readString();\n                    readFile.close();\n                }\n            }\n            \n            // Write only if content is different\n            if (oldContent != newContent) {\n                File file = m_filesystem->open(ESP_FS_WS_CONFIG_FILE, \"w\");\n                if (file) {\n                    file.print(newContent);\n                    file.close();\n                    log_debug(\"Config file written (content changed)\");\n                } \n                else {\n                    log_error(\"Error opening config file for write\");\n                    delete (m_doc);\n                    m_doc = nullptr;\n                    if (m_savedDoc) { \n                        delete (m_savedDoc); \n                        m_savedDoc = nullptr; \n                    }\n                    m_opened = false;\n                    return false;\n                }\n            } \n            else {\n                log_debug(\"Config file unchanged, skipping write\");\n            }\n            \n            delete (m_doc);\n            m_doc = nullptr;\n            if (m_savedDoc) { \n                delete (m_savedDoc); \n                m_savedDoc = nullptr; \n            }\n\n            m_opened = false;\n            numOptions = 0;\n            return true;\n        }\n\n        // Save logo image from binary data (uint8_t array)\n        // Supports: PNG, JPEG, GIF, SVG (plain or gzipped)\n        // Automatically detects gzip compression (magic bytes 0x1f 0x8b)\n        void setSetupPageLogo(const uint8_t* imageData, size_t imageSize, const char* mimeType = \"image/png\", bool overwrite = false) {\n            // Ensure configuration document is open so we can update _meta\n            if (m_doc == nullptr) {\n                if (!openConfiguration()) {\n                    log_error(\"Error! /setup configuration not possible\");\n                    return;\n                }\n            }\n\n            // Determine file extension from MIME type\n            String extension = \".png\";\n            if (strcmp(mimeType, \"image/jpeg\") == 0 || strcmp(mimeType, \"image/jpg\") == 0) {\n                extension = \".jpg\";\n            } else if (strcmp(mimeType, \"image/gif\") == 0) {\n                extension = \".gif\";\n            } else if (strcmp(mimeType, \"image/svg+xml\") == 0) {\n                extension = \".svg\";\n            }\n            \n            String filename = ESP_FS_WS_CONFIG_FOLDER;\n            filename += \"/logo\";\n            filename += extension;\n            \n            // Auto-detect gzip compression by checking magic bytes\n            if (imageSize >= 2 && readBinaryByte(imageData, 0) == 0x1f && readBinaryByte(imageData, 1) == 0x8b) {\n                filename += \".gz\";\n            }\n            \n            // Save binary logo file\n            if (optionToFileBinary(filename.c_str(), imageData, imageSize, overwrite)) {\n                // Store path in _meta.logo instead of creating an \"img-logo\" element\n                m_doc->ensureObject(\"_meta\");\n                m_doc->setString(\"_meta\", \"logo\", filename);\n                // Mark configuration as changed so closeConfiguration() will persist it\n                numOptions++;\n            }\n        }\n\n        // Overload for string literals (e.g., SVG text)\n        void setSetupPageLogo(const char* svgText, bool overwrite = false) {\n            setSetupPageLogo((const uint8_t*)svgText, strlen(svgText), \"image/svg+xml\", overwrite);\n        }\n\n        // Set page title as metadata (_meta.app_title) instead of a normal option element\n        void setSetupPageTitle(const char* title) {\n            if (m_doc == nullptr) {\n                if (!openConfiguration()) {\n                    log_error(\"Error! /setup configuration not possible\");\n                    return;\n                }\n            }\n\n            m_doc->ensureObject(\"_meta\");\n            m_doc->setString(\"_meta\", \"app_title\", String(title));\n            // Mark configuration as changed so closeConfiguration() will persist it\n            numOptions++;\n        }\n\n        bool optionToFile(const char* filename, const char* str, bool overWrite) {\n            // Check if file is already saved\n            if (m_filesystem->exists(filename) && !overWrite) {\n                return true;\n            }\n            // Create or overwrite option file\n            else {\n                File file = m_filesystem->open(filename, \"w\");\n                if (file) {\n                    #if defined(ESP8266)\n                    String _str = str;\n                    file.print(_str);\n                    #else\n                    file.print(str);\n                    #endif\n                    file.close();\n                    log_debug(\"File %s saved\", filename);\n                    return true;\n                }\n                else {\n                    log_debug(\"Error writing file %s\", filename);\n                }\n            }\n            return false;\n        }\n\n        // Save binary data to file (e.g., pre-compressed gzip data)\n        bool optionToFileBinary(const char* filename, const uint8_t* data, size_t len, bool overWrite) {\n            if (m_filesystem->exists(filename) && !overWrite) {\n                return true;\n            }\n            File file = m_filesystem->open(filename, \"w\");\n            if (file) {\n                bool ok = writeBinaryFile(file, data, len);\n                file.close();\n                log_debug(\"Binary file %s saved (%d bytes)\", filename, ok ? len : 0);\n                return ok;\n            }\n            log_debug(\"Error writing binary file %s\", filename);\n            return false;\n        }\n\n        // Save binary data to file (for pre-compressed gzip data)\n        bool optionToFileGzip(const char* filename, const uint8_t* data, size_t len, bool overWrite) {\n            if (m_filesystem->exists(filename) && !overWrite) {\n                return true;\n            }\n            File file = m_filesystem->open(filename, \"w\");\n            if (file) {\n                bool ok = writeBinaryFile(file, data, len);\n                file.close();\n                log_debug(\"Binary file %s saved (%d bytes)\", filename, ok ? len : 0);\n                return ok;\n            }\n            log_debug(\"Error writing binary file %s\", filename);\n            return false;\n        }\n\n        void addSource(const String& source, const String& id, const String& extension, bool overWrite) {\n            if (m_doc == nullptr) {\n                if (!openConfiguration()) {\n                    log_error(\"Error! /setup configuration not possible\");\n                }\n            }\n\n            String path = ESP_FS_WS_CONFIG_FOLDER;\n            path += \"/\";\n            path += id;\n            path += extension;\n\n            bool isCss = extension.equals(\".css\");\n            bool isJs = extension.equals(\".js\");\n\n            if (optionToFile(path.c_str(), source.c_str(), overWrite)) {\n                // Register asset path inside _assets.{css,js,html} instead of flat raw-* keys\n                m_doc->ensureObject(\"_assets\");\n                cJSON* root = m_doc->getRoot();\n                if (!root) return;\n                cJSON* assets = cJSON_GetObjectItemCaseSensitive(root, \"_assets\");\n                if (!assets || !cJSON_IsObject(assets)) return;\n\n                const char* arrayKey = nullptr;\n                if (isCss) arrayKey = \"css\";\n                else if (isJs) arrayKey = \"js\";\n\n                if (arrayKey) {\n                    cJSON* arr = cJSON_GetObjectItemCaseSensitive(assets, arrayKey);\n                    if (!arr || !cJSON_IsArray(arr)) {\n                        arr = cJSON_CreateArray();\n                        cJSON_AddItemToObject(assets, arrayKey, arr);\n                    }\n\n                    // Avoid duplicates\n                    bool found = false;\n                    for (cJSON* it = arr->child; it; it = it->next) {\n                        if (cJSON_IsString(it) && it->valuestring && path.equals(String(it->valuestring))) {\n                            found = true;\n                            break;\n                        }\n                    }\n                    if (!found) {\n                        cJSON_AddItemToArray(arr, cJSON_CreateString(path.c_str()));\n                    }\n                }\n            }\n            else {\n                log_error(\"Source option not saved\");\n            }\n\n        }\n\n        void addHTML(const char* html, const char* id, bool overWrite) {\n            String path = String(ESP_FS_WS_CONFIG_FOLDER) + \"/\" + id + \".htm\";\n            optionToFile(path.c_str(), html, overWrite);\n\n            // Add HTML as an element in the current section\n            CJSON::Json elem;\n            elem.createObject();\n            elem.setString(\"type\", \"html\");\n            elem.setString(\"label\", \"\");\n            elem.setString(\"value\", path);\n            \n            m_currentElements.add(elem);\n            numOptions++;\n        }\n\n        void addCSS(const char* css,  const char* id, bool overWrite) {\n            String source = css;\n            addSource(source, id, \".css\", overWrite);\n        }\n\n        void addJavascript(const char* script,  const char* id, bool overWrite) {\n            String source = script;\n            addSource(source, id, \".js\", overWrite);\n        }\n\n        /*\n            Add a new dropdown input element\n        */\n        void addDropdownList(const char *label, const char** array, size_t size) {\n            if (m_doc == nullptr) {\n                if (!openConfiguration()) {\n                    log_error(\"Error! /setup configuration not possible\");\n                }\n            }\n\n            ensureActiveSection();\n\n            // Determine selected value: prefer saved, otherwise first item\n            String selectedValue = (size > 0) ? String(array[0]) : String(\"\");\n            if (m_savedDoc) {\n                const cJSON* root = m_savedDoc->getRoot();\n                if (root) {\n                    const cJSON* sections = cJSON_GetObjectItemCaseSensitive(root, \"sections\");\n                    if (sections && cJSON_IsArray(sections)) {\n                        const cJSON* sec = sections->child;\n                        while (sec) {\n                            const cJSON* elems = cJSON_GetObjectItemCaseSensitive(sec, \"elements\");\n                            if (elems && cJSON_IsArray(elems)) {\n                                const cJSON* el = elems->child;\n                                while (el) {\n                                    const cJSON* lbl = cJSON_GetObjectItemCaseSensitive(el, \"label\");\n                                    if (lbl && cJSON_IsString(lbl) && lbl->valuestring && String(lbl->valuestring).equals(label)) {\n                                        const cJSON* val = cJSON_GetObjectItemCaseSensitive(el, \"value\");\n                                        if (val && cJSON_IsString(val) && val->valuestring) {\n                                            selectedValue = String(val->valuestring);\n                                        }\n                                        break;\n                                    }\n                                    el = el->next;\n                                }\n                            }\n                            sec = sec->next;\n                        }\n                    }\n                }\n            }\n\n            CJSON::Json elem;\n            elem.createObject();\n            elem.setString(\"label\", label);\n            elem.setString(\"type\", \"select\");\n            elem.setString(\"value\", selectedValue);\n            std::vector<String> vals; vals.reserve(size);\n            for (size_t i = 0; i < size; i++) vals.emplace_back(String(array[i]));\n            elem.setArray(\"options\", vals);\n\n            m_currentElements.add(elem);\n            numOptions++;\n        }\n\n        /*\n            Add a new dropdown using a static definition that tracks current index\n        */\n        void addDropdownList(SetupConfig::DropdownList &def) {\n            if (m_doc == nullptr) {\n                if (!openConfiguration()) {\n                    log_error(\"Error! /setup configuration not possible\");\n                }\n            }\n\n            const char* label = def.label;\n            ensureActiveSection();\n\n            // Determine selected value: prefer saved, otherwise provided index, otherwise first\n            String selectedValue = (def.size > 0) ? String(def.values[(def.selectedIndex < def.size) ? def.selectedIndex : 0]) : String(\"\");\n            if (m_savedDoc) {\n                const cJSON* root = m_savedDoc->getRoot();\n                if (root) {\n                    const cJSON* sections = cJSON_GetObjectItemCaseSensitive(root, \"sections\");\n                    if (sections && cJSON_IsArray(sections)) {\n                        const cJSON* sec = sections->child;\n                        while (sec) {\n                            const cJSON* elems = cJSON_GetObjectItemCaseSensitive(sec, \"elements\");\n                            if (elems && cJSON_IsArray(elems)) {\n                                const cJSON* el = elems->child;\n                                while (el) {\n                                    const cJSON* lbl = cJSON_GetObjectItemCaseSensitive(el, \"label\");\n                                    if (lbl && cJSON_IsString(lbl) && lbl->valuestring && String(lbl->valuestring).equals(label)) {\n                                        const cJSON* val = cJSON_GetObjectItemCaseSensitive(el, \"value\");\n                                        if (val && cJSON_IsString(val) && val->valuestring) {\n                                            selectedValue = String(val->valuestring);\n                                        }\n                                        break;\n                                    }\n                                    el = el->next;\n                                }\n                            }\n                            sec = sec->next;\n                        }\n                    }\n                }\n            }\n\n            CJSON::Json elem;\n            elem.createObject();\n            elem.setString(\"label\", label);\n            elem.setString(\"type\", \"select\");\n            elem.setString(\"value\", selectedValue);\n            std::vector<String> vals; vals.reserve(def.size);\n            for (size_t i = 0; i < def.size; i++) { vals.emplace_back(String(def.values[i])); }\n            elem.setArray(\"options\", vals);\n\n            // Update def.selectedIndex from selectedValue\n            for (size_t i = 0; i < def.size; i++) {\n                if (selectedValue.equals(String(def.values[i]))) {\n                    def.selectedIndex = i;\n                    break;\n                }\n            }\n\n            m_currentElements.add(elem);\n            numOptions++;\n        }\n\n        /*\n            Update a dropdown definition's selectedIndex from persisted config\n            Returns true if a matching value was found\n        */\n        bool getDropdownSelection(SetupConfig::DropdownList &def) {\n            // Ensure we have a doc to read from\n            if (m_doc == nullptr && !openConfiguration()) {\n                log_error(\"Error! /setup configuration not possible\");\n                return false;\n            }\n\n            CJSON::Json* sourceDoc = (m_savedDoc != nullptr) ? m_savedDoc : m_doc;\n            if (sourceDoc == nullptr) {\n                log_error(\"No configuration document available for reading\");\n                return false;\n            }\n\n            const cJSON* root = sourceDoc->getRoot();\n            if (!root) return false;\n            const cJSON* sections = cJSON_GetObjectItemCaseSensitive(root, \"sections\");\n            if (!sections || !cJSON_IsArray(sections)) return false;\n\n            String sel;\n            const cJSON* sec = sections->child;\n            while (sec) {\n                const cJSON* elems = cJSON_GetObjectItemCaseSensitive(sec, \"elements\");\n                if (elems && cJSON_IsArray(elems)) {\n                    const cJSON* el = elems->child;\n                    while (el) {\n                        const cJSON* lbl = cJSON_GetObjectItemCaseSensitive(el, \"label\");\n                        if (lbl && cJSON_IsString(lbl) && lbl->valuestring && String(lbl->valuestring).equals(def.label)) {\n                            const cJSON* val = cJSON_GetObjectItemCaseSensitive(el, \"value\");\n                            if (val && cJSON_IsString(val) && val->valuestring) {\n                                sel = String(val->valuestring);\n                            }\n                            break;\n                        }\n                        el = el->next;\n                    }\n                }\n                sec = sec->next;\n            }\n\n            if (!sel.length()) return false;\n\n            for (size_t i = 0; i < def.size; i++) {\n                if (sel.equals(String(def.values[i]))) {\n                    def.selectedIndex = i;\n                    return true;\n                }\n            }\n            return false;\n        }\n\n        /*\n            Add a new slider using a static definition that tracks current value\n        */\n        void addSlider(SetupConfig::Slider &def) {\n            if (m_doc == nullptr) {\n                if (!openConfiguration()) {\n                    log_error(\"Error! /setup configuration not possible\");\n                }\n            }\n\n            const char* label = def.label;\n            ensureActiveSection();\n\n            // Prefer saved value when available; else use def.value\n            double current = def.value;\n            if (m_savedDoc) {\n                const cJSON* root = m_savedDoc->getRoot();\n                if (root) {\n                    const cJSON* sections = cJSON_GetObjectItemCaseSensitive(root, \"sections\");\n                    if (sections && cJSON_IsArray(sections)) {\n                        const cJSON* sec = sections->child;\n                        while (sec) {\n                            const cJSON* elems = cJSON_GetObjectItemCaseSensitive(sec, \"elements\");\n                            if (elems && cJSON_IsArray(elems)) {\n                                const cJSON* el = elems->child;\n                                while (el) {\n                                    const cJSON* lbl = cJSON_GetObjectItemCaseSensitive(el, \"label\");\n                                    if (lbl && cJSON_IsString(lbl) && lbl->valuestring && String(lbl->valuestring).equals(label)) {\n                                        const cJSON* val = cJSON_GetObjectItemCaseSensitive(el, \"value\");\n                                        if (val && cJSON_IsNumber(val)) {\n                                            current = val->valuedouble;\n                                        }\n                                        break;\n                                    }\n                                    el = el->next;\n                                }\n                            }\n                            sec = sec->next;\n                        }\n                    }\n                }\n            }\n\n            def.value = current;\n\n            CJSON::Json elem;\n            elem.createObject();\n            elem.setString(\"label\", label);\n            elem.setString(\"type\", \"slider\");\n            elem.setNumber(\"value\", current);\n            elem.setNumber(\"min\", def.min);\n            elem.setNumber(\"max\", def.max);\n            elem.setNumber(\"step\", def.step);\n\n            m_currentElements.add(elem);\n            numOptions++;\n        }\n\n        /*\n            Read slider value into the provided struct from persisted config\n            Returns true if a value was found\n        */\n        bool getSliderValue(SetupConfig::Slider &def) {\n            if (m_doc == nullptr && !openConfiguration()) {\n                log_error(\"Error! /setup configuration not possible\");\n                return false;\n            }\n\n            CJSON::Json* sourceDoc = (m_savedDoc != nullptr) ? m_savedDoc : m_doc;\n            if (sourceDoc == nullptr) return false;\n\n            const cJSON* root = sourceDoc->getRoot();\n            if (!root) return false;\n            const cJSON* sections = cJSON_GetObjectItemCaseSensitive(root, \"sections\");\n            if (!sections || !cJSON_IsArray(sections)) return false;\n\n            const cJSON* sec = sections->child;\n            while (sec) {\n                const cJSON* elems = cJSON_GetObjectItemCaseSensitive(sec, \"elements\");\n                if (elems && cJSON_IsArray(elems)) {\n                    const cJSON* el = elems->child;\n                    while (el) {\n                        const cJSON* lbl = cJSON_GetObjectItemCaseSensitive(el, \"label\");\n                        if (lbl && cJSON_IsString(lbl) && lbl->valuestring && String(lbl->valuestring).equals(def.label)) {\n                            const cJSON* val = cJSON_GetObjectItemCaseSensitive(el, \"value\");\n                            if (val && cJSON_IsNumber(val)) {\n                                def.value = val->valuedouble;\n                                return true;\n                            }\n                            return false;\n                        }\n                        el = el->next;\n                    }\n                }\n                sec = sec->next;\n            }\n            return false;\n        }\n\n        /*\n            Add a new option box with custom label\n        */\n        void addOptionBox(const char* boxTitle) {\n            if (m_doc == nullptr) {\n                if (!openConfiguration()) {\n                    log_error(\"Error! /setup configuration not possible\");\n                    return;\n                }\n            }\n            startNewSection(boxTitle);\n        }\n\n        /**\n         * @brief Attach a comment to an existing element by label/tag\n         * Comments are stored inside the element's JSON object under key \"comment\".\n         * The frontend will render them as <div class=\"cmt\">text</div> below the input.\n         */\n        void addComment(const char *tag, const char *comment) {\n            if (m_doc == nullptr) {\n                if (!openConfiguration()) {\n                    log_error(\"Error! /setup configuration not possible\");\n                    return;\n                }\n            }\n            String ct = String(comment);\n            bool found = false;\n            // Update in current elements if present\n            cJSON* arr = m_currentElements.getRoot();\n            if (arr && cJSON_IsArray(arr)) {\n                for (cJSON* el = arr->child; el; el = el->next) {\n                    cJSON* lbl = cJSON_GetObjectItemCaseSensitive(el, \"label\");\n                    if (lbl && cJSON_IsString(lbl) && String(lbl->valuestring) == tag) {\n                        cJSON_DeleteItemFromObjectCaseSensitive(el, \"comment\");\n                        cJSON_AddStringToObject(el, \"comment\", comment);\n                        found = true;\n                        break;\n                    }\n                }\n            }\n            // Fallback: search through sections already added\n            if (!found) {\n                cJSON* secs = m_sectionsArray.getRoot();\n                if (secs && cJSON_IsArray(secs)) {\n                    for (cJSON* sec = secs->child; sec && !found; sec = sec->next) {\n                        cJSON* elems = cJSON_GetObjectItemCaseSensitive(sec, \"elements\");\n                        if (elems && cJSON_IsArray(elems)) {\n                            for (cJSON* el = elems->child; el; el = el->next) {\n                                cJSON* lbl = cJSON_GetObjectItemCaseSensitive(el, \"label\");\n                                if (lbl && cJSON_IsString(lbl) && String(lbl->valuestring) == tag) {\n                                    cJSON_DeleteItemFromObjectCaseSensitive(el, \"comment\");\n                                    cJSON_AddStringToObject(el, \"comment\", comment);\n                                    found = true;\n                                    break;\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n            if (!found && m_doc) {\n                m_doc->setString(tag, \"comment\", ct);\n            }\n        }\n\n\n        /*\n            Add custom option to config webpage (float values)\n        */\n        template <typename T>\n        void addOption(const char *label, T val, double d_min, double d_max, double step) {\n            addOption(label, val, false, d_min, d_max, step);\n        }\n\n        /*\n        Add custom option to config webpage (type of parameter will be deduced from variable itself)\n        */\n        // bool-specific overload with grouping flag (grouped last to avoid breaking existing code)\n        void addOption(const char *label, bool val, bool hidden = false, bool grouped = true) {\n            if (m_doc == nullptr) {\n                if (!openConfiguration()) {\n                    log_error(\"Error! /setup configuration not possible\");\n                }\n            }\n\n            ensureActiveSection();\n            String lbl = label;\n            bool valueFromSaved = false;\n            // read saved as before\n            auto readSavedBool = [&](bool& out) -> bool {\n                if (!m_savedDoc) return false;\n                const cJSON* root = m_savedDoc->getRoot();\n                if (!root) return false;\n                const cJSON* sections = cJSON_GetObjectItemCaseSensitive(root, \"sections\");\n                if (!sections || !cJSON_IsArray(sections)) return false;\n                const cJSON* sec = sections->child;\n                while (sec) {\n                    const cJSON* elems = cJSON_GetObjectItemCaseSensitive(sec, \"elements\");\n                    if (elems && cJSON_IsArray(elems)) {\n                        const cJSON* el = elems->child;\n                        while (el) {\n                            const cJSON* lblNode = cJSON_GetObjectItemCaseSensitive(el, \"label\");\n                            if (lblNode && cJSON_IsString(lblNode) && lblNode->valuestring && String(lblNode->valuestring).equals(lbl)) {\n                                const cJSON* v = cJSON_GetObjectItemCaseSensitive(el, \"value\");\n                                if (v && cJSON_IsBool(v)) {\n                                    out = cJSON_IsTrue(v);\n                                    return true;\n                                }\n                                return false;\n                            }\n                            el = el->next;\n                        }\n                    }\n                    sec = sec->next;\n                }\n                return false;\n            };\n\n            CJSON::Json elem;\n            elem.createObject();\n            elem.setString(\"label\", lbl);\n\n            bool current = val;\n            if (readSavedBool(current)) valueFromSaved = true;\n            elem.setString(\"type\", \"boolean\");\n            elem.setBool(\"value\", current);\n\n            if (!grouped) {\n                elem.setBool(\"group\", false);\n            }\n            if (hidden) {\n                elem.setBool(\"hidden\", true);\n            }\n\n            log_debug(\"Option \\\"%s\\\" using %s value\", lbl.c_str(), valueFromSaved ? \"saved\" : \"default\");\n            m_currentElements.add(elem);\n            numOptions++;\n            (void)valueFromSaved;\n        }\n\n        // generic template for all other types\n        template <typename T>\n        void addOption(const char *label, T val, bool hidden = false,\n                            double d_min = MIN_F, double d_max = MAX_F, double step = 1.0)\n        {\n\n            if (m_doc == nullptr) {\n                if (!openConfiguration()) {\n                    log_error(\"Error! /setup configuration not possible\");\n                }\n            }\n\n            ensureActiveSection();\n\n            String lbl = label;\n            bool valueFromSaved = false;\n\n            // Resolve current value: check saved v2 sections first\n            auto readSavedNumber = [&](double& out) -> bool {\n                if (!m_savedDoc) return false;\n                const cJSON* root = m_savedDoc->getRoot();\n                if (!root) return false;\n                const cJSON* sections = cJSON_GetObjectItemCaseSensitive(root, \"sections\");\n                if (!sections || !cJSON_IsArray(sections)) return false;\n                const cJSON* sec = sections->child;\n                while (sec) {\n                    const cJSON* elems = cJSON_GetObjectItemCaseSensitive(sec, \"elements\");\n                    if (elems && cJSON_IsArray(elems)) {\n                        const cJSON* el = elems->child;\n                        while (el) {\n                            const cJSON* lblNode = cJSON_GetObjectItemCaseSensitive(el, \"label\");\n                            if (lblNode && cJSON_IsString(lblNode) && lblNode->valuestring && String(lblNode->valuestring).equals(lbl)) {\n                                const cJSON* v = cJSON_GetObjectItemCaseSensitive(el, \"value\");\n                                if (v && cJSON_IsNumber(v)) {\n                                    out = v->valuedouble;\n                                    return true;\n                                }\n                                return false;\n                            }\n                            el = el->next;\n                        }\n                    }\n                    sec = sec->next;\n                }\n                return false;\n            };\n\n            auto readSavedString = [&](String& out) -> bool {\n                if (!m_savedDoc) return false;\n                const cJSON* root = m_savedDoc->getRoot();\n                if (!root) return false;\n                const cJSON* sections = cJSON_GetObjectItemCaseSensitive(root, \"sections\");\n                if (!sections || !cJSON_IsArray(sections)) return false;\n                const cJSON* sec = sections->child;\n                while (sec) {\n                    const cJSON* elems = cJSON_GetObjectItemCaseSensitive(sec, \"elements\");\n                    if (elems && cJSON_IsArray(elems)) {\n                        const cJSON* el = elems->child;\n                        while (el) {\n                            const cJSON* lblNode = cJSON_GetObjectItemCaseSensitive(el, \"label\");\n                            if (lblNode && cJSON_IsString(lblNode) && lblNode->valuestring && String(lblNode->valuestring).equals(lbl)) {\n                                const cJSON* v = cJSON_GetObjectItemCaseSensitive(el, \"value\");\n                                if (v && cJSON_IsString(v) && v->valuestring) {\n                                    out = String(v->valuestring);\n                                    return true;\n                                }\n                                return false;\n                            }\n                            el = el->next;\n                        }\n                    }\n                    sec = sec->next;\n                }\n                return false;\n            };\n\n            CJSON::Json elem;\n            elem.createObject();\n            elem.setString(\"label\", lbl);\n\n            if constexpr (std::is_same<T, String>::value) {\n                String current = val;\n                if (readSavedString(current)) valueFromSaved = true;\n                elem.setString(\"type\", \"text\");\n                elem.setString(\"value\", current);\n            } else if constexpr (std::is_same<T, const char*>::value || std::is_same<T, char*>::value) {\n                String current = String(val);\n                if (readSavedString(current)) valueFromSaved = true;\n                elem.setString(\"type\", \"text\");\n                elem.setString(\"value\", current);\n            } else {\n                double current = static_cast<double>(val);\n                if (readSavedNumber(current)) valueFromSaved = true;\n                elem.setString(\"type\", \"number\");\n                elem.setNumber(\"value\", current);\n                if (d_min != MIN_F) elem.setNumber(\"min\", d_min);\n                if (d_max != MAX_F) elem.setNumber(\"max\", d_max);\n                if (step != 1.0) elem.setNumber(\"step\", step);\n            }\n\n            if (hidden) {\n                elem.setBool(\"hidden\", true);\n            }\n\n            log_debug(\"Option \\\"%s\\\" using %s value\", lbl.c_str(), valueFromSaved ? \"saved\" : \"default\");\n            m_currentElements.add(elem);\n            numOptions++;\n        }\n\n        /*\n            Get current value for a specific custom option (true on success)\n            Reads from m_doc if open, or reloads from file if closed\n        */\n        template <typename T>\n        bool getOptionValue(const char *label, T &var) {\n            // If m_doc is nullptr, reload configuration from file\n            if (m_doc == nullptr) {\n                if (!openConfiguration()) {\n                    log_error(\"Error! /setup configuration not possible\");\n                    return false;\n                }\n            }\n            \n            // Prefer persisted values when available; fall back to current session doc\n            CJSON::Json* sourceDoc = (m_savedDoc != nullptr) ? m_savedDoc : m_doc;\n\n            if (sourceDoc == nullptr) {\n                log_error(\"No configuration document available for reading\");\n                return false;\n            }\n\n            const cJSON* root = sourceDoc->getRoot();\n            if (!root) return false;\n\n            // Special case: port/host are stored in _meta\n            if constexpr (!std::is_same<T, String>::value && !std::is_same<T, const char*>::value && !std::is_same<T, char*>::value) {\n                if (strcmp(label, \"port\") == 0) {\n                    const cJSON* meta = cJSON_GetObjectItemCaseSensitive(root, \"_meta\");\n                    const cJSON* p = meta ? cJSON_GetObjectItemCaseSensitive(meta, \"port\") : nullptr;\n                    if (p && cJSON_IsNumber(p)) {\n                        var = static_cast<T>(p->valuedouble);\n                        return true;\n                    }\n                }\n            }\n\n            const cJSON* sections = cJSON_GetObjectItemCaseSensitive(root, \"sections\");\n            if (!sections || !cJSON_IsArray(sections)) return false;\n\n            const cJSON* sec = sections->child;\n            while (sec) {\n                const cJSON* elems = cJSON_GetObjectItemCaseSensitive(sec, \"elements\");\n                if (elems && cJSON_IsArray(elems)) {\n                    const cJSON* el = elems->child;\n                    while (el) {\n                        const cJSON* lblNode = cJSON_GetObjectItemCaseSensitive(el, \"label\");\n                        if (lblNode && cJSON_IsString(lblNode) && lblNode->valuestring && String(lblNode->valuestring).equals(label)) {\n                            const cJSON* valNode = cJSON_GetObjectItemCaseSensitive(el, \"value\");\n                            if constexpr (std::is_same<T, String>::value) {\n                                if (valNode && cJSON_IsString(valNode) && valNode->valuestring) {\n                                    var = String(valNode->valuestring);\n                                    return true;\n                                }\n                            } else if constexpr (std::is_same<T, const char*>::value || std::is_same<T, char*>::value) {\n                                if (valNode && cJSON_IsString(valNode) && valNode->valuestring) {\n                                    static String tmp; // Note: lifetime tied to process; acceptable for config reads\n                                    tmp = String(valNode->valuestring);\n                                    var = tmp.c_str();\n                                    return true;\n                                }\n                            } else if constexpr (std::is_same<T, bool>::value) {\n                                if (valNode && cJSON_IsBool(valNode)) {\n                                    var = cJSON_IsTrue(valNode);\n                                    return true;\n                                }\n                            } else {\n                                if (valNode && cJSON_IsNumber(valNode)) {\n                                    var = static_cast<T>(valNode->valuedouble);\n                                    return true;\n                                }\n                            }\n                            return false;\n                        }\n                        el = el->next;\n                    }\n                }\n                sec = sec->next;\n            }\n            return false;\n        }\n\n        template <typename T>\n        bool saveOptionValue(const char *label, T val) {\n            if (m_doc == nullptr) {\n                if (!openConfiguration()) {\n                    log_error(\"Error! /setup configuration not possible\");\n                    return false;\n                }\n            }\n\n            // Ensure sections are attached before modifying (so we always work on the same tree)\n            finalizeSectionsToRoot();\n\n            cJSON* root = m_doc->getRoot();\n            if (!root) return false;\n            cJSON* targetElement = findElementByLabel(root, label);\n\n            if (!targetElement && adoptSavedConfigurationAsSessionDoc()) {\n                root = m_doc ? m_doc->getRoot() : nullptr;\n                targetElement = findElementByLabel(root, label);\n            }\n\n            if (!targetElement) {\n                log_error(\"Error! /setup configuration element with label \\\"%s\\\" not found\", label);\n                return false;\n            }\n\n            // Replace or create \"value\" field inside target element\n            cJSON_DeleteItemFromObjectCaseSensitive(targetElement, \"value\");\n\n            if constexpr (std::is_same<T, String>::value) {\n                cJSON_AddItemToObject(targetElement, \"value\", cJSON_CreateString(val.c_str()));\n            } else if constexpr (std::is_same<T, const char*>::value || std::is_same<T, char*>::value) {\n                cJSON_AddItemToObject(targetElement, \"value\", cJSON_CreateString(String(val).c_str()));\n            } else if constexpr (std::is_same<T, bool>::value) {\n                cJSON_AddItemToObject(targetElement, \"value\", cJSON_CreateBool(val));\n            } else {\n                cJSON_AddItemToObject(targetElement, \"value\", cJSON_CreateNumber(static_cast<double>(val)));\n            }\n\n            if (numOptions == 0) {\n                numOptions = 1;\n            }\n            return true;\n        }\n\n};\n\n#endif"
  },
  {
    "path": "src/Version.h",
    "content": "\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__[10])\n\n// Month MM\n#define BUILD_MONTH_IS_JAN (__DATE__[0] == 'J' && __DATE__[1] == 'a' && __DATE__[2] == 'n')\n#define BUILD_MONTH_IS_FEB (__DATE__[0] == 'F')\n#define BUILD_MONTH_IS_MAR (__DATE__[0] == 'M' && __DATE__[1] == 'a' && __DATE__[2] == 'r')\n#define BUILD_MONTH_IS_APR (__DATE__[0] == 'A' && __DATE__[1] == 'p')\n#define BUILD_MONTH_IS_MAY (__DATE__[0] == 'M' && __DATE__[1] == 'a' && __DATE__[2] == 'y')\n#define BUILD_MONTH_IS_JUN (__DATE__[0] == 'J' && __DATE__[1] == 'u' && __DATE__[2] == 'n')\n#define BUILD_MONTH_IS_JUL (__DATE__[0] == 'J' && __DATE__[1] == 'u' && __DATE__[2] == 'l')\n#define BUILD_MONTH_IS_AUG (__DATE__[0] == 'A' && __DATE__[1] == 'u')\n#define BUILD_MONTH_IS_SEP (__DATE__[0] == 'S')\n#define BUILD_MONTH_IS_OCT (__DATE__[0] == 'O')\n#define BUILD_MONTH_IS_NOV (__DATE__[0] == 'N')\n#define BUILD_MONTH_IS_DEC (__DATE__[0] == 'D')\n\n#define BUILD_MONTH_CH0 \\\n    ((BUILD_MONTH_IS_OCT || BUILD_MONTH_IS_NOV || BUILD_MONTH_IS_DEC) ? '1' : '0')\n\n#define BUILD_MONTH_CH1 \\\n    ( \\\n        (BUILD_MONTH_IS_JAN) ? '1' : \\\n        (BUILD_MONTH_IS_FEB) ? '2' : \\\n        (BUILD_MONTH_IS_MAR) ? '3' : \\\n        (BUILD_MONTH_IS_APR) ? '4' : \\\n        (BUILD_MONTH_IS_MAY) ? '5' : \\\n        (BUILD_MONTH_IS_JUN) ? '6' : \\\n        (BUILD_MONTH_IS_JUL) ? '7' : \\\n        (BUILD_MONTH_IS_AUG) ? '8' : \\\n        (BUILD_MONTH_IS_SEP) ? '9' : \\\n        (BUILD_MONTH_IS_OCT) ? '0' : \\\n        (BUILD_MONTH_IS_NOV) ? '1' : \\\n        (BUILD_MONTH_IS_DEC) ? '2' : \\\n        '?' \\\n    )\n\n// Day DD\n#define BUILD_DAY_CH0 ((__DATE__[4] >= '0') ? (__DATE__[4]) : '0')\n#define BUILD_DAY_CH1 (__DATE__[5])\n\n// Hour HH\n#define BUILD_HOUR_CH0 (__TIME__[0])\n#define BUILD_HOUR_CH1 (__TIME__[1])\n\n// Minute mm\n#define BUILD_MIN_CH0 (__TIME__[3])\n#define BUILD_MIN_CH1 (__TIME__[4])\n\n#define BUILD_TIMESTAMP \\\n    (const char[]) { \\\n        BUILD_YEAR_CH0, BUILD_YEAR_CH1, \\\n        BUILD_MONTH_CH0, BUILD_MONTH_CH1, \\\n        BUILD_DAY_CH0, BUILD_DAY_CH1, \\\n        BUILD_HOUR_CH0, BUILD_HOUR_CH1, \\\n        BUILD_MIN_CH0, BUILD_MIN_CH1, \\\n        '\\0' \\\n    }\n\n#endif\t// VERSION_H"
  },
  {
    "path": "src/WiFiService.cpp",
    "content": "#include \"WiFiService.h\"\n#include \"Json.h\"\n\n#if defined(ESP32) || defined(ESP8266)\nWiFiConnectedCallbackF WiFiService::m_wifiConnectedCallback = nullptr;\nWiFiDisconnectedCallbackF WiFiService::m_wifiDisconnectedCallback = nullptr;\n#endif\n\n#if defined(ESP8266)\nWiFiEventHandler WiFiService::m_wifiConnectedHandler;\nWiFiEventHandler WiFiService::m_wifiDisconnectedHandler;\n\nvoid WiFiService::handleWiFiConnected(const WiFiEventStationModeGotIP& event) {\n    if (m_wifiConnectedCallback) {\n        m_wifiConnectedCallback(event);\n    }\n}\n\nvoid WiFiService::handleWiFiDisconnected(const WiFiEventStationModeDisconnected& event) {\n    if (m_wifiDisconnectedCallback) {\n        m_wifiDisconnectedCallback(event);\n    }\n}\n#endif\n\n// Helper: log the actual station network configuration as seen by the core\n// (independent from WiFiConnectParams / CredentialManager values).\nstatic void logCurrentStaNetworkConfig() {\n#if defined(ESP8266) || defined(ESP32)\n    auto mode = WiFi.getMode();\n    if (mode != WIFI_STA && mode != WIFI_AP_STA) {\n        return;\n    }\n    log_info(\"[WiFi] Core STA config: IP=%s GW=%s SN=%s DNS1=%s DNS2=%s\",\n             WiFi.localIP().toString().c_str(),\n             WiFi.gatewayIP().toString().c_str(),\n             WiFi.subnetMask().toString().c_str(),\n             WiFi.dnsIP(0).toString().c_str(),\n             WiFi.dnsIP(1).toString().c_str());\n#endif\n}\n\n#if defined(ESP32)\nstatic inline void resetTaskWdtIfSubscribed() {\n    if (esp_task_wdt_status(NULL) == ESP_OK) {\n        esp_task_wdt_reset();\n    }\n}\n#endif\n\nvoid WiFiService::setTaskWdt(uint32_t timeout) {\n#if defined(ESP32)\n    #if ESP_ARDUINO_VERSION_MAJOR > 2\n    esp_task_wdt_config_t twdt_config = {\n        .timeout_ms = timeout,\n        .idle_core_mask = (1 << portNUM_PROCESSORS) - 1,\n        .trigger_panic = false,\n    };\n    ESP_ERROR_CHECK(esp_task_wdt_reconfigure(&twdt_config));\n    #else\n    ESP_ERROR_CHECK(esp_task_wdt_init(timeout / 1000, 0));\n    #endif\n    // The Arduino core already subscribes loopTask to the WDT.\n    // Adding it again logs \"task is already subscribed\". Avoid duplicate add.\n#elif defined(ESP8266)\n    ESP.wdtDisable();\n    ESP.wdtEnable(timeout);\n#endif\n}\n\nWiFiScanResult WiFiService::scanNetworks() {\n    WiFiScanResult result;\n    int res = WiFi.scanComplete();\n\n#ifdef WIFI_SCAN_RUNNING\n    if (res == WIFI_SCAN_RUNNING) {\n        result.reload = true;\n        result.json = \"{\\\"reload\\\":1}\";\n        return result;\n    }\n    if (res == WIFI_SCAN_FAILED) {\n        WiFi.scanNetworks(true);\n        result.reload = true;\n        result.json = \"{\\\"reload\\\":1}\";\n        return result;\n    }\n#else\n    if (res == -2) {\n        WiFi.scanNetworks(true);\n        result.reload = true;\n        result.json = \"{\\\"reload\\\":1}\";\n        return result;\n    }\n    if (res == -1) {\n        result.reload = true;\n        result.json = \"{\\\"reload\\\":1}\";\n        return result;\n    }\n#endif\n\n    if (res >= 0) {\n        CJSON::Json json_array;\n        json_array.createArray();\n        for (int i = 0; i < res; ++i) {\n            CJSON::Json item;\n            item.setNumber(\"strength\", WiFi.RSSI(i));\n            item.setString(\"ssid\", WiFi.SSID(i));\n#if defined(ESP8266)\n            item.setString(\"security\", AUTH_OPEN ? \"none\" : \"enabled\");\n#elif defined(ESP32)\n            item.setString(\"security\", WIFI_AUTH_OPEN ? \"none\" : \"enabled\");\n#endif\n            json_array.add(item);\n        }\n        result.json = json_array.serialize();\n        WiFi.scanDelete();\n        WiFi.scanNetworks(true);\n        return result;\n    }\n\n    result.reload = true;\n    result.json = \"{\\\"reload\\\":1}\";\n    return result;\n}\n\nWiFiConnectResult WiFiService::connectWithParams(const WiFiConnectParams& params) {\n    WiFiConnectResult result;\n\n    if (strlen(params.config.ssid)) {\n        setTaskWdt(params.wdtLongTimeout);\n        WiFi.mode(WIFI_AP_STA);\n\n        if (!params.dhcp) {\n            log_info(\"Manual config WiFi connection with IP: %s\", params.config.local_ip.toString().c_str());\n            bool hasDns1 = params.config.dns1 != IPAddress(0, 0, 0, 0);\n            bool hasDns2 = params.config.dns2 != IPAddress(0, 0, 0, 0);\n            bool ok = false;\n            if (hasDns1 && hasDns2) {\n                ok = WiFi.config(params.config.local_ip, params.config.gateway, params.config.subnet, params.config.dns1, params.config.dns2);\n            } else if (hasDns1) {\n                ok = WiFi.config(params.config.local_ip, params.config.gateway, params.config.subnet, params.config.dns1);\n            } else {\n                ok = WiFi.config(params.config.local_ip, params.config.gateway, params.config.subnet);\n            }\n\n            if (!ok) {\n                log_error(\"STA Failed to configure\");\n            }\n        }        \n\n        DBG_OUTPUT_PORT.print(\"\\n\\n\\nConnecting to \");\n    DBG_OUTPUT_PORT.println(params.config.ssid);\n    WiFi.begin(params.config.ssid, params.password.c_str());\n\n        uint32_t beginTime = millis();\n        while (WiFi.status() != WL_CONNECTED) {\n            delay(250);\n            DBG_OUTPUT_PORT.print(\"*\");\n#if defined(ESP8266)\n            ESP.wdtFeed();\n#else\n            resetTaskWdtIfSubscribed();\n#endif\n            if (millis() - beginTime > params.timeout) {\n                result.status = 408;\n                result.body = \"<br><br>Connection timeout!<br>Check password or try to restart ESP.\";\n                setTaskWdt(params.wdtTimeout);\n                return result;\n            }\n        }\n\n        if (WiFi.status() == WL_CONNECTED) {\n            result.ip = WiFi.localIP();\n            result.connected = true;\n\n            // Debug: print the actual STA configuration as seen by the core\n            logCurrentStaNetworkConfig();\n\n            DBG_OUTPUT_PORT.print(\"\\nConnected to \");\n            DBG_OUTPUT_PORT.print(params.config.ssid);\n            DBG_OUTPUT_PORT.print(\". IP address: \");\n            DBG_OUTPUT_PORT.println(result.ip);\n            String serverLoc = F(\"http://\");\n            for (int i = 0; i < 4; i++) {\n                if (i) serverLoc += \".\";\n                serverLoc += result.ip[i];\n            }\n            serverLoc += \"/setup\";\n            String resp;\n            resp  = \"ESP successfully connected to \";\n            resp += params.config.ssid;\n            resp += \" WiFi network.\";\n\n            if (params.fromApClient) {\n                // Case 1: request came from a client connected to the ESP AP.\n                // We stay in WIFI_AP_STA, so we can still deliver this page over the AP and then let the user switch WiFi.\n                resp += \" <br><br><i>Note:<br>Disconnect your browser from ESP's access point and connect it to the new WiFi network.<br><br>IP address <a href='\";\n                resp += serverLoc;\n                resp += \"'>\";\n                resp += serverLoc;\n                resp += \"</a><br> Hostname <a href='http://\";\n                resp += params.host;\n                resp += \".local/setup'>http://\";\n                resp += params.host;\n                resp += \".local/setup</a></i><br><p style='text-align: center'>Do you want to proceed with a ESP restart right now?</p><div id='action-restart-required'></div>\";\n            } else {\n                // Case 2: request came from a client already on the same WiFi as the ESP (pure SSID switch). \n                // After the switch this page will no longer reach the device until the client changes WiFi as well.\n                resp += \" <br><br><i>Note:<br>This setup page may stop communicating with the device due to the WiFi network change.<br>After you switch your PC/phone to the new WiFi network, open <a href='http://\";\n                resp += params.host;\n                resp += \".local'>http://\";\n                resp += params.host;\n                resp += \".local</a> (or the new IP: \";\n                resp += result.ip.toString();\n                resp += \") to reach the ESP again.</i><br><p style='text-align: center'>Do you want to proceed with a ESP restart right now?</p><div id='action-restart-required'></div>\";\n            }\n            result.status = 200;\n            result.body = resp;\n            setTaskWdt(params.wdtTimeout);\n            return result;\n        }\n    }\n\n    setTaskWdt(params.wdtTimeout);\n    result.status = 401;\n    result.body = \"Wrong credentials provided\";\n    return result;\n}\n\nWiFiStartResult WiFiService::startWiFi(CredentialManager* credentialManager, fs::FS* filesystem, const char* configFile, uint32_t timeout) {\n    WiFiStartResult result;\n    WiFi.mode(WIFI_STA);\n    WiFi.setAutoReconnect(true);\n#ifdef ESP32    \n    if (m_wifiDisconnectedCallback)\n        WiFi.onEvent(m_wifiDisconnectedCallback, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED);\n    if (m_wifiConnectedCallback)\n        WiFi.onEvent(m_wifiConnectedCallback, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_GOT_IP);\n#elif defined(ESP8266)\n    if (m_wifiDisconnectedCallback) {\n        m_wifiDisconnectedHandler = WiFi.onStationModeDisconnected(&WiFiService::handleWiFiDisconnected);\n    }\n    if (m_wifiConnectedCallback) {\n        m_wifiConnectedHandler = WiFi.onStationModeGotIP(&WiFiService::handleWiFiConnected);\n    }\n#endif\n\n    WiFiCredential* bestCred = nullptr;\n    if (credentialManager) {\n#ifdef ESP32\n        credentialManager->loadFromNVS();\n#else\n        credentialManager->loadFromFS();\n    #endif\n    #ifdef BOARD_HAS_SDIO_ESP_HOSTED\n        WiFi.setPins(BOARD_SDIO_ESP_HOSTED_CLK, BOARD_SDIO_ESP_HOSTED_CMD, BOARD_SDIO_ESP_HOSTED_D0,\n                BOARD_SDIO_ESP_HOSTED_D1, BOARD_SDIO_ESP_HOSTED_D2, BOARD_SDIO_ESP_HOSTED_D3,\n                BOARD_SDIO_ESP_HOSTED_RESET);\n        WiFi.STA.begin();\n        WiFi.mode(WIFI_STA);\n        WiFi.disconnect(false, true, 1000); // needed for scanNetworks to work\n    #endif\n        std::vector<WiFiCredential>* creds = credentialManager->getCredentials();\n        if (creds && creds->size() > 0) {\n            int networksFound = WiFi.scanNetworks();\n            if (networksFound > 0) {\n                int32_t bestRSSI = -200;\n                \n                for (int i = 0; i < networksFound; i++) {\n                    String scannedSSID = WiFi.SSID(i);\n                    int32_t scannedRSSI = WiFi.RSSI(i);\n                    for (size_t j = 0; j < creds->size(); j++) {\n                        if (strcmp((*creds)[j].ssid, scannedSSID.c_str()) == 0) {\n                            if (scannedRSSI > bestRSSI) {\n                                bestRSSI = scannedRSSI;\n                                bestCred = &(*creds)[j];\n                            }\n                            break;\n                        }\n                    }\n                }\n                WiFi.scanDelete();\n\n                if (bestCred != nullptr) {\n                    if (bestCred->local_ip != IPAddress(0, 0, 0, 0)) {\n                        log_info(\"Configuring static IP: %s, GW: %s, SN: %s\",\n                                 bestCred->local_ip.toString().c_str(),\n                                 bestCred->gateway.toString().c_str(),\n                                 bestCred->subnet.toString().c_str());\n\n                        IPAddress dns1 = bestCred->dns1;\n                        IPAddress dns2 = bestCred->dns2;\n                        bool hasDns1 = dns1 != IPAddress(0, 0, 0, 0);\n                        bool hasDns2 = dns2 != IPAddress(0, 0, 0, 0);\n                        bool ok = false;\n                        if (hasDns1 && hasDns2) {\n                            ok = WiFi.config(bestCred->local_ip, bestCred->gateway, bestCred->subnet, dns1, dns2);\n                        } else if (hasDns1) {\n                            ok = WiFi.config(bestCred->local_ip, bestCred->gateway, bestCred->subnet, dns1);\n                        } else {\n                            ok = WiFi.config(bestCred->local_ip, bestCred->gateway, bestCred->subnet);\n                        }\n\n                        if (!ok) {\n                            log_error(\"Failed to configure static IP\");\n                        }\n                    }\n                    \n                    if (credentialManager->getPassword(bestCred->ssid).length() > 0) {\n                        log_info(\"Connecting to %s (RSSI: %d dBm)...\", bestCred->ssid, bestRSSI);\n                        WiFi.begin(bestCred->ssid, credentialManager->getPassword(bestCred->ssid).c_str());\n\n                        int tryDelay = timeout / 10;\n                        int numberOfTries = 10;\n                        while (numberOfTries > 0) {\n                            switch (WiFi.status()) {\n                            case WL_NO_SSID_AVAIL:   log_debug(\"[WiFi] SSID not found\"); break;\n                            case WL_CONNECTION_LOST: log_debug(\"[WiFi] Connection was lost\"); break;\n                            case WL_SCAN_COMPLETED:  log_debug(\"[WiFi] Scan is completed\"); break;\n                            case WL_DISCONNECTED:    log_debug(\"[WiFi] WiFi is disconnected\"); break;\n                            case WL_CONNECT_FAILED:\n                                log_debug(\"[WiFi] Failed - WiFi not connected!\");\n                                result.action = WiFiStartAction::StartAp;\n                                return result;\n                            case WL_CONNECTED:\n                                log_debug(\"[WiFi] WiFi is connected!  IP address: %s\", WiFi.localIP().toString().c_str());\n                                // Debug: print the actual STA configuration as seen by the core\n                                logCurrentStaNetworkConfig();\n                                result.ip = WiFi.localIP();\n                                result.action = WiFiStartAction::Connected;\n                                return result;\n                            default:\n                                log_debug(\"[WiFi] WiFi Status: %d\", WiFi.status());\n                                break;\n                            }\n                            delay(tryDelay);\n#if defined(ESP8266)\n                            ESP.wdtFeed();\n#else\n                            resetTaskWdtIfSubscribed();\n#endif\n                            numberOfTries--;\n                        }\n\n                        log_debug(\"[WiFi] Failed to connect to WiFi!\");\n                        WiFi.disconnect();\n                        result.action = WiFiStartAction::StartAp;\n                        return result;\n                    }\n                }\n            }\n            result.action = WiFiStartAction::StartAp;\n            return result;\n        }\n    }\n\n    if (bestCred && strlen(bestCred->ssid)) {\n        WiFi.setHostname(credentialManager->getHostname().c_str());\n        WiFi.begin(bestCred->ssid, credentialManager->getPassword(bestCred->ssid).c_str());\n        log_debug(\"Connecting to %s\", bestCred->ssid);\n\n        int tryDelay = timeout / 10;\n        int numberOfTries = 10;\n\n        while (true) {\n            switch (WiFi.status()) {\n            case WL_NO_SSID_AVAIL:   log_debug(\"[WiFi] SSID not found\"); break;\n            case WL_CONNECTION_LOST: log_debug(\"[WiFi] Connection was lost\"); break;\n            case WL_SCAN_COMPLETED:  log_debug(\"[WiFi] Scan is completed\"); break;\n            case WL_DISCONNECTED:    log_debug(\"[WiFi] WiFi is disconnected\"); break;\n            case WL_CONNECT_FAILED:\n                log_debug(\"[WiFi] Failed - WiFi not connected!\");\n                result.action = WiFiStartAction::Failed;\n                return result;\n            case WL_CONNECTED:\n                log_debug(\"[WiFi] WiFi is connected!  IP address: %s\", WiFi.localIP().toString().c_str());\n                // Debug: print the actual STA configuration as seen by the core\n                logCurrentStaNetworkConfig();\n                result.ip = WiFi.localIP();\n                result.action = WiFiStartAction::Connected;\n                return result;\n            default:\n                log_debug(\"[WiFi] WiFi Status: %d\", WiFi.status());\n                break;\n            }\n            delay(tryDelay);\n#if defined(ESP8266)\n            ESP.wdtFeed();\n#else\n            resetTaskWdtIfSubscribed();\n#endif\n            if (numberOfTries <= 0) {\n                log_debug(\"[WiFi] Failed to connect to WiFi!\");\n                WiFi.disconnect();\n                result.action = WiFiStartAction::Failed;\n                return result;\n            }\n            else {\n                numberOfTries--;\n            }\n        }\n    }\n\n    result.action = WiFiStartAction::StartAp;\n    return result;\n}\n\n\nbool WiFiService::startAccessPoint(WiFiConnectParams& params, IPAddress& outIp) {    \n    delay(100);\n    WiFi.mode(WIFI_AP);\n\n    IPAddress apIP = params.config.local_ip != IPAddress(0,0,0,0) ? params.config.local_ip : IPAddress(192, 168, 4, 1);\n    IPAddress gateway = params.config.gateway != IPAddress(0,0,0,0) ? params.config.gateway : IPAddress(192, 168, 4, 1);\n    IPAddress netmask = params.config.subnet != IPAddress(0,0,0,0) ? params.config.subnet : IPAddress(255, 255, 255, 0);\n\n    // Configure AP IP parameters\n    WiFi.softAPConfig(apIP, gateway, netmask);\n\n    if (!WiFi.softAP(params.config.ssid, params.password.c_str())) {\n        log_error(\"Captive portal failed to start: WiFi.softAP() failed!\");\n        return false;\n    }\n    outIp = WiFi.softAPIP();\n    return true;\n}\n\nbool WiFiService::startMDNSResponder(DNSServer*& dnsServer, const String& host, uint16_t port, const IPAddress& serverIp) {\n    if (dnsServer) {\n        delete dnsServer;\n        dnsServer = nullptr;\n    }\n    dnsServer = new DNSServer();\n    dnsServer->setErrorReplyCode(DNSReplyCode::NoError);\n    if (!dnsServer->start(53, \"*\", serverIp)) {\n        log_error(\"Captive portal failed to start: no sockets for DNS server available!\");\n        return false;\n    }\n\n    if (!MDNS.begin(host.c_str())) {\n        log_error(\"MDNS responder not started\");\n        return false;\n    } else {\n        log_debug(\"MDNS responder started %s (AP mode)\", host.c_str());\n        MDNS.addService(\"http\", \"tcp\", port);\n        MDNS.setInstanceName(\"esp-fs-webserver\");\n    }\n    return true;\n}\n\nbool WiFiService::startMDNSOnly(const String& host, uint16_t port) {\n    if (!MDNS.begin(host.c_str())) {\n        log_error(\"MDNS responder not started\");\n        return false;\n    }\n\n    log_debug(\"MDNS responder started %s (STA mode)\", host.c_str());\n    MDNS.addService(\"http\", \"tcp\", port);\n    MDNS.setInstanceName(\"esp-fs-webserver\");\n    return true;\n}\n"
  },
  {
    "path": "src/WiFiService.h",
    "content": "#pragma once\n\n#include <Arduino.h>\n#include <DNSServer.h>\n#include <FS.h>\n#include \"SerialLog.h\"\n#include \"CredentialManager.h\"\n\n#if defined(ESP8266)\n#include <ESP8266WiFi.h>\n#include <ESP8266mDNS.h>\n#elif defined(ESP32)\n#include <WiFi.h>\n#include <ESPmDNS.h>\n#include <esp_wifi.h>\n#include <esp_task_wdt.h>\n#else\n#error Platform not supported\n#endif\n\nstruct WiFiScanResult {\n    bool reload = false;\n    String json;\n};\n\nenum class WiFiStartAction {\n    Connected,\n    StartAp,\n    Failed\n};\n\nstruct WiFiStartResult {\n    WiFiStartAction action = WiFiStartAction::Failed;\n    IPAddress ip = IPAddress(0, 0, 0, 0);\n};\n\nstruct WiFiConnectParams {\n    WiFiCredential config;                       // WiFi credentials (ssid, encrypted password, IP config, DNS)\n    bool fromApClient = false;                  // True if the setup client started the connection while AP mode is active\n    bool dhcp = true;                           // True to use DHCP, false for static IP\n    String password;                            // Plaintext password (temporary, not stored in credential)\n    String host;                                // Hostname for mDNS\n    uint32_t timeout = 0;                       // Connection timeout in milliseconds\n    uint32_t wdtLongTimeout = 0;                // Long WDT timeout in milliseconds \n    uint32_t wdtTimeout = 0;                    // Regular WDT timeout in milliseconds\n\n    WiFiConnectParams() {\n        config = WiFiCredential();\n    }\n\n    WiFiConnectParams(const char* ssid, const char* plaintext_password) {\n        config = WiFiCredential();\n        if (ssid) {\n            strncpy(config.ssid, ssid, sizeof(config.ssid) - 1);\n            config.ssid[sizeof(config.ssid) - 1] = '\\0';\n        }\n        if (plaintext_password) {\n            password = plaintext_password;\n        }\n    }\n};\n\nstruct WiFiConnectResult {\n    bool connected = false;\n    int status = 500;    \n    IPAddress ip = IPAddress(0, 0, 0, 0);\n    String body;\n};\n\n#ifdef ESP32\nusing WiFiConnectedCallbackF = std::function<void(WiFiEvent_t, WiFiEventInfo_t)>;\nusing WiFiDisconnectedCallbackF = std::function<void(WiFiEvent_t, WiFiEventInfo_t)>;\n#elif defined(ESP8266)\nusing WiFiConnectedCallbackF = std::function<void(const WiFiEventStationModeGotIP&)>;\nusing WiFiDisconnectedCallbackF = std::function<void(const WiFiEventStationModeDisconnected&)>;\n#endif\n\nclass WiFiService {\npublic:\n    static void setTaskWdt(uint32_t timeout);\n    static WiFiScanResult scanNetworks();\n    static WiFiConnectResult connectWithParams(const WiFiConnectParams& params);\n    static WiFiStartResult startWiFi(CredentialManager* credentialManager, fs::FS* filesystem, const char* configFile, uint32_t timeout);    \n    static bool startAccessPoint(WiFiConnectParams& params, IPAddress& outIp);\n    static bool startMDNSResponder(DNSServer*& dnsServer, const String& host, uint16_t port, const IPAddress& serverIp);\n    static bool startMDNSOnly(const String& host, uint16_t port);\n#if defined(ESP32) || defined(ESP8266)\n    static void setWiFiConnectionCallbacks(WiFiConnectedCallbackF connectedCallback, WiFiDisconnectedCallbackF disconnectedCallback) {\n        m_wifiConnectedCallback = connectedCallback;\n        m_wifiDisconnectedCallback = disconnectedCallback;\n    }\n#endif\n\nprivate:\n#if defined(ESP32) || defined(ESP8266)\n    static WiFiConnectedCallbackF m_wifiConnectedCallback;\n    static WiFiDisconnectedCallbackF m_wifiDisconnectedCallback;\n#endif\n#ifdef ESP8266\n    static WiFiEventHandler m_wifiConnectedHandler;\n    static WiFiEventHandler m_wifiDisconnectedHandler;\n    static void handleWiFiConnected(const WiFiEventStationModeGotIP& event);\n    static void handleWiFiDisconnected(const WiFiEventStationModeDisconnected& event);\n#endif\n};\n"
  },
  {
    "path": "src/assets/edit_htm.h",
    "content": "// Auto Generated file (shared build script)\n#pragma once\n#include <pgmspace.h>\n\nconst uint8_t _acedit_htm[6758] PROGMEM = {\n  0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0xed, 0x3d, 0xe9, 0x6e, 0x23, 0xc9, \n  0x79, 0xff, 0x17, 0xd8, 0x77, 0x28, 0xf5, 0xcc, 0x0e, 0xbb, 0x57, 0x64, 0x93, 0xd2, 0x1c, 0xf0, \n  0x50, 0xa2, 0xe4, 0x39, 0xbd, 0x13, 0xcf, 0x85, 0xd1, 0x8c, 0x0f, 0xc8, 0xda, 0xa0, 0xd8, 0x5d, \n  0x24, 0x6b, 0xd4, 0xac, 0x6a, 0x77, 0x15, 0x25, 0x6a, 0xbd, 0x0a, 0xe0, 0x60, 0xe1, 0xfc, 0x34, \n  0x9c, 0xc3, 0xf9, 0x93, 0xc4, 0x2f, 0xe0, 0x07, 0x48, 0x82, 0x20, 0x0f, 0xe3, 0x17, 0x88, 0x1f, \n  0x21, 0xf8, 0xaa, 0xba, 0xc9, 0x3e, 0xaa, 0xba, 0x9b, 0x92, 0xc6, 0xb0, 0x83, 0xcc, 0x00, 0x43, \n  0x4e, 0x77, 0x1d, 0x5f, 0x7d, 0x57, 0x7d, 0x57, 0x15, 0xf7, 0x67, 0x72, 0x1e, 0xa1, 0x08, 0xb3, \n  0xe9, 0x88, 0xb0, 0x83, 0xcf, 0x3f, 0x43, 0x08, 0xa1, 0xfd, 0x39, 0x91, 0x18, 0x05, 0x33, 0x9c, \n  0x08, 0x22, 0x47, 0xce, 0x42, 0x4e, 0x7a, 0xdf, 0x73, 0xb2, 0x77, 0x92, 0xca, 0x88, 0x1c, 0x3c, \n  0xa7, 0x11, 0x41, 0x73, 0xcc, 0xf0, 0x94, 0x24, 0xfb, 0x7d, 0xfd, 0x2c, 0x6d, 0x20, 0xe4, 0x05, \n  0xbc, 0x23, 0x21, 0xc5, 0x23, 0x11, 0x24, 0x64, 0x35, 0x2c, 0xfc, 0x19, 0xf3, 0xf0, 0x02, 0xfd, \n  0x62, 0xfd, 0x7f, 0xf8, 0x33, 0xe1, 0x4c, 0xf6, 0x04, 0xfd, 0x86, 0x0c, 0xd1, 0xce, 0x6e, 0xbc, \n  0xdc, 0x33, 0xbc, 0x9d, 0xe0, 0x39, 0x8d, 0x2e, 0x86, 0xe8, 0x47, 0x24, 0x09, 0x31, 0xc3, 0xdd, \n  0x47, 0x09, 0xc5, 0x51, 0xf7, 0x08, 0x33, 0xd1, 0x13, 0x24, 0xa1, 0x93, 0x75, 0x97, 0xcb, 0xcf, \n  0x3f, 0xcb, 0xcd, 0xb6, 0x90, 0x92, 0xb3, 0xf2, 0x7c, 0x67, 0x24, 0x91, 0x34, 0xc0, 0x51, 0x0f, \n  0x47, 0x74, 0xca, 0x86, 0x68, 0x4e, 0xc3, 0x30, 0x22, 0xa5, 0x69, 0x67, 0x84, 0x4e, 0x67, 0x72, \n  0x88, 0x76, 0xab, 0x10, 0x85, 0x54, 0xc4, 0x11, 0xbe, 0x18, 0x22, 0xca, 0x22, 0xca, 0x48, 0x6f, \n  0x12, 0x91, 0x72, 0x13, 0x35, 0x72, 0x8f, 0x4a, 0x32, 0x17, 0x43, 0x14, 0x10, 0x26, 0x49, 0x52, \n  0x6a, 0xf1, 0x71, 0x21, 0x24, 0x9d, 0x5c, 0xf4, 0x02, 0xce, 0x24, 0x61, 0xd2, 0xd0, 0xaa, 0xb0, \n  0x12, 0xca, 0xe2, 0x85, 0xdc, 0x74, 0x1d, 0x85, 0x11, 0x7c, 0x35, 0xd3, 0x52, 0xbe, 0x22, 0x6c, \n  0x51, 0xc6, 0xc8, 0x37, 0x3d, 0xca, 0x42, 0xb2, 0x1c, 0xa2, 0xbb, 0x83, 0x41, 0x09, 0xce, 0x98, \n  0x0b, 0x2a, 0x29, 0x67, 0x43, 0x84, 0xc7, 0x82, 0x47, 0x0b, 0x59, 0x46, 0x54, 0x44, 0x26, 0x72, \n  0x88, 0xee, 0x57, 0xb0, 0x34, 0xe6, 0x49, 0x48, 0x92, 0x21, 0xda, 0x89, 0x97, 0x48, 0xf0, 0x88, \n  0x86, 0xe8, 0x56, 0x10, 0x04, 0xc6, 0x46, 0xbd, 0x04, 0x87, 0x74, 0x21, 0x86, 0xe8, 0x5e, 0x75, \n  0x14, 0x1c, 0x9c, 0x4e, 0x13, 0xbe, 0x60, 0x61, 0x2f, 0xe0, 0x11, 0x4f, 0x86, 0xe8, 0xd6, 0x64, \n  0x32, 0xb1, 0x11, 0x84, 0x71, 0x46, 0x2a, 0x33, 0x2c, 0x7b, 0x62, 0x86, 0x43, 0x7e, 0x3e, 0x44, \n  0x03, 0xb4, 0x1b, 0x2f, 0xd1, 0xf7, 0xe2, 0x25, 0x4a, 0xa6, 0x63, 0xec, 0x0e, 0xba, 0xf0, 0xd7, \n  0xdf, 0xb9, 0xef, 0xed, 0xd9, 0x19, 0xf2, 0xae, 0x99, 0x21, 0xcf, 0x53, 0xf6, 0xb8, 0x57, 0x41, \n  0x58, 0x81, 0x5f, 0x7b, 0x38, 0x8e, 0x23, 0xd2, 0x13, 0x17, 0x42, 0x92, 0x79, 0x17, 0x3d, 0x8e, \n  0x28, 0x3b, 0x7d, 0x85, 0x83, 0x23, 0xf5, 0xff, 0xe7, 0x9c, 0xc9, 0x2e, 0xea, 0x1c, 0x91, 0x29, \n  0x27, 0xe8, 0xc3, 0x8b, 0x4e, 0x17, 0xbd, 0xe3, 0x63, 0x2e, 0x79, 0x17, 0x89, 0x15, 0x63, 0x17, \n  0xa9, 0x69, 0xa4, 0xe5, 0x22, 0x2a, 0x93, 0x33, 0xa2, 0x42, 0xf6, 0x94, 0x18, 0x1a, 0x51, 0x32, \n  0xc7, 0xc9, 0x94, 0xb2, 0x21, 0xaa, 0x90, 0x1a, 0x87, 0x21, 0x65, 0x53, 0x45, 0x86, 0xc2, 0xcb, \n  0xdc, 0xcc, 0x16, 0x18, 0x22, 0x5a, 0x86, 0x61, 0xcd, 0x37, 0x09, 0x89, 0xb0, 0xa4, 0x67, 0x15, \n  0x28, 0x28, 0xeb, 0x9d, 0xd3, 0x50, 0xce, 0x86, 0x68, 0xe7, 0xfe, 0xa0, 0x82, 0xe5, 0x60, 0x91, \n  0x08, 0x20, 0x77, 0xcc, 0xa9, 0x41, 0x7a, 0x1a, 0x56, 0xb8, 0x5a, 0x09, 0xd0, 0x7a, 0xe7, 0x41, \n  0x65, 0x70, 0x1b, 0x06, 0x64, 0x82, 0x59, 0x06, 0x76, 0x99, 0xf3, 0xd0, 0xc0, 0xdf, 0xb9, 0x2f, \n  0x10, 0xc1, 0xa2, 0x9d, 0x88, 0x45, 0x74, 0x38, 0xe3, 0x67, 0x24, 0x29, 0x23, 0x66, 0x3d, 0x2e, \n  0xf0, 0xf2, 0x00, 0xfe, 0x6e, 0x36, 0x9e, 0x88, 0x71, 0x45, 0xa5, 0x65, 0xb2, 0x31, 0x18, 0xb4, \n  0x1b, 0x6c, 0x56, 0x05, 0x2b, 0x15, 0xd7, 0x81, 0x59, 0x42, 0x25, 0x8f, 0x0b, 0xa2, 0x4c, 0x06, \n  0xf0, 0xd7, 0x82, 0x56, 0x03, 0xff, 0xe4, 0xe1, 0x10, 0xa2, 0x27, 0x13, 0x42, 0xce, 0x28, 0x39, \n  0x47, 0x11, 0xed, 0x16, 0x1f, 0x54, 0xb9, 0x79, 0x45, 0xcc, 0x41, 0x4b, 0x2a, 0xb6, 0x63, 0x0e, \n  0xbd, 0xa4, 0xdd, 0x78, 0xd9, 0x0a, 0x4e, 0xa5, 0x7f, 0xed, 0x3c, 0x6e, 0xd1, 0x8d, 0x3c, 0xc6, \n  0x01, 0x95, 0x17, 0x43, 0x34, 0x68, 0x35, 0x49, 0xcd, 0xbe, 0xb8, 0x53, 0xe1, 0xe1, 0xde, 0x9c, \n  0x7f, 0xd3, 0x5b, 0x08, 0x92, 0xf4, 0x04, 0x89, 0x48, 0x20, 0x8d, 0x6b, 0xed, 0x9d, 0x93, 0xf1, \n  0x29, 0x95, 0x8d, 0xed, 0x2a, 0xef, 0x5b, 0xc1, 0x5b, 0xcf, 0x89, 0x93, 0x7a, 0x89, 0x6e, 0x3d, \n  0x83, 0x59, 0x8c, 0x80, 0x95, 0x7b, 0x21, 0x09, 0x78, 0x82, 0x35, 0x09, 0x16, 0x2c, 0x24, 0x09, \n  0xec, 0xc7, 0xed, 0xe9, 0xb9, 0x1d, 0xe1, 0x31, 0x89, 0xb6, 0xab, 0x2c, 0xb7, 0xe2, 0x2c, 0xf5, \n  0x77, 0x77, 0x23, 0x26, 0xf9, 0x9b, 0xea, 0x70, 0x85, 0xfd, 0xa9, 0x9d, 0x58, 0x00, 0x60, 0x5d, \n  0xc3, 0xb3, 0xe1, 0x70, 0x4c, 0x26, 0x3c, 0x21, 0x15, 0xb4, 0x5f, 0x01, 0xb9, 0x0a, 0xdc, 0x61, \n  0x48, 0x05, 0x1e, 0x47, 0x24, 0xd4, 0xc8, 0xb0, 0x8d, 0x1b, 0x92, 0x09, 0x5e, 0x44, 0xd2, 0xc6, \n  0xdf, 0xfe, 0x83, 0x0d, 0x66, 0x0c, 0x66, 0x24, 0x38, 0x25, 0xe1, 0x90, 0x71, 0xe9, 0xae, 0xa6, \n  0xf7, 0xea, 0xf0, 0x36, 0x8e, 0x78, 0x70, 0x7a, 0x33, 0x88, 0xeb, 0x56, 0x19, 0xcc, 0xf0, 0xc8, \n  0x86, 0xe6, 0xb2, 0xe9, 0xa7, 0x00, 0xb3, 0x58, 0x8e, 0x86, 0x7d, 0x47, 0xf5, 0xa9, 0x79, 0x6f, \n  0xb6, 0xe8, 0xda, 0x2f, 0xdc, 0xca, 0x1d, 0x99, 0x91, 0xe9, 0xfc, 0xf1, 0x77, 0xff, 0xf0, 0x4b, \n  0xe4, 0xec, 0xa1, 0xfe, 0x97, 0xe8, 0x39, 0x8f, 0x42, 0x92, 0x20, 0x1a, 0x70, 0x86, 0xbe, 0xec, \n  0x17, 0x3b, 0xa4, 0x1b, 0x33, 0x5e, 0x48, 0xde, 0x52, 0xe9, 0x6e, 0x6a, 0x8c, 0x5a, 0xd9, 0x62, \n  0xbb, 0xfd, 0x4a, 0xfe, 0x36, 0x5d, 0xc9, 0x9b, 0x98, 0x30, 0x34, 0xb1, 0x2c, 0xa7, 0x49, 0xbb, \n  0xb4, 0x99, 0xe8, 0xbb, 0x74, 0xa2, 0xa7, 0x5a, 0x0a, 0xd0, 0x04, 0xbc, 0x9f, 0x1b, 0x40, 0x5c, \n  0x03, 0x6c, 0xbe, 0x5c, 0xca, 0x56, 0xf0, 0xfd, 0x6b, 0x0a, 0xdf, 0x7b, 0xb2, 0xb4, 0x01, 0xd7, \n  0x34, 0x15, 0x9d, 0x4f, 0xdb, 0x4c, 0xf5, 0xdb, 0xff, 0xfa, 0x9f, 0x7f, 0xff, 0x75, 0x3a, 0xdb, \n  0x8b, 0x39, 0x9e, 0x92, 0x36, 0xd3, 0xdd, 0x9a, 0x11, 0x1c, 0x56, 0x55, 0x78, 0xe3, 0xf6, 0xa9, \n  0x76, 0xe8, 0x32, 0xa3, 0x25, 0x5a, 0x7a, 0x06, 0x46, 0x37, 0x64, 0x60, 0xf3, 0xe2, 0xaa, 0x9e, \n  0xc5, 0xca, 0xac, 0x00, 0xab, 0x46, 0x29, 0xfa, 0x66, 0xd7, 0xe3, 0xde, 0xbd, 0x7b, 0x7b, 0xe6, \n  0xed, 0x8e, 0x10, 0x9b, 0x98, 0xde, 0x02, 0x3c, 0x5f, 0x6d, 0xe9, 0xbb, 0x26, 0xaf, 0x4a, 0x4a, \n  0x3e, 0x6f, 0xbb, 0xfc, 0x94, 0x1b, 0x77, 0x07, 0x5f, 0xd4, 0x18, 0xc8, 0x36, 0xb8, 0x49, 0x48, \n  0x25, 0x4f, 0xba, 0xb7, 0xe2, 0xc4, 0x68, 0x9d, 0x5c, 0x79, 0x09, 0x16, 0x02, 0xd6, 0xaf, 0x6c, \n  0x77, 0xf0, 0x85, 0x0d, 0x4e, 0x0b, 0x7c, 0x06, 0xf2, 0x11, 0x62, 0x75, 0x14, 0xee, 0xdb, 0xf1, \n  0x20, 0x24, 0x96, 0x0b, 0x71, 0xb5, 0xe5, 0xdf, 0xb5, 0xad, 0x7e, 0x67, 0x60, 0xf6, 0x2b, 0x53, \n  0x73, 0xaf, 0x06, 0x9c, 0x89, 0x78, 0x45, 0x64, 0x55, 0x98, 0x32, 0x57, 0x6a, 0x30, 0xb0, 0x71, \n  0x7a, 0x2f, 0xc3, 0xb0, 0xdd, 0x9c, 0xb9, 0x75, 0x8e, 0x13, 0x46, 0xd9, 0xb4, 0x3c, 0xf8, 0x6a, \n  0xcb, 0x1a, 0x54, 0x38, 0xa9, 0x8a, 0x66, 0x9e, 0x60, 0x36, 0x25, 0x7b, 0x56, 0x07, 0xc5, 0xa6, \n  0x93, 0x24, 0xe7, 0x91, 0xa4, 0xf1, 0xc6, 0x88, 0x5e, 0x05, 0x2f, 0x76, 0xdb, 0x31, 0x9a, 0xe6, \n  0xcb, 0x2a, 0x9a, 0xce, 0xa8, 0xa0, 0x63, 0x1a, 0x29, 0x7b, 0x66, 0x46, 0xc3, 0x90, 0xb0, 0xab, \n  0xc4, 0x22, 0xcc, 0x9e, 0xd8, 0xca, 0x62, 0x4d, 0x77, 0x47, 0x63, 0x48, 0xc8, 0x10, 0x31, 0xa9, \n  0x8e, 0xb2, 0xe2, 0xd8, 0xbb, 0xb5, 0x0c, 0x34, 0x68, 0xa4, 0x71, 0x6a, 0x56, 0xdb, 0xd0, 0x9e, \n  0xc7, 0x86, 0xfa, 0x6e, 0xb5, 0x43, 0x6e, 0x45, 0x1c, 0x87, 0x06, 0xa6, 0x69, 0x27, 0x21, 0xbd, \n  0x9d, 0xc1, 0xe0, 0x6c, 0xb6, 0x89, 0x36, 0x83, 0x0e, 0xe7, 0x7b, 0x56, 0x06, 0xad, 0x0c, 0xb6, \n  0xe2, 0x8f, 0x9d, 0x41, 0xb3, 0x8a, 0xcf, 0x07, 0x86, 0x2a, 0x71, 0xa1, 0xb5, 0x3b, 0x57, 0x13, \n  0x37, 0x48, 0x1b, 0x21, 0x3f, 0x0d, 0x16, 0xf4, 0x28, 0xeb, 0xf1, 0x85, 0x6c, 0x40, 0x9e, 0x2f, \n  0x66, 0xfc, 0xbc, 0xe2, 0x4c, 0x19, 0x37, 0xc0, 0x15, 0x14, 0x3b, 0x0d, 0x63, 0xf6, 0xe6, 0x62, \n  0x7a, 0x15, 0x03, 0xf6, 0x6a, 0xfb, 0x72, 0x1a, 0x07, 0xac, 0x28, 0x08, 0x85, 0x9b, 0x09, 0x4f, \n  0xe6, 0x43, 0xfd, 0x35, 0xc2, 0x92, 0xfc, 0xc4, 0xed, 0xdd, 0x1f, 0x7c, 0xe1, 0x59, 0x44, 0xa7, \n  0x2a, 0x54, 0x39, 0xce, 0xbe, 0x6b, 0xd7, 0x5e, 0xdf, 0x3f, 0x25, 0x17, 0x93, 0x04, 0xcf, 0x89, \n  0x40, 0x22, 0xa6, 0x8c, 0x91, 0xa4, 0x87, 0x19, 0x9d, 0x97, 0x51, 0x30, 0xf8, 0xa2, 0xfc, 0xa4, \n  0x04, 0x66, 0xc2, 0x25, 0x96, 0xc4, 0x1d, 0x78, 0xc5, 0x56, 0x85, 0xb9, 0xe0, 0x0f, 0x68, 0xc3, \n  0x76, 0x43, 0xdd, 0x7d, 0x30, 0x08, 0xc9, 0xb4, 0x3a, 0x9e, 0x59, 0x11, 0xe6, 0x81, 0xd7, 0x4e, \n  0x52, 0x42, 0x7e, 0xbe, 0xa0, 0x09, 0x09, 0xbd, 0xeb, 0x91, 0xd3, 0x12, 0x89, 0x53, 0xe4, 0xac, \n  0x12, 0x6e, 0x13, 0x82, 0x2a, 0x7a, 0x76, 0x4d, 0x44, 0x85, 0x45, 0xa4, 0x1e, 0x7a, 0x81, 0x28, \n  0x3b, 0x02, 0x51, 0x36, 0xa1, 0x8c, 0x4a, 0xa2, 0xdc, 0x22, 0x6c, 0xd5, 0x86, 0x0f, 0x72, 0x51, \n  0x27, 0x42, 0x2c, 0x01, 0x64, 0x10, 0xff, 0x4c, 0x86, 0x15, 0x54, 0x31, 0x4e, 0x08, 0x93, 0xf5, \n  0xe1, 0xe6, 0xbb, 0xbb, 0x06, 0xfb, 0x6a, 0x09, 0x8c, 0xa6, 0x34, 0x6c, 0xda, 0x7a, 0xcc, 0xcb, \n  0x8d, 0x40, 0x71, 0x4e, 0x22, 0x88, 0x28, 0x1b, 0xf7, 0x09, 0xa5, 0xe8, 0x41, 0xe7, 0x80, 0xcd, \n  0xdc, 0x7b, 0xf8, 0xf0, 0xe1, 0xc3, 0xca, 0x34, 0xa9, 0x2a, 0x7b, 0x50, 0x35, 0x4b, 0x33, 0x4d, \n  0x06, 0xaf, 0x2a, 0x11, 0xd8, 0xfd, 0xbe, 0x0a, 0x6b, 0xad, 0x32, 0x2d, 0x41, 0x42, 0x63, 0x89, \n  0x44, 0x12, 0x8c, 0x9c, 0x99, 0x94, 0xb1, 0x18, 0xf6, 0xfb, 0x41, 0xc8, 0xfc, 0x8f, 0x22, 0x24, \n  0x11, 0x3d, 0x4b, 0x7c, 0x46, 0x64, 0x9f, 0xc5, 0xf3, 0x3e, 0x0e, 0x48, 0x6f, 0xbc, 0xa0, 0x51, \n  0x28, 0xbe, 0xbf, 0xe3, 0xdf, 0xbb, 0xeb, 0xdf, 0xed, 0x8b, 0x24, 0xe8, 0x41, 0x04, 0x96, 0xf1, \n  0x80, 0xb3, 0x49, 0x44, 0x03, 0x09, 0x8d, 0xfc, 0x39, 0x85, 0xde, 0xce, 0xc1, 0x7e, 0x5f, 0x8f, \n  0x9d, 0xce, 0x54, 0x98, 0x2f, 0x97, 0xcd, 0x39, 0xc3, 0x09, 0x02, 0xf3, 0xb6, 0x8b, 0x26, 0xe2, \n  0x05, 0x9b, 0xe4, 0x5d, 0x9e, 0x80, 0x33, 0x21, 0xd1, 0x6d, 0x34, 0x42, 0x93, 0x05, 0x0b, 0x80, \n  0x01, 0x5c, 0x52, 0x61, 0xdf, 0x84, 0xc8, 0x45, 0xc2, 0x50, 0xc8, 0x83, 0xc5, 0x9c, 0x30, 0xe9, \n  0x4f, 0x89, 0x7c, 0x16, 0x11, 0xf8, 0xfa, 0xf8, 0xe2, 0x45, 0xe8, 0x12, 0xcf, 0x18, 0x83, 0xd6, \n  0x43, 0x07, 0x11, 0x17, 0x44, 0x05, 0x33, 0x6b, 0xa7, 0xa0, 0x13, 0xe4, 0xae, 0xc6, 0x87, 0xec, \n  0x53, 0x6e, 0x12, 0xf1, 0xf8, 0xe2, 0x49, 0x84, 0x85, 0x78, 0x8d, 0xe7, 0xc4, 0x75, 0x72, 0xf1, \n  0x51, 0xc7, 0xf3, 0x23, 0xc2, 0xa6, 0x72, 0xe6, 0xa1, 0x62, 0xdf, 0x84, 0xcc, 0xf9, 0x19, 0x79, \n  0x32, 0xa3, 0x11, 0x40, 0x67, 0x0e, 0x91, 0x23, 0xd4, 0x45, 0x8c, 0x9c, 0x3f, 0x9b, 0x6f, 0xb6, \n  0xf6, 0x20, 0x21, 0x58, 0x92, 0x14, 0xb2, 0xe2, 0xd2, 0x73, 0xf3, 0x64, 0x03, 0x22, 0x41, 0xe4, \n  0x4b, 0xad, 0xe6, 0x5d, 0xd2, 0x45, 0xac, 0x32, 0xfc, 0x6d, 0xd7, 0xc9, 0x6d, 0x03, 0x8e, 0xe7, \n  0x2b, 0xd9, 0xfb, 0xea, 0xfd, 0xab, 0x97, 0x68, 0x84, 0x18, 0xfa, 0xf6, 0x5b, 0xe4, 0x38, 0xdd, \n  0x62, 0x17, 0x82, 0x0e, 0x73, 0xdd, 0x1c, 0xcf, 0x0f, 0x00, 0x39, 0x2f, 0xa9, 0x90, 0x3e, 0x0e, \n  0x43, 0xd7, 0x51, 0x5b, 0x94, 0xe3, 0xa1, 0xa1, 0xad, 0x95, 0xc6, 0xce, 0xaa, 0x61, 0x69, 0xf8, \n  0x22, 0x26, 0x15, 0x23, 0xfb, 0x3a, 0xbc, 0x84, 0x46, 0x6a, 0x6e, 0xe7, 0x1c, 0x53, 0xe9, 0xa0, \n  0x21, 0x72, 0xd2, 0x68, 0x93, 0x63, 0xc4, 0xef, 0x0a, 0x05, 0x09, 0xc1, 0x21, 0xc4, 0x8e, 0x8e, \n  0xe8, 0x37, 0xc4, 0x42, 0x79, 0x82, 0xf6, 0xd1, 0xce, 0x60, 0xf7, 0x9e, 0x57, 0xd5, 0xd2, 0x29, \n  0xfe, 0x09, 0xda, 0x46, 0x0e, 0x7a, 0xec, 0x54, 0x76, 0x9c, 0x04, 0xb9, 0xc0, 0xde, 0x0c, 0x8d, \n  0x50, 0x6f, 0x67, 0x0f, 0xb1, 0xed, 0xed, 0x6e, 0x59, 0xfd, 0xef, 0xde, 0x43, 0xfb, 0x30, 0x47, \n  0x7f, 0xa4, 0x27, 0xd9, 0x43, 0x86, 0x79, 0xf6, 0x8c, 0x64, 0x27, 0xbe, 0xe4, 0xcf, 0xe9, 0x92, \n  0x84, 0xee, 0xae, 0x87, 0xb6, 0xd1, 0xb1, 0x83, 0x7e, 0x48, 0x1f, 0x3b, 0x5d, 0xe4, 0xa0, 0x57, \n  0xe9, 0xe7, 0x0f, 0xd2, 0xcf, 0xf7, 0xfa, 0xf3, 0x2d, 0x7d, 0xec, 0x9c, 0x1c, 0xb3, 0x93, 0x26, \n  0x94, 0x4c, 0x12, 0x22, 0x66, 0x47, 0xca, 0x65, 0x71, 0x4d, 0x4c, 0xa1, 0xbd, 0x99, 0x12, 0x3f, \n  0x38, 0x6e, 0xda, 0x11, 0x2c, 0x11, 0xdf, 0xf7, 0xca, 0xd8, 0x00, 0x44, 0x10, 0xe0, 0x1b, 0x72, \n  0x8e, 0x7e, 0xf2, 0xea, 0xe5, 0x57, 0x52, 0xc6, 0xef, 0xc8, 0xcf, 0x17, 0x44, 0x94, 0x15, 0x2c, \n  0xf1, 0x39, 0x03, 0xce, 0xc8, 0xb3, 0xbe, 0x67, 0xda, 0x23, 0x15, 0x6a, 0xbb, 0xa8, 0xdc, 0x1f, \n  0xfe, 0xec, 0x0e, 0x06, 0x68, 0x6b, 0x84, 0x88, 0x9f, 0x3a, 0x5e, 0x87, 0x08, 0xf8, 0x09, 0xe6, \n  0x7c, 0x96, 0x24, 0x3c, 0x01, 0x4a, 0x0f, 0x91, 0x0b, 0x74, 0x71, 0xb5, 0xd6, 0x41, 0x23, 0xf4, \n  0x57, 0x47, 0x6f, 0x5e, 0xfb, 0x31, 0xe4, 0xab, 0x5d, 0xe2, 0x27, 0x44, 0xc4, 0x9c, 0x09, 0x02, \n  0xd1, 0x10, 0xcf, 0xf3, 0xe5, 0x45, 0xac, 0x89, 0xdc, 0x43, 0x65, 0x8e, 0x57, 0xb8, 0x53, 0x63, \n  0xf8, 0x54, 0xbc, 0x39, 0x45, 0x87, 0x30, 0xee, 0xf6, 0x08, 0x39, 0x90, 0x01, 0x87, 0x60, 0x52, \n  0x38, 0xea, 0xa4, 0xfe, 0x56, 0x07, 0x12, 0x56, 0xa3, 0xce, 0xa0, 0x83, 0x78, 0x2c, 0xe9, 0x7c, \n  0x31, 0x87, 0xef, 0x11, 0x3f, 0x1f, 0x75, 0x1e, 0x0e, 0x3a, 0x68, 0x46, 0xa7, 0xb3, 0x51, 0xe7, \n  0xe1, 0xfd, 0x0e, 0x9a, 0xe3, 0xe5, 0xa8, 0xb3, 0x33, 0x18, 0x74, 0xd0, 0x19, 0x8e, 0x16, 0x64, \n  0xd4, 0x71, 0xd0, 0x36, 0x72, 0x77, 0xd0, 0x36, 0x7a, 0x85, 0xe5, 0xcc, 0x57, 0xc6, 0xa6, 0xfb, \n  0xf0, 0x21, 0xfa, 0x32, 0x9b, 0x78, 0x21, 0x48, 0xf8, 0xf8, 0x42, 0x12, 0x81, 0xfa, 0xd9, 0x23, \n  0xc9, 0x25, 0x8e, 0xd4, 0x33, 0x0f, 0x98, 0xc3, 0xe9, 0x20, 0x95, 0x60, 0x4f, 0xc7, 0x92, 0x68, \n  0x54, 0x64, 0xfd, 0x4a, 0x2f, 0xd4, 0xab, 0x0c, 0xae, 0xc6, 0x41, 0x13, 0x08, 0x45, 0xf0, 0x09, \n  0x82, 0x71, 0xea, 0x87, 0x48, 0x27, 0x3e, 0x80, 0x96, 0x12, 0xbe, 0xee, 0xf7, 0x15, 0x4a, 0x0e, \n  0x6a, 0x50, 0xb8, 0x60, 0x62, 0x11, 0xc7, 0x3c, 0x91, 0x24, 0x84, 0xf2, 0x00, 0x81, 0xee, 0xdc, \n  0xc9, 0xf0, 0x89, 0xf6, 0x55, 0xae, 0x00, 0xf0, 0x99, 0xba, 0x1f, 0x9d, 0x83, 0x1f, 0x3f, 0x7a, \n  0xf7, 0xfa, 0xc5, 0xeb, 0x1f, 0xe8, 0x37, 0x4a, 0x89, 0x8c, 0x3a, 0xa9, 0x33, 0xd2, 0x51, 0xf5, \n  0x05, 0x3a, 0x6b, 0xaa, 0xc2, 0x4f, 0x98, 0x32, 0x81, 0x72, 0xe3, 0xab, 0xa8, 0x13, 0x03, 0x53, \n  0x6f, 0xb8, 0x3f, 0x4e, 0xfa, 0x0a, 0x4c, 0x1b, 0x14, 0x0a, 0x78, 0x98, 0xe4, 0x20, 0xfd, 0x70, \n  0x3c, 0xe0, 0xa0, 0x94, 0xd0, 0x6a, 0x76, 0xa5, 0x8c, 0x46, 0x9d, 0x8a, 0x3b, 0x90, 0x90, 0x70, \n  0x4f, 0x7f, 0x3b, 0x9f, 0x51, 0x49, 0xf6, 0xf2, 0xa9, 0xdf, 0x31, 0x8f, 0xc2, 0xce, 0xc1, 0x8b, \n  0xd7, 0x2f, 0xde, 0xa3, 0x67, 0xef, 0xde, 0xbd, 0x79, 0x87, 0xb6, 0xb2, 0xe1, 0x0d, 0x18, 0xb2, \n  0x09, 0x1e, 0x33, 0xb4, 0x75, 0x8e, 0xde, 0xbe, 0x78, 0xfe, 0xfc, 0xc8, 0x01, 0x21, 0xc8, 0xe8, \n  0x02, 0x2c, 0x0c, 0xd8, 0xbc, 0xed, 0x3a, 0xf3, 0xd3, 0x90, 0x26, 0x8e, 0x97, 0x2a, 0xd0, 0xd4, \n  0x00, 0x04, 0x19, 0xd6, 0x26, 0xa0, 0xe3, 0xd9, 0xcd, 0x4c, 0xb5, 0x37, 0x55, 0x04, 0x36, 0x26, \n  0xcc, 0x75, 0x7e, 0xf0, 0xec, 0x3d, 0x68, 0x99, 0x7e, 0x0a, 0x65, 0x17, 0x6d, 0x0d, 0xbc, 0x4a, \n  0x53, 0x41, 0x58, 0xe8, 0xb2, 0x45, 0x14, 0x79, 0xf5, 0x5a, 0xa8, 0x22, 0xaf, 0xbf, 0x28, 0x57, \n  0x47, 0x90, 0x44, 0xba, 0x8e, 0x42, 0xdb, 0x10, 0x74, 0xdf, 0xf6, 0x5a, 0xda, 0xb7, 0x91, 0x73, \n  0x82, 0xf4, 0x93, 0x82, 0x1c, 0xd7, 0xcf, 0x18, 0x60, 0x06, 0xbb, 0xe1, 0x6b, 0x72, 0xfe, 0x44, \n  0x07, 0x2b, 0x0d, 0xca, 0x2f, 0xd5, 0xbc, 0x5b, 0x5b, 0x40, 0x0c, 0x7c, 0x46, 0x1e, 0x4b, 0xe6, \n  0x78, 0x7e, 0x96, 0x7f, 0x80, 0x1d, 0x71, 0x6b, 0x0b, 0x8c, 0x21, 0x9a, 0xcc, 0x5d, 0xe7, 0xc9, \n  0x0c, 0x82, 0x18, 0x02, 0x49, 0x8e, 0x2e, 0xf8, 0x22, 0x59, 0x6d, 0x5e, 0xe8, 0x9c, 0x46, 0x11, \n  0x1a, 0x13, 0x14, 0x71, 0x21, 0x61, 0x8b, 0xb9, 0xe0, 0x0b, 0xc5, 0xa1, 0x94, 0x2d, 0x88, 0xe3, \n  0x29, 0x22, 0x11, 0xa6, 0xe4, 0x0a, 0x9f, 0x91, 0xa7, 0x54, 0x04, 0x38, 0x09, 0x1f, 0x4b, 0x26, \n  0xdc, 0xad, 0x9d, 0x32, 0x42, 0xb7, 0x06, 0x0d, 0xab, 0x32, 0x0f, 0x44, 0x8c, 0x6a, 0xbd, 0xba, \n  0xa0, 0x11, 0xda, 0x22, 0xdd, 0x4a, 0xc3, 0x70, 0x35, 0x52, 0xb9, 0x6d, 0x3d, 0x2c, 0x53, 0x22, \n  0xdf, 0x2a, 0x93, 0x5a, 0xa7, 0x0d, 0xec, 0x06, 0x8d, 0x0b, 0x1b, 0x85, 0xfa, 0x47, 0xd1, 0x34, \n  0x91, 0xe2, 0xc7, 0x54, 0xce, 0x5c, 0xa7, 0xef, 0x78, 0xe8, 0x10, 0x11, 0xd8, 0xde, 0xfb, 0x8a, \n  0xbe, 0x9e, 0x4f, 0x58, 0x58, 0x7c, 0xe9, 0x8b, 0x88, 0x06, 0xc4, 0x1d, 0x74, 0x51, 0x6f, 0x07, \n  0x64, 0x94, 0x78, 0xbe, 0x58, 0x8c, 0x85, 0x4c, 0xc0, 0xcc, 0x19, 0x74, 0x11, 0xf1, 0x23, 0x2c, \n  0xe4, 0x0b, 0x70, 0xe7, 0xdf, 0x4c, 0x54, 0xaf, 0x26, 0xbe, 0x50, 0x46, 0xd5, 0x57, 0x2a, 0xfc, \n  0xac, 0xec, 0xa4, 0x2e, 0x92, 0x15, 0xc0, 0x61, 0x2f, 0xc2, 0x68, 0x84, 0x6e, 0x17, 0xac, 0xae, \n  0xcc, 0x94, 0xa3, 0x7a, 0xdf, 0x7b, 0x36, 0x77, 0x1d, 0x95, 0xa7, 0x70, 0xca, 0x1e, 0x0e, 0xd5, \n  0xe2, 0x39, 0x42, 0x0e, 0x28, 0xa5, 0xb2, 0xf4, 0x53, 0x7f, 0xbe, 0x00, 0x95, 0x16, 0x41, 0x8b, \n  0xad, 0x9d, 0xca, 0x5b, 0x50, 0x62, 0xd0, 0x37, 0xc4, 0x12, 0x97, 0xfb, 0x62, 0x1f, 0xc7, 0x31, \n  0x61, 0xa1, 0xb6, 0x37, 0xa9, 0x67, 0xd8, 0x95, 0x79, 0x13, 0x74, 0xdc, 0xa7, 0x40, 0x5e, 0x27, \n  0xc6, 0x72, 0xf6, 0x42, 0xb5, 0xe8, 0x96, 0x1b, 0x64, 0xe0, 0x83, 0xf5, 0x5b, 0x7d, 0x9b, 0x01, \n  0x08, 0x03, 0x54, 0xdf, 0xa6, 0x96, 0xda, 0x8f, 0x60, 0xaf, 0x83, 0x56, 0xfd, 0xfa, 0x35, 0x70, \n  0xd3, 0x1a, 0xc4, 0x7a, 0x0d, 0xba, 0x60, 0xab, 0xb2, 0x08, 0x51, 0xb4, 0x56, 0xfe, 0xf0, 0xfb, \n  0x5f, 0x41, 0x16, 0xe2, 0x43, 0x0c, 0xa6, 0x46, 0xfd, 0x84, 0xc2, 0x34, 0x61, 0xd4, 0x38, 0x61, \n  0x94, 0x62, 0x4d, 0x2b, 0xdb, 0x6e, 0xf9, 0x65, 0x45, 0xf7, 0x42, 0x62, 0xb7, 0xda, 0xac, 0x00, \n  0xb4, 0xca, 0xbc, 0xbd, 0x3a, 0x7d, 0x5a, 0x1d, 0xaf, 0x08, 0x71, 0x64, 0x82, 0x38, 0x6c, 0x84, \n  0x38, 0xac, 0xcc, 0xf6, 0x1d, 0x7a, 0x75, 0xfa, 0xdc, 0xc0, 0x91, 0xc5, 0xe9, 0x42, 0xd3, 0x74, \n  0xc9, 0x7a, 0x3a, 0xd8, 0xd2, 0x2a, 0x93, 0x25, 0x29, 0x7a, 0x74, 0x8a, 0xe0, 0xb1, 0x82, 0x48, \n  0x94, 0xe7, 0x49, 0xda, 0xa1, 0xa9, 0x08, 0x4e, 0x62, 0x02, 0x27, 0x68, 0x5c, 0x7d, 0x90, 0x02, \n  0x94, 0xa9, 0xc1, 0x6e, 0xe5, 0x75, 0x11, 0x39, 0x7f, 0xff, 0xdf, 0x08, 0xd4, 0x6a, 0xb5, 0x9d, \n  0x06, 0x59, 0xe7, 0xea, 0x5e, 0x92, 0x09, 0xd8, 0x5a, 0xce, 0xdd, 0x41, 0xbc, 0xac, 0xb6, 0xcc, \n  0xeb, 0xce, 0x41, 0x65, 0xe9, 0xf9, 0x35, 0x05, 0xa6, 0x35, 0xc5, 0x8d, 0x6b, 0x8a, 0xd3, 0x35, \n  0xe5, 0x34, 0x76, 0xb7, 0xd2, 0xa2, 0x20, 0x16, 0xbf, 0xfa, 0x4f, 0x94, 0x6e, 0x14, 0xd5, 0x96, \n  0xed, 0xc1, 0x8d, 0xcb, 0xfb, 0x94, 0x4b, 0xd6, 0xb0, 0x62, 0xc7, 0xf3, 0x52, 0xb8, 0x70, 0x40, \n  0x5e, 0x52, 0x76, 0x5a, 0xf1, 0x22, 0x4b, 0x40, 0x7d, 0xf7, 0x1f, 0x20, 0xab, 0x8f, 0x02, 0x82, \n  0x9e, 0x29, 0x6e, 0xa9, 0xb6, 0x9f, 0x25, 0x64, 0x02, 0x4d, 0xb3, 0x10, 0xc6, 0x94, 0xca, 0xd9, \n  0x62, 0xec, 0x07, 0x7c, 0xde, 0xc7, 0x1f, 0xf1, 0x92, 0x27, 0x53, 0x88, 0x4d, 0xf4, 0xcf, 0xe9, \n  0x29, 0xed, 0xa7, 0x19, 0xd8, 0xde, 0x0f, 0xc9, 0xc5, 0x98, 0xe3, 0x24, 0xec, 0x1d, 0xcd, 0x78, \n  0x22, 0x83, 0x85, 0x14, 0xd5, 0x71, 0x25, 0x4e, 0xa6, 0x44, 0x51, 0xf0, 0xaf, 0xc7, 0x11, 0x36, \n  0x41, 0x9a, 0x90, 0x48, 0xb3, 0x25, 0x18, 0x41, 0x24, 0x41, 0x8c, 0x27, 0x64, 0x42, 0x92, 0x84, \n  0x18, 0xa0, 0x34, 0x71, 0xc6, 0x8e, 0x81, 0x33, 0xb2, 0x96, 0xba, 0x68, 0x6c, 0x84, 0x9c, 0x5b, \n  0x83, 0xc1, 0x83, 0x07, 0x41, 0x60, 0x6b, 0x07, 0x5a, 0xf7, 0xe9, 0xaa, 0x8e, 0xc5, 0x26, 0x25, \n  0x45, 0x12, 0x91, 0x0a, 0x89, 0x2a, 0x12, 0x9b, 0x51, 0x29, 0x33, 0xe8, 0x2a, 0x02, 0x5c, 0x70, \n  0xff, 0x52, 0x6f, 0xde, 0x6b, 0x12, 0xce, 0xca, 0xf6, 0xc5, 0x59, 0xa0, 0x2c, 0xa5, 0xfa, 0x58, \n  0xc7, 0xca, 0xe3, 0x33, 0xb8, 0x7b, 0xe0, 0xec, 0x8d, 0x10, 0xf5, 0x61, 0xf3, 0x14, 0x69, 0xd8, \n  0x25, 0xf5, 0x1c, 0x56, 0x8f, 0x8f, 0x07, 0x27, 0x6a, 0x1f, 0x32, 0x58, 0xcb, 0xdc, 0x57, 0x7e, \n  0x56, 0xc5, 0xd4, 0xf8, 0xf6, 0x5b, 0xe4, 0xa6, 0xef, 0xf4, 0xbe, 0x84, 0xb6, 0xb3, 0xb6, 0x9e, \n  0x7d, 0x18, 0x34, 0xaa, 0x18, 0x3a, 0x59, 0x27, 0xb0, 0x4c, 0xd5, 0x28, 0x6c, 0x23, 0x03, 0x5b, \n  0x00, 0x8a, 0x22, 0x1a, 0x9c, 0x36, 0x63, 0xc8, 0x86, 0x09, 0xe6, 0x83, 0x64, 0xe8, 0xdd, 0xce, \n  0x5d, 0x63, 0xa4, 0xbb, 0x5a, 0xcf, 0x26, 0xf0, 0x84, 0xed, 0xe1, 0x51, 0xd1, 0x3d, 0x34, 0x5a, \n  0xa1, 0x58, 0x26, 0x74, 0xee, 0x7a, 0x06, 0x12, 0x3a, 0x0e, 0x1a, 0x8d, 0x46, 0x48, 0xa2, 0xc3, \n  0xcc, 0xb4, 0x7f, 0xb4, 0x72, 0xd0, 0xd0, 0x7c, 0x21, 0x24, 0x18, 0xcc, 0x53, 0x7a, 0x46, 0x74, \n  0xf4, 0x48, 0x56, 0x0c, 0xbf, 0xb4, 0xd7, 0xf3, 0xcc, 0xa9, 0xd3, 0x9d, 0x18, 0x97, 0x88, 0xb0, \n  0x10, 0x9d, 0x53, 0x39, 0x43, 0x18, 0x75, 0xfa, 0x1d, 0x55, 0x93, 0x8e, 0x03, 0x49, 0x12, 0x35, \n  0x90, 0x46, 0xcc, 0x13, 0x65, 0xe3, 0xb9, 0x72, 0x23, 0x34, 0x44, 0x7f, 0x2a, 0x34, 0xe8, 0xaa, \n  0x14, 0x0b, 0x26, 0xdc, 0x32, 0x2a, 0x80, 0x6b, 0xa5, 0x72, 0x4c, 0xfb, 0x8e, 0x89, 0x4d, 0x4b, \n  0x2b, 0xde, 0x68, 0xc9, 0x41, 0xfb, 0x25, 0x4b, 0x1f, 0x36, 0x52, 0x77, 0xa3, 0xe1, 0xe3, 0x4d, \n  0x86, 0x4f, 0xf7, 0x34, 0xb7, 0x36, 0x25, 0x62, 0xb1, 0xe6, 0xdf, 0x27, 0x84, 0x98, 0x63, 0x9e, \n  0x19, 0xa5, 0x6e, 0xbb, 0x4e, 0x5a, 0x14, 0xe0, 0x18, 0x0c, 0x7a, 0xbc, 0x56, 0x95, 0x21, 0x3d, \n  0x73, 0xaa, 0x94, 0x2c, 0x45, 0x02, 0xb3, 0xd9, 0xa9, 0x71, 0x35, 0x3a, 0x18, 0xbd, 0x94, 0xda, \n  0xdf, 0x89, 0x23, 0x2a, 0xdd, 0x8e, 0xdf, 0xf1, 0xfc, 0x98, 0xc7, 0xae, 0xe7, 0x4b, 0xfe, 0x92, \n  0x9f, 0x93, 0xe4, 0x09, 0x16, 0xc4, 0xc0, 0x32, 0x59, 0x5f, 0x30, 0x80, 0x8f, 0x3b, 0x72, 0x29, \n  0x3b, 0x5d, 0xd4, 0x99, 0xc9, 0x79, 0xfa, 0x11, 0xc1, 0xe7, 0x47, 0xa1, 0xff, 0xe5, 0x0c, 0x3e, \n  0x83, 0x38, 0x56, 0x1f, 0x42, 0x3d, 0x5d, 0xea, 0x26, 0x17, 0x58, 0x7f, 0x06, 0xe2, 0x0c, 0x3e, \n  0xa6, 0xdf, 0x74, 0x4e, 0x2a, 0x53, 0xa5, 0xce, 0xd9, 0x52, 0x82, 0x29, 0x1d, 0x44, 0x8b, 0x90, \n  0x08, 0x77, 0x29, 0x15, 0xc3, 0x01, 0xec, 0xa3, 0x11, 0x22, 0xb5, 0xc4, 0xb6, 0xe0, 0x84, 0xd7, \n  0xe0, 0xe4, 0x3a, 0x28, 0x51, 0x08, 0x89, 0xd9, 0x54, 0xad, 0x3d, 0x4e, 0x3f, 0x88, 0xfa, 0x9c, \n  0xd2, 0x09, 0x7c, 0xd0, 0x80, 0xc3, 0x87, 0x38, 0x53, 0x0f, 0xc7, 0xf3, 0xb8, 0x66, 0xd1, 0xeb, \n  0x25, 0xcb, 0xf2, 0x8c, 0x97, 0x2d, 0x89, 0x2f, 0xcc, 0x1c, 0x97, 0xe7, 0xba, 0x94, 0xa7, 0x16, \n  0x91, 0x63, 0x52, 0x0e, 0xa4, 0xb0, 0x93, 0x4a, 0x53, 0x93, 0xcc, 0x0f, 0x4d, 0x07, 0x8a, 0xa8, \n  0x71, 0x20, 0x59, 0x18, 0x08, 0x9b, 0xb4, 0x04, 0x2e, 0xee, 0xee, 0x2a, 0xc6, 0x75, 0xf0, 0x87, \n  0x7f, 0xf9, 0x35, 0x18, 0x62, 0xef, 0x94, 0x86, 0xed, 0xbf, 0xe2, 0x67, 0xa4, 0x26, 0x5e, 0x85, \n  0x4d, 0xc2, 0x2c, 0x8d, 0xcb, 0x57, 0x54, 0xcb, 0x72, 0x31, 0xc5, 0xa4, 0x88, 0x69, 0x71, 0x71, \n  0xc2, 0xe7, 0xb1, 0x74, 0x1d, 0x0d, 0x87, 0x8a, 0xf4, 0x30, 0x15, 0x9b, 0x94, 0xdc, 0x01, 0x0c, \n  0x5b, 0xba, 0x43, 0xd4, 0x09, 0x02, 0x62, 0x18, 0x36, 0x44, 0x0c, 0xdf, 0x18, 0x7c, 0x8b, 0x5d, \n  0xd6, 0x45, 0xd8, 0x10, 0x75, 0xbf, 0xac, 0x3e, 0x32, 0x2d, 0xb3, 0x88, 0xed, 0xee, 0x0d, 0x62, \n  0xfb, 0xf7, 0x7f, 0x07, 0xd8, 0x7e, 0xca, 0xcf, 0x55, 0x3c, 0xfc, 0xe6, 0x50, 0xad, 0xf0, 0xd8, \n  0x45, 0x74, 0xaf, 0x99, 0x12, 0x5d, 0x73, 0x13, 0x6c, 0x89, 0x3b, 0x2a, 0x3b, 0x92, 0x1a, 0xa3, \n  0xfc, 0x5e, 0x21, 0x4a, 0x88, 0x4d, 0xd1, 0xc1, 0xb5, 0x49, 0xb8, 0x0a, 0xdb, 0xa5, 0x61, 0x85, \n  0x71, 0xc4, 0xc7, 0x8e, 0xb5, 0x75, 0xcb, 0x7c, 0x81, 0x29, 0x13, 0xf1, 0x38, 0xe2, 0x63, 0xf7, \n  0x78, 0x3d, 0xe1, 0x49, 0xb7, 0xa6, 0xa3, 0x22, 0xe6, 0x45, 0x4c, 0x86, 0xc8, 0x81, 0x33, 0x39, \n  0x34, 0x50, 0xe6, 0x76, 0x9f, 0x07, 0x92, 0x48, 0x21, 0x13, 0x82, 0xe7, 0x8e, 0xbd, 0xf3, 0xa5, \n  0x57, 0x37, 0x70, 0x57, 0x25, 0x89, 0xdc, 0x73, 0xca, 0x42, 0x7e, 0xee, 0x7f, 0x78, 0xf7, 0x12, \n  0xb4, 0x6a, 0xfa, 0x3f, 0x5d, 0x7f, 0xff, 0xe1, 0xdd, 0x4b, 0x2f, 0x4d, 0xf0, 0xbd, 0x19, 0x7f, \n  0x24, 0x01, 0x3c, 0x30, 0x04, 0x9b, 0x8a, 0x83, 0x82, 0x4e, 0xb1, 0x64, 0x07, 0xc1, 0x0d, 0xdb, \n  0xb3, 0xf7, 0x96, 0xbe, 0x20, 0xf2, 0x91, 0x94, 0x09, 0x1d, 0x2f, 0x24, 0x71, 0x9d, 0x30, 0xe5, \n  0x42, 0xa0, 0x9d, 0x9f, 0x90, 0x38, 0xc2, 0x01, 0x71, 0xfb, 0x5f, 0xfb, 0x5f, 0x1e, 0xff, 0xec, \n  0x67, 0xfd, 0x93, 0x7e, 0x17, 0x39, 0x8e, 0x67, 0xa3, 0xa7, 0x69, 0x3c, 0x70, 0xd8, 0x94, 0xb0, \n  0xd6, 0xf4, 0x29, 0x66, 0xfb, 0x8a, 0xba, 0xaf, 0x76, 0x2a, 0x25, 0x0b, 0x6e, 0xfb, 0xa1, 0xf3, \n  0x29, 0x59, 0x69, 0x41, 0xe9, 0xa5, 0xf9, 0xb1, 0x95, 0x29, 0x55, 0xa0, 0xfb, 0xca, 0x5a, 0x85, \n  0xbb, 0x4c, 0x07, 0x82, 0x3f, 0x9d, 0x7a, 0xf9, 0xe3, 0xef, 0x7e, 0xf3, 0x4b, 0xd0, 0x2f, 0x3f, \n  0x82, 0xc2, 0x47, 0x2c, 0x54, 0xc9, 0xc1, 0xa7, 0x51, 0xe7, 0x36, 0x25, 0x22, 0x25, 0x99, 0xc7, \n  0x2a, 0x05, 0xfd, 0x48, 0x40, 0x80, 0xde, 0x65, 0xed, 0xf0, 0xe5, 0x5d, 0x49, 0x0d, 0xe3, 0xd4, \n  0x59, 0x4e, 0x8f, 0x37, 0x3c, 0x3b, 0x83, 0x10, 0xbf, 0xcd, 0x57, 0x2e, 0x74, 0xd0, 0xe5, 0x72, \n  0xd0, 0x74, 0xe0, 0xdc, 0x0c, 0x01, 0x66, 0xe6, 0x24, 0xd8, 0xa7, 0x24, 0xf6, 0x3f, 0xff, 0x46, \n  0x6d, 0x26, 0x24, 0x22, 0x92, 0xfc, 0x49, 0xc9, 0xbc, 0xca, 0x87, 0xe8, 0xb9, 0x75, 0xb1, 0xf7, \n  0x6a, 0xcf, 0x3e, 0xd4, 0x09, 0x8f, 0x45, 0x1b, 0xda, 0x5f, 0x5a, 0x0c, 0xab, 0xc8, 0x1a, 0x96, \n  0x37, 0x98, 0x44, 0xca, 0x5e, 0x37, 0x41, 0xaa, 0xc3, 0xf4, 0xa5, 0x0a, 0x83, 0x20, 0xe1, 0x51, \n  0xf4, 0x9e, 0xc7, 0xa0, 0x8e, 0x57, 0xaf, 0xb2, 0x2f, 0xa9, 0x2a, 0x5d, 0xb7, 0x32, 0x0f, 0xcb, \n  0x2d, 0xc3, 0xaa, 0x08, 0x50, 0xf3, 0xb8, 0xd0, 0xcc, 0x3c, 0x70, 0xa4, 0xac, 0xe3, 0x20, 0xa2, \n  0x84, 0xc9, 0x9f, 0x40, 0x6c, 0xc2, 0xdc, 0x2c, 0xcc, 0x35, 0xfb, 0x29, 0xda, 0x36, 0xee, 0xfb, \n  0x58, 0x97, 0x60, 0xbc, 0x4e, 0x03, 0xf5, 0xf9, 0x3a, 0x96, 0x1a, 0xc9, 0xc8, 0x85, 0x65, 0x55, \n  0xd5, 0x58, 0x5d, 0xdb, 0x48, 0xc7, 0xbb, 0x22, 0x20, 0x7a, 0x35, 0xe2, 0x95, 0x6f, 0x29, 0x39, \n  0x04, 0x36, 0x43, 0x7b, 0x43, 0x17, 0xdc, 0x63, 0x81, 0x86, 0x39, 0x1f, 0xd1, 0x66, 0x58, 0xb7, \n  0x37, 0xae, 0x5b, 0x1a, 0xd8, 0xd9, 0x80, 0x54, 0xf9, 0x88, 0xcc, 0xf3, 0x03, 0x68, 0xfb, 0x9a, \n  0x87, 0x3a, 0xc2, 0x94, 0x1e, 0x40, 0xb1, 0x6d, 0x3c, 0xdd, 0x16, 0xc6, 0x79, 0x4b, 0x31, 0x57, \n  0xbb, 0x0c, 0x54, 0x1b, 0x98, 0x6d, 0xc7, 0x7f, 0xfb, 0x2d, 0x7a, 0xc2, 0xa3, 0x08, 0xc7, 0xa2, \n  0x4e, 0xda, 0xaf, 0x20, 0xf1, 0x2d, 0xa5, 0x5e, 0xa7, 0x0a, 0x6d, 0xf8, 0x51, 0x79, 0xac, 0x1b, \n  0xd9, 0x67, 0x1b, 0x35, 0x66, 0x15, 0x9d, 0xd4, 0x6e, 0x77, 0x1a, 0x35, 0xe7, 0x3f, 0x7e, 0x87, \n  0xde, 0xe9, 0xd2, 0x96, 0x06, 0x4c, 0xd2, 0x4f, 0x86, 0xc9, 0xc4, 0x55, 0xea, 0x6d, 0x23, 0x8c, \n  0xa9, 0xe0, 0x90, 0x85, 0x39, 0xfe, 0x09, 0x3d, 0x5b, 0xc6, 0x98, 0x85, 0x7f, 0xa6, 0xac, 0x31, \n  0xb8, 0x69, 0x54, 0xd4, 0xb9, 0x31, 0x1b, 0x72, 0x8f, 0x55, 0x18, 0xaf, 0xef, 0x31, 0xdf, 0x00, \n  0xca, 0xf7, 0xea, 0xdd, 0x9e, 0x6b, 0x78, 0xcf, 0x57, 0xf1, 0xa0, 0x37, 0x97, 0xe7, 0x4f, 0x4f, \n  0x91, 0xd6, 0x96, 0xd0, 0xa7, 0x15, 0x80, 0x8a, 0x55, 0xa4, 0x83, 0xbc, 0xed, 0xec, 0x22, 0x0b, \n  0x5e, 0x4d, 0xa6, 0xb2, 0x8b, 0x2d, 0x4e, 0x96, 0xdd, 0xb9, 0xc2, 0xb6, 0xc0, 0x12, 0xe4, 0x50, \n  0xb1, 0xcf, 0x27, 0x13, 0x41, 0xe4, 0x8f, 0xa1, 0xfa, 0xd8, 0x6c, 0x6f, 0xcc, 0x72, 0xcd, 0xbe, \n  0x52, 0x85, 0x4e, 0x7b, 0x66, 0x3b, 0x73, 0xce, 0x17, 0x82, 0xf0, 0x85, 0x6c, 0x8e, 0xf6, 0xea, \n  0x14, 0xe2, 0xca, 0xd4, 0xd9, 0x47, 0x11, 0x18, 0x4e, 0xeb, 0x07, 0x07, 0xca, 0xae, 0x08, 0xf2, \n  0x0f, 0x7f, 0x8a, 0xf6, 0x51, 0x58, 0x7c, 0x70, 0xa0, 0x8c, 0x8a, 0x99, 0x42, 0xec, 0x9a, 0x4c, \n  0xf8, 0xea, 0x86, 0x67, 0x68, 0x37, 0x3c, 0xd2, 0xb8, 0x61, 0x73, 0x44, 0x42, 0x59, 0x27, 0x16, \n  0x99, 0xcb, 0x15, 0xe8, 0x56, 0x4b, 0x7f, 0x36, 0xaa, 0x75, 0x64, 0x60, 0x29, 0x48, 0x43, 0x6a, \n  0xca, 0xea, 0x80, 0x17, 0x6b, 0xa7, 0x00, 0x65, 0xa5, 0x27, 0x5b, 0x90, 0xa6, 0xb8, 0x73, 0x27, \n  0xab, 0x17, 0x7d, 0x8b, 0xe5, 0xac, 0x5c, 0x38, 0x69, 0x19, 0x3a, 0xdf, 0x43, 0xb6, 0xf0, 0x0e, \n  0x5d, 0xa6, 0xaa, 0xed, 0xae, 0x3c, 0x51, 0x56, 0xca, 0x7a, 0x65, 0x3a, 0x27, 0x0d, 0x91, 0x5b, \n  0x15, 0xcf, 0x12, 0x06, 0x22, 0xe6, 0x09, 0x38, 0xe8, 0x22, 0x07, 0x4a, 0x9c, 0xc1, 0x95, 0x54, \n  0x55, 0x98, 0xaa, 0xa2, 0xb4, 0xe3, 0xfb, 0xbe, 0x51, 0xcf, 0xb9, 0xcb, 0x79, 0x04, 0x44, 0xb4, \n  0x05, 0xd3, 0xb2, 0x98, 0x97, 0xa2, 0x6a, 0xda, 0xd6, 0xe2, 0x52, 0x9a, 0x72, 0x9e, 0xc2, 0x12, \n  0xc1, 0x6b, 0xc7, 0xb0, 0x80, 0x8e, 0x1b, 0xe0, 0x59, 0x69, 0xe3, 0x59, 0xa9, 0x8c, 0x97, 0x2d, \n  0x55, 0x35, 0x70, 0xdb, 0xc5, 0x9e, 0xce, 0x6a, 0x31, 0xe5, 0xc8, 0x40, 0xf9, 0x56, 0xea, 0x21, \n  0x29, 0x7d, 0xa5, 0xc4, 0xd9, 0xf9, 0xf0, 0x12, 0x32, 0x67, 0x88, 0xf9, 0x12, 0x4f, 0x95, 0x17, \n  0x93, 0x32, 0xec, 0x3a, 0xb0, 0x63, 0xe5, 0xf4, 0x76, 0x5e, 0x84, 0x4e, 0x9d, 0x09, 0x9e, 0x48, \n  0xb7, 0x7d, 0x87, 0x62, 0xb5, 0xb6, 0x0a, 0x66, 0x2a, 0x18, 0xe1, 0x1b, 0xd4, 0xaa, 0xc1, 0x0e, \n  0xec, 0x47, 0x3c, 0xc0, 0x11, 0x79, 0xc2, 0xe7, 0x70, 0xde, 0xc4, 0x65, 0xea, 0xa1, 0xaa, 0x5c, \n  0x53, 0xed, 0x2a, 0xaf, 0xe1, 0xa1, 0x57, 0x17, 0x67, 0x6c, 0xb0, 0x01, 0xda, 0xb9, 0x42, 0xda, \n  0x48, 0x23, 0x5e, 0x3b, 0x8f, 0xa8, 0x50, 0xee, 0xae, 0xf6, 0xf0, 0x34, 0xf7, 0xdc, 0x55, 0x8c, \n  0x36, 0xd8, 0x43, 0x02, 0xed, 0x23, 0xbc, 0x87, 0xc4, 0xf6, 0x76, 0x23, 0xc2, 0xb2, 0x62, 0x25, \n  0x5d, 0x0b, 0xa7, 0x12, 0x4b, 0xae, 0x2a, 0x5e, 0x3a, 0x16, 0x27, 0x5e, 0x86, 0xbb, 0x02, 0x0d, \n  0x6c, 0xae, 0xff, 0xd5, 0xb2, 0x23, 0x16, 0x02, 0x62, 0x5d, 0x0d, 0x01, 0x49, 0x56, 0xa0, 0xa2, \n  0x3a, 0x6d, 0xe0, 0xa8, 0x02, 0xc3, 0x75, 0x5a, 0xbf, 0xdb, 0x3c, 0x1a, 0x29, 0x57, 0x59, 0xb4, \n  0xe8, 0x43, 0x21, 0x1a, 0x78, 0xa8, 0x76, 0xb2, 0xc2, 0x69, 0x0a, 0xb9, 0x94, 0x2a, 0x07, 0x9c, \n  0x45, 0x0b, 0x2b, 0x0d, 0xe8, 0x7c, 0xda, 0x6a, 0x02, 0x52, 0x3a, 0xda, 0x01, 0xe6, 0xe0, 0x3e, \n  0x3d, 0x70, 0x2b, 0x45, 0xde, 0x52, 0x2d, 0xd6, 0xdb, 0xef, 0x53, 0xab, 0xcd, 0x64, 0xaf, 0xfd, \n  0x20, 0x5e, 0xab, 0x2e, 0xad, 0x73, 0xc0, 0x4d, 0xdb, 0x06, 0x50, 0xcc, 0x6b, 0xee, 0x76, 0xd9, \n  0xdc, 0xa4, 0x2d, 0xdc, 0x3a, 0x6c, 0x32, 0x6f, 0x3c, 0x51, 0x64, 0xa7, 0x03, 0x24, 0x9d, 0x09, \n  0x93, 0x69, 0x8d, 0x92, 0xdb, 0x06, 0x63, 0x59, 0x25, 0x10, 0x8f, 0xdf, 0x26, 0x3c, 0xc6, 0x53, \n  0xac, 0xf5, 0x77, 0xcb, 0x9e, 0x2a, 0x78, 0x06, 0x98, 0xea, 0x16, 0x8b, 0x89, 0x3f, 0x31, 0xb6, \n  0xea, 0x9b, 0x5c, 0x02, 0x50, 0xa1, 0xae, 0xd7, 0x41, 0xa1, 0x0f, 0xc7, 0x36, 0xbd, 0x72, 0xbc, \n  0xe7, 0xd3, 0x0a, 0x7d, 0x1b, 0x69, 0xdf, 0x6b, 0x37, 0x79, 0x63, 0xf5, 0x6f, 0x8d, 0xd2, 0x59, \n  0x17, 0x06, 0x2b, 0xf7, 0x78, 0xcc, 0x97, 0x6d, 0xc4, 0xee, 0x8c, 0xd3, 0x30, 0x2d, 0x05, 0x52, \n  0x26, 0x9a, 0xfa, 0xc7, 0xa5, 0xf9, 0x02, 0x3e, 0x27, 0xfb, 0xee, 0x78, 0x1b, 0xcb, 0x31, 0x6d, \n  0xd3, 0xc5, 0xcd, 0xed, 0x33, 0xea, 0x3a, 0x16, 0xa8, 0x27, 0x03, 0xe9, 0x48, 0xab, 0xeb, 0x6b, \n  0x32, 0x88, 0xf6, 0x99, 0x65, 0x2b, 0x6d, 0xb9, 0x41, 0x45, 0x99, 0xb9, 0x7f, 0x16, 0x8a, 0x00, \n  0xeb, 0xf6, 0x4f, 0xab, 0x45, 0xe4, 0xf5, 0xb4, 0xdf, 0x1a, 0xf4, 0xc3, 0xdc, 0x77, 0x08, 0xb6, \n  0x81, 0x3d, 0x45, 0xdb, 0xc6, 0x58, 0x8a, 0xf1, 0x16, 0x85, 0x80, 0xff, 0xd7, 0xa3, 0x1b, 0xe8, \n  0xd1, 0x9d, 0x3f, 0x43, 0x3d, 0xda, 0xa4, 0x74, 0x64, 0xa9, 0xa4, 0xbb, 0xc6, 0xc2, 0xac, 0xb3, \n  0x18, 0x73, 0x87, 0x04, 0xbc, 0x55, 0x3d, 0x24, 0xb1, 0x84, 0x30, 0x20, 0x38, 0x91, 0x3b, 0x76, \n  0x27, 0x4b, 0xc7, 0xee, 0x2c, 0xeb, 0xcf, 0x69, 0x37, 0xf0, 0x0d, 0x2c, 0x61, 0x44, 0x7d, 0xd4, \n  0x09, 0xd7, 0x45, 0x19, 0x6d, 0x13, 0xa8, 0x53, 0x67, 0xf9, 0xea, 0xc9, 0xc0, 0x15, 0xde, 0xd5, \n  0x53, 0x93, 0xa9, 0x73, 0x56, 0x3a, 0x25, 0x05, 0x37, 0x0c, 0x1e, 0x86, 0x34, 0x19, 0x29, 0x57, \n  0xd0, 0x56, 0x0e, 0x91, 0xf5, 0x35, 0x1d, 0x9b, 0xaa, 0xf3, 0x58, 0x83, 0xba, 0x0a, 0x5a, 0xe5, \n  0x46, 0xa9, 0x4a, 0x2a, 0x03, 0x4f, 0x30, 0xad, 0xf5, 0x58, 0x17, 0x91, 0x96, 0x73, 0xc5, 0x76, \n  0x3f, 0xa8, 0xec, 0xff, 0xaa, 0x98, 0x63, 0xc9, 0x01, 0x86, 0xf3, 0x51, 0x9d, 0x55, 0xdc, 0xcb, \n  0xea, 0x0f, 0x33, 0x63, 0x79, 0x2e, 0x5b, 0x15, 0xe6, 0xb2, 0xeb, 0x38, 0xd1, 0x61, 0xd6, 0xd0, \n  0x12, 0x00, 0xcd, 0xf9, 0x4d, 0xe8, 0x39, 0x4f, 0xe6, 0x4f, 0xb1, 0xc4, 0x35, 0x75, 0x55, 0x6e, \n  0x7a, 0xd2, 0xc5, 0x0c, 0xd4, 0xba, 0x95, 0x48, 0x02, 0x07, 0xf0, 0xdc, 0xc8, 0x34, 0x6f, 0x3f, \n  0x68, 0xa6, 0x81, 0xc3, 0x12, 0x4e, 0x23, 0x9f, 0xc8, 0x96, 0x84, 0x5b, 0x58, 0x98, 0xa4, 0x4c, \n  0x35, 0x15, 0xa2, 0xbc, 0xe1, 0xb0, 0x45, 0x0e, 0xe3, 0xc4, 0x86, 0x71, 0xd6, 0x8c, 0x71, 0x56, \n  0xc6, 0x78, 0x0b, 0x64, 0x3e, 0x7d, 0xf6, 0xf2, 0xd9, 0xfb, 0x67, 0x1b, 0xe0, 0xb3, 0xa1, 0x5a, \n  0x7b, 0xe5, 0x1f, 0x16, 0x53, 0xa8, 0xb9, 0xeb, 0xd1, 0xaa, 0x25, 0xf1, 0xba, 0xb0, 0xbe, 0x72, \n  0xd2, 0xa9, 0xe2, 0x71, 0x57, 0x62, 0xdb, 0x72, 0x46, 0x85, 0x1f, 0x44, 0x04, 0x27, 0xaf, 0x30, \n  0x65, 0x6f, 0x31, 0x53, 0xc7, 0x0f, 0xea, 0xa3, 0x36, 0xb7, 0xdd, 0xf4, 0x94, 0x8d, 0xe9, 0xc8, \n  0xa7, 0xad, 0xfa, 0x61, 0xd5, 0x29, 0x3b, 0x9a, 0xb3, 0x41, 0x5f, 0xb9, 0x41, 0x7e, 0x58, 0x96, \n  0x82, 0xf4, 0x21, 0x3d, 0xcb, 0xce, 0xd2, 0xe6, 0x6e, 0x3b, 0xd2, 0x97, 0x1d, 0x75, 0x0e, 0x5c, \n  0x55, 0x3d, 0x00, 0x55, 0xdd, 0x13, 0x38, 0x63, 0x8b, 0x78, 0x02, 0x71, 0x87, 0x39, 0xd6, 0x95, \n  0xde, 0xab, 0x53, 0xbb, 0xde, 0x7e, 0x3f, 0xa4, 0x67, 0x07, 0xce, 0x26, 0xc5, 0xc7, 0x0a, 0xb3, \n  0x39, 0xf7, 0xb1, 0x5d, 0x55, 0x37, 0x04, 0xfe, 0xba, 0x28, 0xda, 0x33, 0x22, 0xd0, 0xb8, 0x17, \n  0x1a, 0x70, 0x60, 0x3c, 0x7a, 0xaa, 0x4c, 0x76, 0x98, 0xf9, 0x10, 0x39, 0x70, 0x31, 0xe9, 0x84, \n  0x32, 0x12, 0x2a, 0x67, 0x04, 0x5c, 0x02, 0x3e, 0x41, 0x38, 0x20, 0x2a, 0xff, 0x6b, 0x19, 0xd4, \n  0x32, 0x3f, 0xee, 0xfe, 0x85, 0x33, 0x48, 0xfe, 0xb4, 0xf5, 0xfa, 0x88, 0x75, 0xe7, 0x00, 0x8e, \n  0x07, 0x69, 0x80, 0x50, 0xc0, 0x17, 0x51, 0xa8, 0x38, 0x42, 0x9d, 0xb2, 0xc5, 0x21, 0x9c, 0xf6, \n  0x4e, 0xf8, 0x1c, 0xc9, 0x19, 0x41, 0xaa, 0xfa, 0x87, 0x11, 0xe0, 0x98, 0x44, 0x3f, 0x55, 0x6a, \n  0x40, 0xdd, 0x5d, 0xf2, 0x51, 0x20, 0x3f, 0xbb, 0xa1, 0x11, 0x14, 0x9e, 0xe4, 0xaa, 0x2c, 0x0a, \n  0x81, 0x10, 0x93, 0xc4, 0xf7, 0xfd, 0xec, 0x14, 0x78, 0x9c, 0x10, 0x75, 0x12, 0x5d, 0xae, 0xfc, \n  0x19, 0xd1, 0xc9, 0x80, 0x5a, 0x5f, 0xed, 0xa2, 0xae, 0x70, 0xec, 0x1c, 0xec, 0xf7, 0xe3, 0x84, \n  0x18, 0xe3, 0x25, 0xae, 0x68, 0xd2, 0x93, 0xcd, 0xf1, 0xd8, 0xdb, 0x2a, 0x0e, 0x94, 0x41, 0xe1, \n  0x94, 0x9d, 0x2c, 0xc5, 0xd6, 0x79, 0xbb, 0xea, 0xaa, 0x25, 0x69, 0xa2, 0x58, 0xc0, 0xe9, 0x19, \n  0x9b, 0xe8, 0xaa, 0x37, 0x7d, 0xb0, 0x02, 0x56, 0xf0, 0x21, 0x89, 0x74, 0x02, 0x82, 0x6b, 0x4e, \n  0x76, 0xa3, 0xcd, 0xb8, 0x35, 0xfa, 0x4b, 0xe7, 0x56, 0x3a, 0x9f, 0xaa, 0xcb, 0x75, 0xd4, 0xd6, \n  0x19, 0x69, 0x83, 0x27, 0xe5, 0x93, 0x39, 0x5e, 0xa6, 0xb7, 0x97, 0xab, 0x3b, 0xf1, 0xe0, 0xda, \n  0x87, 0xec, 0x62, 0xd7, 0xec, 0x81, 0xba, 0xee, 0x53, 0x71, 0xd1, 0xea, 0x9a, 0x26, 0x7d, 0x3d, \n  0x53, 0x07, 0xf5, 0x0f, 0x54, 0xe8, 0xaf, 0xb8, 0x15, 0xb8, 0x9b, 0x1d, 0xe7, 0x28, 0xab, 0x3c, \n  0x5d, 0x86, 0xd7, 0xac, 0xf8, 0x6c, 0xaa, 0xcd, 0xa0, 0x05, 0xe1, 0x06, 0x95, 0x2d, 0x93, 0x7e, \n  0xf3, 0xd2, 0x7d, 0xd3, 0xd0, 0xe7, 0x2a, 0x24, 0xdf, 0xbb, 0x06, 0xc9, 0xf7, 0x36, 0x20, 0x79, \n  0x2b, 0x70, 0x61, 0xd1, 0x56, 0x9d, 0x6d, 0x15, 0xe4, 0x9b, 0x51, 0x74, 0x3e, 0x3a, 0x9a, 0xf1, \n  0x73, 0xa5, 0xbc, 0x80, 0x96, 0xe9, 0xc5, 0xab, 0xd7, 0x54, 0x5d, 0x35, 0x85, 0x50, 0xa2, 0xcd, \n  0x9d, 0x2e, 0x39, 0x1d, 0xb2, 0x59, 0xad, 0xf6, 0xf5, 0x95, 0x1b, 0x2a, 0xdd, 0x7e, 0x64, 0x57, \n  0x69, 0x56, 0x17, 0x39, 0x53, 0x6b, 0x86, 0xd7, 0x97, 0x88, 0x44, 0x82, 0xd8, 0x56, 0x90, 0xd7, \n  0x81, 0x7b, 0x9b, 0x66, 0x1f, 0x4d, 0xb2, 0x9a, 0x4b, 0x81, 0x36, 0x4b, 0xa9, 0xe2, 0xc2, 0xec, \n  0x22, 0x90, 0x51, 0xe1, 0x22, 0x10, 0x8b, 0x77, 0x9f, 0x28, 0xc7, 0xca, 0x74, 0xf2, 0x04, 0x16, \n  0x09, 0xe3, 0xf5, 0x76, 0x54, 0x34, 0xb4, 0xee, 0x0e, 0x85, 0xf6, 0x03, 0x5a, 0xb0, 0x56, 0xb8, \n  0x37, 0xe9, 0xf8, 0x44, 0x57, 0xb1, 0x93, 0x3d, 0xd4, 0xdb, 0xd1, 0x39, 0xc3, 0xf2, 0xdc, 0x60, \n  0x29, 0x6d, 0xdd, 0x86, 0xe4, 0x14, 0xf2, 0xea, 0x72, 0x77, 0xf1, 0x42, 0xcc, 0x1a, 0x2a, 0xc7, \n  0x0d, 0x09, 0x72, 0x6b, 0xce, 0xcb, 0xd1, 0x72, 0x8d, 0x0e, 0xb3, 0xa1, 0x15, 0x2c, 0xc3, 0xc6, \n  0x89, 0x82, 0x2b, 0x94, 0xba, 0x9a, 0x78, 0x61, 0x7d, 0xa0, 0xb3, 0xc0, 0x0a, 0x6d, 0x5d, 0x71, \n  0xdd, 0x75, 0xe5, 0xd5, 0xfd, 0x5f, 0x72, 0xbe, 0xf5, 0x3d, 0x18, 0x2a, 0x49, 0xdd, 0xc2, 0xb5, \n  0x7e, 0x73, 0x74, 0x83, 0xbe, 0xb5, 0x8d, 0x50, 0xfa, 0xb8, 0x65, 0xb3, 0xcc, 0x96, 0xa9, 0xa4, \n  0xfa, 0xb5, 0x74, 0xbd, 0xcd, 0x07, 0x98, 0xd7, 0x47, 0x97, 0xc9, 0x5f, 0xa8, 0xbf, 0xbe, 0x59, \n  0xf0, 0x63, 0xb3, 0xa3, 0xd5, 0x5a, 0x41, 0x19, 0xa8, 0xd6, 0xe6, 0x14, 0xa9, 0xbe, 0x8c, 0x20, \n  0x4b, 0x46, 0xa9, 0x22, 0x10, 0x5a, 0xa1, 0x6a, 0xc3, 0x39, 0xc7, 0x35, 0xe6, 0x9c, 0x38, 0xc2, \n  0x94, 0x39, 0xe6, 0x9a, 0x2a, 0xe0, 0xff, 0xbe, 0x7b, 0x38, 0xfc, 0x99, 0xef, 0x1e, 0x7f, 0xed, \n  0x9f, 0x6c, 0x7b, 0xde, 0xe1, 0xed, 0xbe, 0x4f, 0x96, 0x04, 0x02, 0x7d, 0xc7, 0x3b, 0x27, 0x16, \n  0xcb, 0x2b, 0x9f, 0x08, 0xb2, 0xe8, 0x46, 0x71, 0x4e, 0x65, 0x30, 0x43, 0x75, 0x35, 0xfe, 0x58, \n  0x10, 0xa4, 0x72, 0xcd, 0xc3, 0x1a, 0xf5, 0xba, 0x5e, 0x41, 0x4d, 0xa0, 0x79, 0x9c, 0x10, 0x7c, \n  0xba, 0x57, 0x37, 0xcd, 0x4c, 0xce, 0x1b, 0xa7, 0x81, 0x73, 0xad, 0xd7, 0x9b, 0xe5, 0xa3, 0x68, \n  0x9c, 0xe4, 0x23, 0x3e, 0xc3, 0xfa, 0xb2, 0xca, 0xeb, 0x4d, 0x15, 0x08, 0xeb, 0x5c, 0xba, 0x81, \n  0x68, 0x6c, 0x11, 0xcf, 0xe2, 0xfa, 0x06, 0x0a, 0x21, 0xc3, 0xfa, 0x05, 0x73, 0x56, 0xdf, 0x62, \n  0xd9, 0x34, 0x04, 0x1c, 0x11, 0xae, 0x6f, 0x11, 0x88, 0xb3, 0x26, 0xb4, 0xca, 0xd6, 0xc5, 0x8c, \n  0x69, 0x54, 0x8d, 0xd5, 0x4a, 0x73, 0xca, 0xdf, 0x50, 0x22, 0xc2, 0x56, 0x17, 0x3e, 0x38, 0x7d, \n  0x75, 0x59, 0xb3, 0x0f, 0xac, 0x54, 0x16, 0xed, 0x5c, 0x07, 0x9d, 0x14, 0x55, 0xa7, 0xf2, 0xa1, \n  0xc0, 0xcc, 0xde, 0x10, 0xaf, 0x0e, 0x62, 0xa9, 0xeb, 0x8d, 0xe6, 0x58, 0x92, 0xba, 0x71, 0xa9, \n  0x8e, 0xdc, 0xa8, 0x18, 0xe0, 0x5f, 0x07, 0x71, 0xbc, 0x3e, 0xd1, 0xaf, 0xba, 0xf7, 0xb5, 0x90, \n  0xc0, 0x1d, 0x56, 0xea, 0xbf, 0xea, 0x76, 0x3c, 0xeb, 0x95, 0x46, 0x8b, 0x28, 0xaa, 0x9e, 0x3e, \n  0x07, 0x1f, 0x1a, 0xa2, 0x15, 0xa0, 0x10, 0xab, 0x96, 0x65, 0xae, 0x3a, 0xb1, 0x71, 0x8b, 0x31, \n  0x16, 0x68, 0xa5, 0xc5, 0x59, 0x22, 0x2b, 0xce, 0x82, 0x64, 0x48, 0xa1, 0x3a, 0x4b, 0x18, 0xa3, \n  0xda, 0x09, 0x21, 0x79, 0xfb, 0xd4, 0xad, 0x56, 0x1a, 0x9a, 0xba, 0xd5, 0x96, 0xe8, 0xd9, 0xeb, \n  0xf1, 0xae, 0xb6, 0xb2, 0xfc, 0xa1, 0xfe, 0x2b, 0x84, 0x0e, 0x4c, 0x9d, 0xac, 0x81, 0x80, 0x36, \n  0xde, 0x67, 0x7a, 0xdd, 0x9e, 0x85, 0x06, 0xa3, 0x1c, 0x0d, 0x20, 0x72, 0x02, 0x27, 0x28, 0xd5, \n  0x25, 0x59, 0xae, 0x68, 0xac, 0x7e, 0x8c, 0x74, 0x8c, 0xf8, 0x48, 0xfd, 0x1c, 0x93, 0x2d, 0x8d, \n  0x69, 0xbd, 0x64, 0x4e, 0xdd, 0xb0, 0x01, 0xf4, 0x2c, 0x47, 0x17, 0xae, 0x97, 0x7c, 0xaa, 0x54, \n  0x43, 0xe6, 0x0d, 0xd0, 0x86, 0xb4, 0x42, 0x63, 0xa0, 0x2c, 0x69, 0x0c, 0x59, 0x59, 0x93, 0x6c, \n  0xa2, 0x6d, 0x7a, 0x2d, 0xd5, 0x49, 0xe9, 0x3e, 0xb7, 0xae, 0xaf, 0x88, 0xe0, 0x42, 0xe0, 0x23, \n  0x22, 0x84, 0xc2, 0x34, 0xd0, 0xe9, 0x15, 0x0f, 0x89, 0x0b, 0xd7, 0x1b, 0xf5, 0xe7, 0x3c, 0x24, \n  0xa9, 0x90, 0x57, 0xef, 0x00, 0x23, 0xf2, 0xfd, 0x8c, 0xcc, 0xd3, 0x96, 0x12, 0xbe, 0xaa, 0xa6, \n  0xb8, 0xda, 0xf4, 0xb6, 0x62, 0xb4, 0x23, 0x75, 0x22, 0x4c, 0x1f, 0x46, 0xdc, 0x41, 0x7d, 0x34, \n  0xa8, 0xb4, 0x2b, 0x03, 0xf2, 0x41, 0x90, 0x23, 0x3e, 0x91, 0xef, 0xf1, 0x58, 0xb8, 0xd5, 0xd5, \n  0x57, 0xdb, 0xbf, 0xc7, 0x63, 0x55, 0xe8, 0xb5, 0x6b, 0x84, 0xf6, 0x2b, 0x3a, 0x9d, 0x45, 0x10, \n  0xa1, 0x7a, 0x14, 0xc0, 0x25, 0xe0, 0x2f, 0x29, 0x23, 0xc6, 0x51, 0x05, 0x91, 0x10, 0x85, 0x78, \n  0x9b, 0x50, 0x26, 0x5f, 0xa9, 0x28, 0x96, 0x41, 0x22, 0x23, 0xb8, 0x9e, 0x69, 0x8e, 0x59, 0x28, \n  0xa0, 0x58, 0xed, 0x89, 0xfe, 0xee, 0x1a, 0xf8, 0x06, 0xd2, 0xd7, 0x43, 0x7d, 0x31, 0x97, 0x49, \n  0x56, 0xc6, 0x94, 0x85, 0x3f, 0x24, 0x17, 0x43, 0x9b, 0xfd, 0x72, 0x0e, 0x3f, 0x99, 0xe3, 0x3c, \n  0x91, 0x49, 0xd4, 0x3b, 0xb2, 0x55, 0xcf, 0xcc, 0x71, 0x00, 0x6d, 0x34, 0x0c, 0xbd, 0x23, 0x83, \n  0x15, 0x76, 0x69, 0x12, 0xa0, 0x25, 0x09, 0x86, 0x6d, 0xea, 0x13, 0x88, 0xf1, 0x32, 0x14, 0xdb, \n  0xb0, 0x50, 0x71, 0xf7, 0x86, 0xc1, 0x6f, 0x3f, 0x96, 0x0f, 0x49, 0x5d, 0x9a, 0x50, 0xad, 0xa8, \n  0xe7, 0x73, 0xe6, 0x3a, 0xba, 0xea, 0xc5, 0xe9, 0x36, 0x81, 0x64, 0x11, 0xfc, 0xca, 0x6d, 0xf5, \n  0xd5, 0xd9, 0xd2, 0x78, 0xc6, 0xd5, 0x43, 0x84, 0x9b, 0xa8, 0x21, 0x43, 0x5b, 0x9a, 0xed, 0xc2, \n  0xfd, 0xd5, 0x3d, 0xb6, 0x5c, 0xd7, 0x05, 0x1b, 0x77, 0x97, 0x1b, 0x92, 0x54, 0xb3, 0xdb, 0xde, \n  0x74, 0x3b, 0x10, 0x90, 0xbc, 0xd5, 0x25, 0xc6, 0x24, 0xf3, 0x1e, 0xf8, 0xde, 0xf5, 0xb0, 0x43, \n  0x2c, 0x45, 0x55, 0xea, 0xe0, 0x25, 0xac, 0x5c, 0x6f, 0x1f, 0xaa, 0x84, 0xcd, 0x76, 0xfe, 0x98, \n  0x76, 0x9b, 0x15, 0xf7, 0x11, 0x3e, 0xbb, 0x31, 0xbd, 0x1d, 0x9a, 0x7a, 0xf1, 0x92, 0x27, 0xe9, \n  0x95, 0x7d, 0xfb, 0xf5, 0xdd, 0x0d, 0xd2, 0x7a, 0x65, 0x83, 0xbe, 0xaa, 0xc1, 0x50, 0x21, 0x73, \n  0xe9, 0x59, 0xfc, 0x4e, 0xd1, 0x3a, 0x26, 0x90, 0x6e, 0x19, 0x7c, 0x43, 0x96, 0x48, 0xef, 0x2c, \n  0x6a, 0xe4, 0x8a, 0x95, 0xa0, 0x99, 0xab, 0xd6, 0xad, 0x3c, 0xb1, 0xd1, 0x5d, 0x4e, 0xd5, 0xb1, \n  0xa3, 0x7a, 0xaf, 0x97, 0xb3, 0xc7, 0x3c, 0xbc, 0x50, 0xa5, 0xb5, 0xc6, 0x7b, 0x93, 0x88, 0xbe, \n  0xc5, 0xe2, 0x17, 0x97, 0x95, 0x5f, 0x2a, 0x50, 0xf7, 0x58, 0x40, 0x21, 0x3b, 0x8c, 0xa3, 0x2e, \n  0xeb, 0x5b, 0xdf, 0x21, 0x71, 0x7c, 0x78, 0xe7, 0x64, 0xdb, 0x3d, 0xfe, 0x7a, 0x74, 0xe7, 0x64, \n  0xdb, 0x1b, 0xb9, 0xc7, 0x5f, 0xdf, 0x39, 0xf9, 0xd2, 0xeb, 0x4f, 0x69, 0xb7, 0x10, 0xd4, 0x02, \n  0x01, 0x31, 0xa2, 0x8a, 0x1d, 0xcb, 0x13, 0xb0, 0x87, 0x1b, 0x54, 0x57, 0x3e, 0x1c, 0xbf, 0x55, \n  0x48, 0xa1, 0xaa, 0xeb, 0x76, 0xd1, 0xa8, 0xe8, 0xd3, 0x67, 0x16, 0x5f, 0x17, 0x31, 0x75, 0x59, \n  0x1a, 0x7c, 0xc2, 0x0f, 0x5c, 0xc3, 0xa7, 0xda, 0xa7, 0x2b, 0xea, 0xa6, 0x70, 0x4d, 0xac, 0xa3, \n  0x7f, 0xad, 0xcc, 0xe9, 0x2a, 0x33, 0x6a, 0x35, 0xb8, 0xba, 0x76, 0xca, 0x81, 0x47, 0x3a, 0xf2, \n  0x51, 0xe5, 0x42, 0xab, 0x39, 0x6c, 0xfc, 0xfd, 0x5c, 0xe5, 0x26, 0x28, 0x1f, 0x35, 0x87, 0x71, \n  0x7b, 0xb2, 0x18, 0x56, 0x0a, 0x97, 0x23, 0x71, 0x38, 0x8e, 0xcd, 0xa7, 0xae, 0xf3, 0x04, 0x33, \n  0xc8, 0x1d, 0x28, 0x49, 0x4c, 0xd3, 0x9f, 0xab, 0x34, 0xe9, 0x39, 0x19, 0x03, 0xf4, 0x17, 0x20, \n  0xe8, 0xea, 0x08, 0x02, 0x0a, 0x78, 0x7c, 0x51, 0x10, 0x06, 0x37, 0xfd, 0xf1, 0x88, 0x75, 0x41, \n  0xbb, 0xf6, 0x97, 0x3d, 0xcf, 0x17, 0x49, 0xa0, 0x3c, 0xb1, 0x5c, 0x66, 0x35, 0xaf, 0x70, 0x74, \n  0x43, 0x1f, 0x8b, 0x0b, 0x16, 0x94, 0xef, 0xb4, 0x5d, 0x1d, 0x4c, 0x03, 0x1c, 0x16, 0x2f, 0x62, \n  0x55, 0xbd, 0xb2, 0x58, 0xb3, 0xf9, 0xf7, 0x25, 0xd4, 0x8f, 0x83, 0x6b, 0xe5, 0x32, 0xca, 0xf3, \n  0x6b, 0xee, 0x17, 0x27, 0x54, 0x89, 0x01, 0x0d, 0x47, 0x9a, 0x46, 0x07, 0xba, 0x54, 0xa0, 0xfa, \n  0x1a, 0xc8, 0x64, 0x7d, 0xa9, 0xd9, 0xc3, 0xfa, 0x3a, 0xfb, 0x3d, 0x31, 0x9d, 0x56, 0xc9, 0x52, \n  0x77, 0xe0, 0x60, 0x58, 0xbb, 0xa4, 0xd1, 0xd8, 0x83, 0x22, 0x3f, 0xac, 0xee, 0x3c, 0xcf, 0xfd, \n  0x10, 0x43, 0x76, 0x07, 0x79, 0xa9, 0xe5, 0x38, 0x29, 0x3f, 0x81, 0x91, 0xf5, 0xa5, 0xe8, 0xf9, \n  0x9f, 0x4d, 0x39, 0x48, 0x55, 0xb9, 0x4a, 0x09, 0x95, 0x60, 0xc9, 0xfd, 0x7f, 0xbf, 0x0f, 0xa8, \n  0x3c, 0xf8, 0xfc, 0xb3, 0xfd, 0x3e, 0x84, 0x17, 0x0e, 0x3e, 0xff, 0xec, 0x7f, 0x01, 0x07, 0x92, \n  0x1a, 0xb2, 0xe7, 0x7d, 0x00, 0x00\n};\n"
  },
  {
    "path": "src/assets/logo_svg.h",
    "content": "// Auto Generated file (shared build script)\n#pragma once\n#include <pgmspace.h>\n\nconst uint8_t _aclogo_svg[1560] PROGMEM = {\n  0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0xc5, 0x56, 0xdb, 0x6e, 0x23, 0xc7, \n  0x11, 0x7d, 0xd6, 0x02, 0xfb, 0x0f, 0x93, 0xc9, 0x53, 0x80, 0xee, 0x52, 0x57, 0xf5, 0x5d, 0x16, \n  0x63, 0xc4, 0xb4, 0x61, 0x19, 0xb0, 0x81, 0x00, 0x06, 0xf4, 0x6a, 0x68, 0x47, 0xb4, 0x28, 0x78, \n  0x4c, 0x0a, 0x1a, 0x8a, 0xd2, 0x26, 0xf0, 0x8b, 0x7f, 0xc3, 0x5f, 0xe1, 0x1f, 0xc8, 0xdf, 0xf8, \n  0x3f, 0x82, 0x53, 0x4d, 0xae, 0xb8, 0xe0, 0x5a, 0x41, 0xb2, 0x08, 0xfc, 0xc0, 0x9e, 0x66, 0x5f, \n  0xaa, 0x4e, 0x9d, 0x3e, 0x55, 0xdd, 0xe7, 0x9f, 0x3e, 0xfd, 0x38, 0x76, 0xdb, 0xc5, 0xfd, 0x74, \n  0xbb, 0x5e, 0xcd, 0x7a, 0x26, 0xd7, 0x77, 0x8b, 0xd5, 0xb0, 0xbe, 0xbe, 0x5d, 0xdd, 0xcc, 0xfa, \n  0x87, 0xcd, 0xf7, 0xb6, 0xf4, 0x9f, 0xfe, 0xf5, 0xf5, 0xab, 0xf3, 0x3f, 0x59, 0xdb, 0x7d, 0xb9, \n  0x58, 0x2d, 0xee, 0xaf, 0x36, 0xeb, 0xfb, 0xb3, 0xee, 0x6f, 0xd7, 0xeb, 0x37, 0x8b, 0xee, 0xab, \n  0x71, 0x7c, 0x98, 0x36, 0x3a, 0xd4, 0x49, 0xa6, 0x44, 0x6c, 0xba, 0x6f, 0x2f, 0xbf, 0xec, 0xbe, \n  0x78, 0xba, 0x5b, 0xdf, 0x6f, 0xba, 0xbf, 0x8f, 0x0f, 0x37, 0xf6, 0xab, 0x55, 0x47, 0x3a, 0x78, \n  0xd9, 0x9c, 0x9c, 0x75, 0x89, 0x9c, 0xeb, 0x3e, 0x7b, 0xb8, 0x1d, 0xaf, 0x3b, 0xf7, 0x97, 0xae, \n  0xb3, 0x16, 0xf6, 0xa7, 0xed, 0xcd, 0x21, 0x0c, 0xee, 0xbb, 0xdb, 0xeb, 0x59, 0xff, 0xdb, 0x2f, \n  0xff, 0xfa, 0xed, 0xd7, 0x9f, 0xbf, 0xe3, 0xbe, 0x7b, 0xfa, 0x71, 0x5c, 0x4d, 0xb3, 0x7e, 0xb9, \n  0xd9, 0xdc, 0x9d, 0x9d, 0x9e, 0x3e, 0x3e, 0x3e, 0xd2, 0xa3, 0xa7, 0xf5, 0xfd, 0xcd, 0xa9, 0x38, \n  0xe7, 0x4e, 0xa7, 0xed, 0xcd, 0x6e, 0xc9, 0xd9, 0xd3, 0x78, 0xbb, 0xfa, 0xe1, 0x43, 0x0b, 0xb9, \n  0xd6, 0x7a, 0xaa, 0xb3, 0x7d, 0xf7, 0x34, 0xeb, 0xdd, 0xdd, 0x53, 0xdf, 0xbd, 0x6d, 0xdf, 0xd7, \n  0xaf, 0x4e, 0xba, 0xed, 0xed, 0xe2, 0xf1, 0xb3, 0x35, 0x26, 0x3a, 0xd7, 0xf9, 0x50, 0x3a, 0xcf, \n  0xb1, 0xef, 0xa6, 0xcd, 0xdb, 0x71, 0x31, 0xeb, 0x17, 0xab, 0xab, 0x37, 0xe3, 0xc2, 0xbe, 0xb9, \n  0x1a, 0x7e, 0xb8, 0xb9, 0x5f, 0x3f, 0xac, 0xae, 0xcf, 0x56, 0x8b, 0xc7, 0xee, 0x60, 0xe5, 0x27, \n  0xea, 0xfe, 0x6c, 0xba, 0xbb, 0x1a, 0x16, 0xb3, 0xfe, 0xee, 0x7e, 0x31, 0x2d, 0xee, 0xb7, 0x8b, \n  0x5e, 0x23, 0x83, 0x89, 0x6e, 0xf3, 0xf6, 0x6e, 0x31, 0xeb, 0x37, 0x8b, 0xa7, 0xcd, 0xe9, 0x30, \n  0x4d, 0x98, 0x38, 0xa1, 0x69, 0xe3, 0xfe, 0xf9, 0xfd, 0xed, 0x38, 0x9e, 0xfd, 0xf9, 0x8b, 0xe2, \n  0x93, 0x7c, 0xfe, 0xc9, 0x4f, 0xaf, 0x5f, 0x9d, 0x9f, 0xea, 0x7a, 0x6c, 0xbc, 0xc1, 0xa2, 0xd6, \n  0x9e, 0x9c, 0xdf, 0x5d, 0x6d, 0x96, 0xdd, 0x30, 0x5e, 0x4d, 0xd3, 0xac, 0x9f, 0x36, 0xae, 0xef, \n  0xae, 0x67, 0xfd, 0x37, 0xec, 0x99, 0x8a, 0xe1, 0x14, 0x28, 0x0f, 0xce, 0x14, 0x4a, 0x36, 0x1b, \n  0x8e, 0x94, 0x2c, 0x1a, 0xed, 0x4d, 0xda, 0xb5, 0xd9, 0xbe, 0x1b, 0x1e, 0x9c, 0x2d, 0x94, 0x4c, \n  0x7e, 0x5e, 0xa3, 0x3d, 0x38, 0x39, 0x99, 0xb3, 0x04, 0x18, 0x0c, 0x95, 0xd8, 0xec, 0x8c, 0xc7, \n  0xf4, 0xdc, 0x87, 0xa3, 0xfe, 0xf4, 0x05, 0x40, 0x92, 0x32, 0x65, 0xc3, 0xa1, 0x50, 0x1c, 0x2c, \n  0x3b, 0x9b, 0x1d, 0x65, 0x9b, 0x92, 0x65, 0x49, 0x54, 0x2c, 0xfb, 0x77, 0xed, 0x9c, 0x45, 0x28, \n  0x1a, 0x4e, 0x86, 0x39, 0x1a, 0x61, 0x40, 0x71, 0x85, 0xbc, 0x91, 0xb2, 0xe5, 0x48, 0x3c, 0x64, \n  0x47, 0xc9, 0x38, 0xc3, 0x52, 0x4c, 0xcc, 0x58, 0x29, 0x05, 0xbf, 0x25, 0x26, 0x1b, 0x56, 0x89, \n  0x79, 0x87, 0x29, 0x18, 0x49, 0x9e, 0xbc, 0x62, 0x2d, 0xe6, 0x00, 0xc3, 0xcb, 0x58, 0x73, 0xa5, \n  0x60, 0xd8, 0x31, 0xc9, 0xbc, 0xf5, 0x43, 0x04, 0x00, 0x1f, 0x88, 0xe1, 0x39, 0x17, 0x12, 0xe3, \n  0x06, 0xeb, 0x29, 0x1a, 0x67, 0xb3, 0x71, 0x24, 0x96, 0x1d, 0x05, 0xe3, 0x28, 0x8d, 0x56, 0xc8, \n  0x9b, 0x44, 0x69, 0x08, 0x15, 0x20, 0x32, 0x05, 0x53, 0x2a, 0x79, 0x13, 0x13, 0x55, 0xc3, 0x2e, \n  0x01, 0x81, 0x4b, 0x3b, 0x5a, 0x47, 0x84, 0x2d, 0xe4, 0xd5, 0x0d, 0x6b, 0xa0, 0x62, 0xf6, 0xee, \n  0x41, 0xf8, 0x01, 0x94, 0x17, 0x21, 0x73, 0x86, 0x0f, 0x01, 0x5d, 0x83, 0xf5, 0x02, 0x7c, 0x36, \n  0x09, 0x79, 0xcb, 0x42, 0xd1, 0x96, 0x48, 0x6c, 0x7d, 0x24, 0x3f, 0x69, 0x6b, 0xa3, 0x6f, 0x5f, \n  0x8c, 0xcf, 0x63, 0xa1, 0x62, 0x52, 0x35, 0x99, 0x29, 0x18, 0x8f, 0x3f, 0x15, 0x71, 0x72, 0x1a, \n  0x13, 0x05, 0x93, 0x28, 0x28, 0xd2, 0xc1, 0x0a, 0x1b, 0x61, 0xd8, 0x4e, 0x26, 0x14, 0xaa, 0xad, \n  0x97, 0x0b, 0xe5, 0x89, 0x71, 0x48, 0x11, 0xdc, 0x3e, 0x8f, 0x61, 0xc9, 0xf3, 0x5f, 0xed, 0x4d, \n  0x58, 0x62, 0x75, 0x31, 0xc6, 0xd4, 0xc0, 0x7b, 0x3e, 0xe6, 0x12, 0x98, 0xb2, 0x11, 0x57, 0x8d, \n  0x30, 0xd0, 0x48, 0x3b, 0xfe, 0xe7, 0xd8, 0xfe, 0x03, 0x07, 0xe0, 0xbb, 0x0c, 0x88, 0x58, 0x10, \n  0x31, 0x9c, 0xe0, 0xb0, 0xad, 0x64, 0x8a, 0xa0, 0xa3, 0x0c, 0xb6, 0x52, 0xb5, 0xf8, 0x27, 0x02, \n  0x6a, 0x02, 0x78, 0x10, 0xcb, 0x1e, 0x53, 0x9e, 0xa2, 0x75, 0x14, 0x6c, 0x44, 0x70, 0xfa, 0x4d, \n  0x36, 0x51, 0x69, 0xe1, 0x63, 0xc2, 0x53, 0x31, 0x58, 0x94, 0x4c, 0xa2, 0x8a, 0xe9, 0x81, 0x21, \n  0x31, 0xa6, 0x68, 0xe0, 0xc1, 0x24, 0x8a, 0x20, 0x30, 0x19, 0x0e, 0x24, 0x83, 0x78, 0x1c, 0xf8, \n  0x6e, 0x4c, 0x25, 0xc4, 0x06, 0xac, 0x9b, 0x9c, 0x88, 0x07, 0xeb, 0x28, 0x9a, 0x40, 0xd5, 0x62, \n  0x77, 0xa5, 0x64, 0x45, 0xa5, 0x49, 0x32, 0x32, 0xc4, 0x1e, 0x49, 0x9a, 0xdf, 0x48, 0xc1, 0xb6, \n  0x1c, 0xa0, 0xa2, 0x82, 0xe3, 0xa8, 0xbe, 0xeb, 0xc0, 0xe4, 0x6d, 0x36, 0x62, 0xb1, 0xc9, 0x88, \n  0xd5, 0xa3, 0x77, 0x36, 0x16, 0x1b, 0x04, 0xb9, 0xe4, 0x92, 0xad, 0x88, 0x9d, 0x39, 0x90, 0x1f, \n  0x6c, 0x02, 0xf5, 0x08, 0x14, 0xe6, 0xd8, 0x72, 0x51, 0xb9, 0xca, 0x60, 0x33, 0x50, 0x02, 0x47, \n  0xa0, 0x6c, 0x12, 0x14, 0x53, 0x31, 0x22, 0x7b, 0x00, 0x16, 0x59, 0x8e, 0x78, 0x55, 0xce, 0x49, \n  0x3f, 0x82, 0x08, 0x90, 0x08, 0x00, 0x06, 0x74, 0x5e, 0x1a, 0x07, 0xc1, 0xf8, 0x4c, 0x71, 0xf0, \n  0x86, 0x8d, 0xce, 0x51, 0x30, 0x6a, 0x8d, 0xca, 0xd2, 0x11, 0x0f, 0x9a, 0x0d, 0xde, 0x78, 0x87, \n  0x58, 0x21, 0x75, 0xed, 0xf9, 0x44, 0x71, 0x47, 0xb2, 0xc9, 0xe4, 0xad, 0x40, 0x7d, 0x81, 0x04, \n  0x51, 0x1a, 0xa0, 0x19, 0x19, 0x3a, 0x2a, 0xc4, 0x83, 0x56, 0x1c, 0x6c, 0x16, 0xa8, 0xa3, 0x59, \n  0xa9, 0xc6, 0x93, 0xcc, 0x39, 0x03, 0x3f, 0x57, 0xad, 0x52, 0x39, 0x63, 0x5d, 0x49, 0x2a, 0x1e, \n  0x15, 0x45, 0x13, 0xce, 0xf9, 0xe9, 0xfb, 0x95, 0x73, 0x3d, 0xbe, 0xbd, 0x59, 0xaf, 0xba, 0xbb, \n  0xf5, 0xed, 0x6a, 0x33, 0xcd, 0x7a, 0xa9, 0x8a, 0x89, 0x03, 0x95, 0xce, 0xbb, 0x7c, 0xd0, 0x43, \n  0x45, 0xe1, 0xae, 0xcd, 0xb7, 0xfe, 0xc9, 0xc9, 0x7b, 0x5a, 0x6c, 0x35, 0xb7, 0x15, 0x99, 0x48, \n  0xf2, 0xf5, 0x41, 0x7f, 0x64, 0xc8, 0x8a, 0x07, 0xd4, 0x49, 0x67, 0x04, 0xf8, 0x28, 0xe9, 0xd7, \n  0x93, 0xdf, 0x32, 0x53, 0x05, 0x95, 0x38, 0x32, 0x0c, 0xd8, 0xdd, 0xc4, 0xd2, 0x72, 0xbc, 0x3c, \n  0xb2, 0xf5, 0x8f, 0xee, 0x1b, 0x8e, 0x60, 0x56, 0xaa, 0xa3, 0xdc, 0x48, 0x63, 0x95, 0x92, 0x1e, \n  0x21, 0x31, 0x48, 0xb1, 0x4c, 0x65, 0x50, 0x83, 0x6a, 0x8a, 0x2d, 0x44, 0x8a, 0x6f, 0xa6, 0xbc, \n  0x6d, 0x5a, 0x77, 0x16, 0xae, 0xa0, 0xa0, 0x26, 0x76, 0x9d, 0x1b, 0xec, 0x6e, 0x93, 0x0d, 0x4d, \n  0x8f, 0x36, 0xa3, 0x1d, 0x35, 0x99, 0x8c, 0x23, 0xbe, 0xf4, 0x1c, 0x97, 0x05, 0x46, 0x04, 0x08, \n  0x0b, 0xd5, 0x86, 0x40, 0xcb, 0x8d, 0xaa, 0x24, 0xea, 0xc1, 0x17, 0x93, 0xa8, 0x8c, 0x1e, 0xa9, \n  0x91, 0x28, 0x2c, 0x2b, 0xc5, 0xd1, 0xfa, 0x76, 0x9b, 0xc4, 0x39, 0x27, 0xc0, 0x92, 0x1a, 0x70, \n  0x38, 0x09, 0xdb, 0xa4, 0x8a, 0x39, 0x88, 0xea, 0x88, 0x57, 0x8f, 0x35, 0xb1, 0x52, 0x55, 0x80, \n  0x4a, 0x15, 0x86, 0x82, 0x66, 0x24, 0xfc, 0xe5, 0xad, 0xc7, 0x2d, 0xa7, 0x85, 0x5e, 0x73, 0x07, \n  0x92, 0xd0, 0x89, 0x61, 0xb7, 0xc1, 0x04, 0xa5, 0x3c, 0x9b, 0x8c, 0x76, 0x29, 0x9e, 0xc2, 0xd6, \n  0x16, 0x92, 0x0b, 0xe6, 0xbd, 0xf6, 0xd4, 0xb6, 0xb3, 0x9a, 0x3b, 0x1a, 0x3e, 0xbc, 0xf8, 0x2d, \n  0xf2, 0xa2, 0x2e, 0x85, 0x11, 0x35, 0x36, 0x68, 0xf8, 0x2c, 0xd0, 0x7f, 0x43, 0x83, 0x55, 0x66, \n  0xb7, 0x7a, 0x29, 0xb2, 0x33, 0xeb, 0xe6, 0x1a, 0x0f, 0xa4, 0x82, 0x2c, 0x37, 0x12, 0xf5, 0x28, \n  0x5a, 0x1c, 0xc7, 0x11, 0x4a, 0x6a, 0xe7, 0xbb, 0x37, 0xb0, 0x04, 0xc3, 0x61, 0xb0, 0x90, 0xb9, \n  0xb3, 0x91, 0xc4, 0xb0, 0x55, 0xe8, 0xbf, 0x4f, 0x41, 0xc8, 0x24, 0xbb, 0xc3, 0x49, 0xc4, 0xcf, \n  0x88, 0x97, 0xb6, 0x75, 0x81, 0xb9, 0x55, 0x59, 0x2f, 0x1e, 0x32, 0x4e, 0x50, 0x95, 0x17, 0xad, \n  0x1e, 0xf0, 0x6d, 0xde, 0xa1, 0x38, 0xc2, 0x97, 0x50, 0xa5, 0xa5, 0x38, 0x8a, 0x17, 0x51, 0xf6, \n  0xe7, 0xe0, 0x54, 0x37, 0xdc, 0x20, 0xe0, 0x07, 0x77, 0xf5, 0xc3, 0xb4, 0x30, 0xe5, 0x4b, 0x89, \n  0xf9, 0x22, 0x32, 0x6a, 0xd1, 0x51, 0x54, 0x07, 0x27, 0xf0, 0xa1, 0xd0, 0x58, 0x06, 0x67, 0xf0, \n  0x26, 0x09, 0xad, 0x10, 0xca, 0x73, 0xb3, 0x64, 0x4f, 0x71, 0x97, 0x5a, 0x5e, 0x8b, 0x83, 0x4a, \n  0x1d, 0xbf, 0x2d, 0x3b, 0x7d, 0xfc, 0x1c, 0xa5, 0xd6, 0x45, 0x60, 0x92, 0x2d, 0xa8, 0x91, 0xb8, \n  0xbf, 0xdd, 0x14, 0x92, 0xd1, 0xbb, 0xc0, 0xa8, 0xea, 0x5f, 0x4c, 0xa0, 0x3c, 0xcf, 0xd0, 0xba, \n  0xe0, 0xfe, 0x34, 0x59, 0x1a, 0x35, 0xe6, 0x99, 0xa5, 0xe3, 0xd2, 0x90, 0x03, 0xe8, 0xf8, 0xff, \n  0x88, 0x78, 0x69, 0x45, 0xfe, 0x1b, 0x0d, 0x1f, 0x2a, 0xe2, 0x25, 0x15, 0x37, 0x11, 0xce, 0xf1, \n  0xdc, 0xa9, 0x3b, 0x29, 0x33, 0xee, 0x2e, 0x15, 0xf3, 0x41, 0x44, 0x47, 0xd1, 0x8a, 0x57, 0xf1, \n  0x83, 0x89, 0xa5, 0xd5, 0x03, 0xfa, 0x5f, 0x05, 0xa3, 0x10, 0xf8, 0x0f, 0x57, 0x8c, 0xc2, 0xa8, \n  0x1f, 0x29, 0x19, 0x09, 0xa8, 0x8c, 0x4d, 0x33, 0x12, 0x34, 0x05, 0x55, 0x36, 0x07, 0x6c, 0x1d, \n  0x33, 0x59, 0xdc, 0x7e, 0xdd, 0x47, 0x32, 0x79, 0x21, 0x29, 0xfe, 0xf1, 0x99, 0x27, 0x31, 0x7e, \n  0x2c, 0x8b, 0xd5, 0x69, 0x96, 0x29, 0x8b, 0x05, 0x59, 0xb2, 0x63, 0xf1, 0x99, 0xa9, 0xe3, 0xec, \n  0x63, 0xcc, 0x70, 0x7b, 0xb3, 0x1c, 0x61, 0xaa, 0xca, 0x1f, 0x57, 0x92, 0x2d, 0xee, 0xe9, 0x25, \n  0xa7, 0xdf, 0xbb, 0xac, 0x2f, 0xd5, 0xc8, 0xd7, 0xef, 0xcc, 0xe1, 0x36, 0x46, 0xa8, 0x2d, 0x39, \n  0x46, 0x14, 0x5e, 0xbf, 0xbf, 0x27, 0x35, 0xba, 0x5d, 0x3d, 0x16, 0x8a, 0x4b, 0xbc, 0x95, 0x8e, \n  0x83, 0x9d, 0x8e, 0x83, 0x0c, 0xfa, 0x8c, 0xfb, 0xc0, 0xf5, 0x3c, 0x67, 0x8e, 0x10, 0x10, 0x92, \n  0x8f, 0xfd, 0xfe, 0x21, 0x62, 0x0e, 0x10, 0x1c, 0x3e, 0x72, 0xf6, 0xed, 0xb4, 0xc5, 0xf7, 0xdf, \n  0xea, 0x7c, 0x2b, 0x34, 0xcd, 0x0f, 0x00, 0x00\n};\n"
  },
  {
    "path": "src/assets/setup_htm.h",
    "content": "// Auto Generated file (shared build script)\n#pragma once\n#include <pgmspace.h>\n\nconst uint8_t _acsetup_min_htm[10761] PROGMEM = {\n  0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0xb5, 0x7d, 0xed, 0x92, 0xdb, 0x48, \n  0x92, 0xd8, 0xef, 0xdb, 0x88, 0x7d, 0x07, 0x74, 0x49, 0xea, 0x03, 0x4e, 0x45, 0x34, 0x49, 0x75, \n  0x6b, 0x24, 0xb0, 0xc1, 0x3e, 0x8d, 0xa4, 0xd9, 0xd1, 0xae, 0x46, 0xa3, 0xdb, 0xd6, 0xee, 0xec, \n  0x9e, 0x4e, 0x3b, 0x5d, 0x04, 0x8a, 0x24, 0x46, 0x20, 0xc0, 0x45, 0x15, 0x9b, 0xdd, 0x43, 0xe2, \n  0xa7, 0x1d, 0xfe, 0x67, 0xfb, 0xce, 0x71, 0xb6, 0x23, 0x1c, 0xe1, 0xf0, 0x1b, 0xf8, 0x8f, 0x1d, \n  0x8e, 0xb0, 0xc3, 0x0f, 0xb3, 0x2f, 0x60, 0x3f, 0x82, 0x23, 0xb3, 0xaa, 0x80, 0x02, 0x08, 0x52, \n  0xad, 0xd9, 0xf5, 0x5d, 0xec, 0x88, 0x85, 0xfa, 0xca, 0xca, 0xca, 0xef, 0x4c, 0xa0, 0xcf, 0x8f, \n  0x5e, 0x7c, 0xfb, 0xfc, 0xdd, 0xef, 0xdf, 0xbe, 0x74, 0xe6, 0x72, 0x91, 0x8e, 0x7f, 0xfe, 0xb3, \n  0x73, 0xf8, 0xd7, 0x49, 0x59, 0x36, 0x0b, 0x09, 0xcf, 0x08, 0x3e, 0xe1, 0x2c, 0x1e, 0xff, 0xfc, \n  0x67, 0x8e, 0x73, 0xbe, 0xe0, 0x92, 0x39, 0xd1, 0x9c, 0x15, 0x82, 0xcb, 0x90, 0xac, 0xe4, 0xb4, \n  0xf7, 0x84, 0xa8, 0x1e, 0x99, 0xc8, 0x94, 0x8f, 0x5f, 0x5e, 0xbe, 0x75, 0xbe, 0xba, 0x74, 0xbe, \n  0xe3, 0x13, 0xe7, 0x92, 0x17, 0xd7, 0xbc, 0x38, 0x3f, 0x51, 0x1d, 0xf5, 0xec, 0x8c, 0x2d, 0x78, \n  0x48, 0xae, 0x13, 0xbe, 0x5e, 0xe6, 0x85, 0x24, 0x4e, 0x94, 0x67, 0x92, 0x67, 0x32, 0x24, 0xeb, \n  0x24, 0x96, 0xf3, 0x30, 0xe6, 0xd7, 0x49, 0xc4, 0x7b, 0xd8, 0xa0, 0x4e, 0x92, 0x25, 0x32, 0x61, \n  0x69, 0x4f, 0x44, 0x2c, 0xe5, 0xe1, 0x40, 0xef, 0x25, 0xe4, 0x6d, 0xca, 0xc7, 0x41, 0x91, 0xe7, \n  0x72, 0xd3, 0xeb, 0x45, 0xbd, 0x41, 0x70, 0xaf, 0xff, 0x78, 0xf2, 0x38, 0x3e, 0x1d, 0x61, 0xab, \n  0x17, 0x07, 0xf7, 0xfa, 0x4f, 0x9e, 0x0e, 0x26, 0x43, 0x6c, 0x0f, 0x83, 0x7b, 0xd3, 0xfe, 0xf4, \n  0xe9, 0x74, 0xaa, 0x5a, 0xd0, 0xcb, 0xfb, 0xd3, 0xe1, 0x94, 0x63, 0x5b, 0x06, 0xf7, 0x06, 0xfc, \n  0x11, 0x3b, 0x53, 0xbd, 0xb2, 0x97, 0x06, 0xf7, 0x1e, 0x9f, 0x7e, 0x71, 0xfa, 0x64, 0xf2, 0x45, \n  0x3c, 0xea, 0xf5, 0x66, 0x3d, 0x16, 0xdc, 0xeb, 0x4f, 0x07, 0x5f, 0x0c, 0x19, 0xb6, 0x26, 0xd6, \n  0x68, 0xd1, 0x1b, 0x04, 0x7d, 0xe7, 0x74, 0x79, 0xe3, 0x0c, 0x86, 0xcb, 0x1b, 0xa7, 0x98, 0x4d, \n  0x98, 0xfb, 0x98, 0x0e, 0x9e, 0x0c, 0xe9, 0x70, 0x30, 0xa4, 0xfe, 0x23, 0xaf, 0x04, 0x4c, 0x6e, \n  0xa6, 0x79, 0x26, 0x7b, 0x53, 0xb6, 0x48, 0xd2, 0xdb, 0x40, 0xb0, 0x4c, 0xf4, 0x04, 0x2f, 0x92, \n  0x69, 0x39, 0xc9, 0xe3, 0xdb, 0xcd, 0x84, 0x45, 0x1f, 0x67, 0x45, 0xbe, 0xca, 0xe2, 0x20, 0x4d, \n  0x32, 0xce, 0x8a, 0xde, 0xac, 0x60, 0x71, 0xc2, 0x33, 0xe9, 0x0e, 0x1e, 0x9d, 0xc5, 0x7c, 0x46, \n  0xaf, 0x59, 0xe1, 0x22, 0x18, 0x9e, 0xd3, 0xaf, 0x1a, 0x13, 0xcf, 0x19, 0xf4, 0xfb, 0x0f, 0xbc, \n  0x51, 0x94, 0xa7, 0x79, 0x11, 0xa8, 0xc7, 0x51, 0x4f, 0x7a, 0xa3, 0x45, 0x92, 0xf5, 0xe6, 0x3c, \n  0x99, 0xcd, 0x65, 0x30, 0xe8, 0xf7, 0xaf, 0xe7, 0xa3, 0x25, 0x8b, 0xe3, 0x24, 0x9b, 0x05, 0x7d, \n  0x67, 0xd8, 0x5f, 0xde, 0xe0, 0x7f, 0x4a, 0x46, 0x59, 0xc0, 0x22, 0x99, 0x5c, 0x73, 0xca, 0x82, \n  0x79, 0x7e, 0xcd, 0x8b, 0x8d, 0x5a, 0x28, 0xc9, 0xe6, 0xbc, 0x48, 0xe4, 0x28, 0x5f, 0x49, 0x00, \n  0x27, 0xe8, 0x8f, 0x24, 0xbf, 0x91, 0xbd, 0x98, 0x47, 0x79, 0xc1, 0x64, 0x92, 0x67, 0x41, 0x96, \n  0x67, 0xbc, 0xf4, 0x27, 0xac, 0xa0, 0xfe, 0x34, 0xcf, 0x25, 0xfc, 0xb7, 0x58, 0x50, 0x7f, 0xba, \n  0xa6, 0xfe, 0x3c, 0xa6, 0xfe, 0x5c, 0xac, 0xa9, 0x9f, 0x44, 0xd4, 0x4f, 0xe3, 0x82, 0xfa, 0x4b, \n  0xb1, 0x8e, 0xa9, 0x2f, 0xd2, 0x35, 0xf5, 0x25, 0x9b, 0x08, 0xea, 0xcb, 0x19, 0xfe, 0x06, 0x82, \n  0xa0, 0xbe, 0x5c, 0x6f, 0xe2, 0x44, 0x2c, 0x53, 0x76, 0x1b, 0x4c, 0x53, 0x7e, 0x53, 0xe2, 0x82, \n  0x1b, 0xf8, 0xd9, 0x8b, 0x93, 0x82, 0x47, 0xb8, 0x5f, 0x94, 0xa7, 0xab, 0x45, 0xd6, 0x2b, 0xf8, \n  0x35, 0x2f, 0x04, 0x2f, 0xad, 0x4d, 0x37, 0x2c, 0x4d, 0x66, 0x59, 0x2f, 0x91, 0x7c, 0x21, 0x82, \n  0x88, 0x67, 0x92, 0x17, 0xa5, 0xea, 0x40, 0xd2, 0x81, 0xe3, 0x3f, 0x28, 0xfd, 0x48, 0x66, 0x7a, \n  0x78, 0xe7, 0xc2, 0x38, 0xe0, 0x0e, 0xb7, 0xa0, 0x09, 0xe6, 0xf1, 0x19, 0xd5, 0xa4, 0x34, 0x1d, \n  0x7a, 0xa3, 0x49, 0x5e, 0xc4, 0xbc, 0x08, 0x06, 0xcb, 0x1b, 0x47, 0xe4, 0x69, 0x12, 0x3b, 0xe6, \n  0x1e, 0x06, 0xa6, 0xaf, 0x07, 0xcb, 0xac, 0x44, 0x00, 0xf4, 0x31, 0x9a, 0xe4, 0x37, 0x3d, 0x31, \n  0x67, 0x71, 0xbe, 0x0e, 0xfa, 0xce, 0x13, 0xb8, 0x0b, 0x20, 0x1d, 0x4d, 0xb8, 0x8f, 0x1e, 0x8d, \n  0x6c, 0x64, 0x8c, 0x16, 0xac, 0x98, 0x25, 0x59, 0xd0, 0x77, 0xd8, 0x4a, 0xe6, 0xce, 0xe3, 0x25, \n  0x3c, 0xb9, 0x51, 0x4c, 0x11, 0x3c, 0x39, 0xed, 0x2f, 0x6f, 0xaa, 0x8b, 0x85, 0x95, 0xd4, 0x83, \n  0x5c, 0x24, 0x78, 0xb4, 0x82, 0xa7, 0x0c, 0x2e, 0xb7, 0xf4, 0x81, 0x6f, 0x79, 0xd1, 0x5b, 0xb0, \n  0x24, 0xdb, 0xd8, 0x94, 0xf1, 0x08, 0xa8, 0x00, 0x2e, 0xa0, 0x13, 0x29, 0x66, 0xf3, 0xb3, 0xe5, \n  0x0d, 0x6e, 0x5f, 0xfa, 0x72, 0xb2, 0xc6, 0xfb, 0xb2, 0x51, 0x2e, 0x24, 0x2b, 0x24, 0xd2, 0x9b, \n  0x86, 0x0a, 0xd0, 0x2d, 0x27, 0xeb, 0x8d, 0x9e, 0x0d, 0x33, 0x2b, 0x20, 0xcf, 0x60, 0xbf, 0x68, \n  0x21, 0xf5, 0x0a, 0x82, 0xa7, 0x53, 0x3c, 0x67, 0x4f, 0xaf, 0xa2, 0xa6, 0x00, 0x17, 0xf5, 0x9d, \n  0xbe, 0x63, 0x46, 0xfb, 0x49, 0x06, 0xf7, 0x61, 0x4f, 0xc2, 0x55, 0x0d, 0xa6, 0x54, 0xb7, 0x9e, \n  0xdd, 0x4b, 0xf9, 0x54, 0x02, 0x36, 0xca, 0xf9, 0x40, 0xf1, 0x9c, 0x48, 0x7e, 0xe4, 0xc1, 0x23, \n  0x85, 0x3b, 0x5c, 0x7f, 0xd0, 0xc7, 0x0d, 0x60, 0xf9, 0xf9, 0xd0, 0x1a, 0x03, 0x17, 0x31, 0xc2, \n  0xe6, 0x5a, 0x21, 0xe8, 0x71, 0xbf, 0x6f, 0xe6, 0x9c, 0x1a, 0x24, 0xac, 0x52, 0xcd, 0x24, 0xf7, \n  0xf8, 0x90, 0x3f, 0x99, 0xf6, 0x2b, 0x28, 0x24, 0x9b, 0xa4, 0xbc, 0x75, 0x61, 0xb0, 0x53, 0x99, \n  0x64, 0xcb, 0x95, 0x7c, 0x2f, 0x6f, 0x97, 0x3c, 0xcc, 0x56, 0x8b, 0x09, 0x2f, 0x3e, 0x50, 0xeb, \n  0xd1, 0x92, 0x09, 0xb1, 0xce, 0x8b, 0xb8, 0xf1, 0x10, 0xd8, 0xed, 0x03, 0x15, 0x3c, 0xe5, 0x91, \n  0xb4, 0xc9, 0xd2, 0x50, 0xd6, 0x21, 0xaa, 0x93, 0xbd, 0xb4, 0x4d, 0x77, 0x4f, 0x0c, 0xd9, 0x25, \n  0x3f, 0xc2, 0x2d, 0xe8, 0xce, 0x49, 0x7e, 0xa3, 0x05, 0x87, 0x91, 0x6c, 0x35, 0x2a, 0x06, 0x80, \n  0x2e, 0x4d, 0x26, 0x88, 0x3a, 0x7d, 0x83, 0x0a, 0xbb, 0x20, 0x3d, 0x46, 0x16, 0x7b, 0x21, 0xe4, \n  0xc1, 0x34, 0x8f, 0x56, 0x42, 0x03, 0xad, 0x1a, 0x1b, 0xbd, 0x53, 0x53, 0x3c, 0x0d, 0x7a, 0xb1, \n  0xd7, 0xe4, 0x82, 0xbe, 0x03, 0xfb, 0x19, 0x1e, 0x38, 0x8d, 0x6b, 0xc1, 0x63, 0x23, 0x2f, 0x9a, \n  0xf3, 0xe8, 0xe3, 0x24, 0xbf, 0xf9, 0xb0, 0xe9, 0x3e, 0x4b, 0x25, 0xe2, 0x4a, 0x7f, 0x9e, 0xc4, \n  0x9c, 0xe2, 0x7f, 0x7b, 0x32, 0xc9, 0x6e, 0x55, 0xdb, 0x5a, 0x6a, 0x9a, 0xa4, 0xfc, 0x43, 0x25, \n  0x76, 0x50, 0x98, 0xa9, 0x33, 0x04, 0xcb, 0x94, 0x45, 0x7c, 0x9e, 0xa7, 0x71, 0x25, 0x0c, 0x6d, \n  0xbc, 0x96, 0x20, 0xc5, 0x94, 0xa8, 0x54, 0xcb, 0xe9, 0xdf, 0xfa, 0xd0, 0x4a, 0x86, 0xde, 0xfd, \n  0xd0, 0x43, 0x9b, 0xf1, 0x4b, 0x7d, 0xdd, 0xb6, 0xd8, 0x4a, 0x27, 0x69, 0x90, 0xe5, 0xd2, 0x05, \n  0x89, 0xe9, 0x75, 0x12, 0x82, 0x5a, 0xf6, 0xee, 0xa4, 0xf0, 0x18, 0x49, 0x41, 0xca, 0x7c, 0x11, \n  0xf4, 0xce, 0x96, 0x37, 0xa3, 0x0e, 0x28, 0x2d, 0x2a, 0x78, 0xe4, 0x9f, 0x75, 0xb0, 0x04, 0xd2, \n  0xc0, 0xa3, 0x07, 0x15, 0xc2, 0xe1, 0x18, 0x8a, 0xa7, 0x76, 0x85, 0xcf, 0x48, 0x16, 0x2c, 0xd3, \n  0xcf, 0xfc, 0x47, 0x42, 0x1d, 0x49, 0xe3, 0xa9, 0x3e, 0xce, 0xbd, 0x68, 0x3a, 0x65, 0x53, 0x3e, \n  0xea, 0x46, 0x9d, 0x57, 0xfa, 0x13, 0x94, 0xdf, 0xeb, 0x55, 0x87, 0xb4, 0x1f, 0xdd, 0x55, 0x79, \n  0xc2, 0x4a, 0x95, 0xf2, 0x54, 0x67, 0xd5, 0xea, 0x53, 0x23, 0xb0, 0xdf, 0xc2, 0xd5, 0x59, 0x53, \n  0x5a, 0xab, 0x89, 0x02, 0xc4, 0xba, 0x66, 0x9b, 0xe9, 0x74, 0x3a, 0x8a, 0x56, 0x85, 0xc8, 0x8b, \n  0x60, 0x99, 0x27, 0x08, 0x4c, 0x43, 0x7a, 0xb7, 0x11, 0xf7, 0xc3, 0x4a, 0xc8, 0x64, 0x7a, 0xdb, \n  0xd3, 0xa6, 0x8e, 0x39, 0x40, 0x2d, 0x3c, 0x4f, 0xfb, 0x35, 0x56, 0x01, 0xa3, 0x3b, 0xd8, 0x9b, \n  0xc8, 0xcc, 0x4f, 0x73, 0x16, 0x23, 0x32, 0xde, 0xb3, 0x22, 0x61, 0xbd, 0x38, 0x11, 0x20, 0x79, \n  0xe2, 0x50, 0x16, 0x2b, 0xae, 0x79, 0x43, 0x01, 0x0c, 0x74, 0x6d, 0xe0, 0x8b, 0xf9, 0x94, 0xad, \n  0x52, 0x39, 0xca, 0x97, 0x2c, 0x4a, 0xe4, 0x6d, 0xe0, 0x3f, 0x1a, 0x69, 0x90, 0x7b, 0xfc, 0x9a, \n  0x67, 0x52, 0x18, 0x95, 0x2e, 0x33, 0x5f, 0x2c, 0x58, 0x9a, 0xde, 0x49, 0x1b, 0x4e, 0x4f, 0x4f, \n  0x4f, 0x4f, 0xe9, 0xbd, 0xc9, 0xd3, 0x41, 0x34, 0x88, 0x5a, 0xf4, 0x0d, 0x54, 0x81, 0x5c, 0xad, \n  0x46, 0x9d, 0xc6, 0x36, 0x61, 0x81, 0xa4, 0xad, 0x8f, 0xdd, 0x6f, 0x68, 0x31, 0x94, 0x3d, 0x9a, \n  0x0b, 0x50, 0x80, 0xfa, 0x69, 0x3e, 0xcb, 0x37, 0x96, 0xf0, 0x36, 0xbd, 0xc3, 0x27, 0xd8, 0x1b, \n  0xcf, 0x36, 0x1d, 0x64, 0x8c, 0x26, 0x0b, 0x92, 0x8b, 0x31, 0x0b, 0xe6, 0x86, 0x37, 0x2d, 0x56, \n  0xb1, 0x64, 0x0e, 0xf6, 0x3c, 0x7d, 0xfa, 0x85, 0x2d, 0xd9, 0xfc, 0xe9, 0x7a, 0x33, 0x63, 0x4b, \n  0xdc, 0xb5, 0xbc, 0xb7, 0x5a, 0xc6, 0x4c, 0xf2, 0xde, 0xa4, 0x69, 0x2b, 0xdc, 0x7b, 0x7a, 0x3a, \n  0x64, 0xfd, 0x2f, 0xec, 0xf3, 0x94, 0x68, 0xe2, 0x28, 0xb5, 0xba, 0x2e, 0xd8, 0x32, 0x80, 0xff, \n  0xec, 0xbd, 0x7e, 0xa5, 0xb7, 0x34, 0x57, 0xaa, 0x13, 0xc3, 0x74, 0xc7, 0x56, 0xa3, 0xfd, 0x9d, \n  0xe5, 0x0f, 0xea, 0x05, 0x5b, 0xf2, 0xef, 0xa1, 0x50, 0xa5, 0x35, 0x7b, 0x93, 0x34, 0x8f, 0x3e, \n  0x56, 0xf8, 0x3f, 0xc5, 0xcd, 0xd3, 0x49, 0xa5, 0xe4, 0x6c, 0xcd, 0xa0, 0x29, 0xf9, 0xac, 0xd6, \n  0x8a, 0x7d, 0xe4, 0xfb, 0x2e, 0x93, 0x43, 0xce, 0x52, 0xb5, 0x8e, 0xcc, 0x97, 0x81, 0xb6, 0x1e, \n  0xc4, 0xba, 0xc9, 0xf3, 0x93, 0xf8, 0x8c, 0x0f, 0xda, 0x26, 0x12, 0xac, 0xd7, 0x09, 0xa2, 0xd1, \n  0x46, 0xc3, 0xae, 0x0d, 0x6d, 0x46, 0xa9, 0xf7, 0x70, 0xfc, 0xe1, 0x99, 0x18, 0x5d, 0xf3, 0x42, \n  0x26, 0x11, 0x4b, 0x35, 0x31, 0x2c, 0x92, 0x38, 0x4e, 0xf9, 0xc8, 0x58, 0x2b, 0x78, 0x60, 0xb1, \n  0x0e, 0x26, 0x7c, 0x9a, 0x17, 0xfc, 0x10, 0xd5, 0xcb, 0xdc, 0x51, 0x77, 0x44, 0x81, 0xf1, 0x2d, \n  0x49, 0x32, 0x6c, 0xca, 0x91, 0x4a, 0x7c, 0xf4, 0x1f, 0xb4, 0x25, 0x7e, 0xdf, 0x19, 0x34, 0xf4, \n  0x9c, 0xa1, 0x04, 0x42, 0xaa, 0x33, 0x37, 0x0e, 0x8b, 0x16, 0x09, 0x8a, 0xdb, 0x53, 0xfb, 0xd4, \n  0x6c, 0x22, 0xf2, 0x74, 0x25, 0xf9, 0x08, 0x90, 0x7b, 0xda, 0x14, 0x13, 0x30, 0x5a, 0x9d, 0x5b, \n  0x9d, 0x70, 0xa8, 0xae, 0x34, 0x0a, 0x50, 0x7d, 0xf2, 0xf8, 0x61, 0xfb, 0x1e, 0x7e, 0x9a, 0xcc, \n  0xdc, 0x59, 0xf2, 0xb3, 0x10, 0x68, 0x61, 0xce, 0x42, 0x63, 0x2d, 0x90, 0xf1, 0xc8, 0x67, 0x43, \n  0x05, 0xf9, 0x66, 0xf7, 0xdc, 0xd7, 0x89, 0x48, 0x26, 0x49, 0x0a, 0x82, 0x6c, 0x9e, 0xc4, 0x31, \n  0xcf, 0x50, 0xaf, 0xf4, 0xd6, 0xc9, 0x34, 0xd9, 0xd8, 0x66, 0x60, 0x1f, 0x15, 0xf6, 0x27, 0x94, \n  0xc6, 0x9f, 0x61, 0x54, 0xa1, 0x68, 0x38, 0x6d, 0x1a, 0x43, 0xb0, 0xa3, 0xe3, 0x8b, 0x74, 0xc3, \n  0x96, 0x4b, 0xce, 0x0a, 0x96, 0x45, 0x5c, 0x49, 0x62, 0x9b, 0xf6, 0xb5, 0xed, 0xd8, 0x5c, 0xf1, \n  0xe9, 0xd3, 0xa7, 0x4d, 0x8d, 0x93, 0x64, 0x82, 0x4b, 0x4d, 0x34, 0xca, 0x56, 0x40, 0x6e, 0x1c, \n  0x4c, 0x47, 0x95, 0x6f, 0x57, 0x59, 0xb5, 0x96, 0x65, 0x66, 0xb9, 0x6d, 0x96, 0xfa, 0x18, 0x8a, \n  0x2e, 0x28, 0x83, 0xa0, 0xb7, 0xe6, 0x93, 0x8f, 0x89, 0xec, 0x89, 0x34, 0x01, 0x58, 0xe4, 0x7c, \n  0xb5, 0x98, 0x6c, 0xcc, 0xc3, 0xf6, 0x19, 0x0e, 0x9c, 0xe9, 0xcf, 0xd3, 0xbd, 0xc3, 0x1d, 0x94, \n  0x5b, 0xa6, 0xcd, 0x5e, 0x8e, 0xaa, 0x74, 0x8c, 0xe2, 0xa8, 0xc7, 0x8f, 0xdb, 0xc2, 0xce, 0xf0, \n  0x51, 0xad, 0x34, 0xd0, 0xf3, 0x35, 0xa7, 0xaf, 0x14, 0x42, 0xdf, 0x8c, 0x44, 0x17, 0x6a, 0x47, \n  0x75, 0xe8, 0xb9, 0x8f, 0x95, 0x36, 0x8a, 0x8b, 0x83, 0xfc, 0xf3, 0xa4, 0x8f, 0xca, 0x51, 0xdd, \n  0xd5, 0x74, 0x68, 0x7e, 0x45, 0x91, 0x73, 0xd6, 0x7f, 0x50, 0x3f, 0xf7, 0x46, 0xdd, 0x5e, 0x17, \n  0x5e, 0x7a, 0xb0, 0xd7, 0x58, 0xa8, 0x98, 0x61, 0x9a, 0xdc, 0xf0, 0xb8, 0xf4, 0xc5, 0xd7, 0x1b, \n  0x96, 0x25, 0x0b, 0xe5, 0x96, 0x8b, 0xaf, 0x9d, 0x47, 0xc2, 0xe1, 0x4c, 0xf0, 0x5e, 0xbe, 0x92, \n  0x4e, 0x92, 0x4d, 0x21, 0x70, 0xc2, 0xbb, 0xa8, 0x7d, 0xd7, 0x39, 0xed, 0xef, 0xca, 0x2b, 0xb4, \n  0xea, 0xac, 0x19, 0x36, 0xd9, 0x75, 0x4b, 0xa3, 0xb3, 0xe1, 0x83, 0x8a, 0xcc, 0x10, 0xd1, 0x43, \n  0x0b, 0xbe, 0x5e, 0xcc, 0x41, 0xc4, 0xf9, 0x03, 0x31, 0xaa, 0x9f, 0xc9, 0x64, 0x01, 0x8e, 0xc5, \n  0x74, 0x95, 0x29, 0x34, 0x28, 0x7c, 0x96, 0xbe, 0x78, 0xd4, 0x31, 0x73, 0x78, 0xa7, 0x99, 0xa7, \n  0x1d, 0x33, 0x1f, 0xdd, 0x69, 0xe6, 0x59, 0xc7, 0xcc, 0xd3, 0x43, 0x33, 0xa3, 0xd5, 0x24, 0x89, \n  0x7a, 0x13, 0xfe, 0x63, 0xc2, 0x0b, 0xd7, 0x3f, 0xa5, 0x7d, 0x3a, 0xa0, 0x03, 0xaf, 0xfc, 0xdb, \n  0x8f, 0xfc, 0x76, 0x5a, 0xb0, 0x05, 0x17, 0x8e, 0xf8, 0x7a, 0xd3, 0x7f, 0xb0, 0x41, 0x4e, 0x84, \n  0x00, 0x44, 0x80, 0xbf, 0x52, 0x26, 0xf9, 0xef, 0xdc, 0xbe, 0x57, 0x0e, 0xbb, 0xfb, 0xdc, 0x61, \n  0xbf, 0xbf, 0xbc, 0xf1, 0xca, 0x2f, 0xf6, 0x4d, 0x3d, 0xeb, 0x9f, 0x41, 0xbf, 0xcc, 0xbb, 0xbb, \n  0x9f, 0xa8, 0xe9, 0xa5, 0xbf, 0x9c, 0x35, 0x95, 0xae, 0x62, 0x94, 0x01, 0xdb, 0xcf, 0x77, 0x83, \n  0x3b, 0xd8, 0x10, 0x96, 0xcf, 0xdc, 0xaf, 0x1d, 0xc5, 0xe1, 0x59, 0xed, 0x63, 0x63, 0x94, 0x49, \n  0xf9, 0xfe, 0xbb, 0xba, 0x7a, 0x87, 0xbd, 0x6c, 0x39, 0x75, 0x2a, 0x46, 0x56, 0x24, 0x61, 0x39, \n  0x5b, 0x2b, 0x0f, 0x48, 0x05, 0xaa, 0xbc, 0x8e, 0xc3, 0xf4, 0x63, 0x1c, 0xe6, 0xf8, 0xcb, 0xd9, \n  0x21, 0xbe, 0x7c, 0xda, 0xbf, 0x8b, 0x38, 0x32, 0xdc, 0x67, 0xec, 0xe4, 0x7e, 0x17, 0x9d, 0x37, \n  0x8c, 0xf2, 0x91, 0xb1, 0x61, 0x7f, 0xec, 0x25, 0x59, 0xcc, 0x6f, 0x82, 0x33, 0x04, 0x47, 0x03, \n  0x8c, 0x50, 0x99, 0xc5, 0x06, 0xa5, 0xbf, 0xc8, 0xe3, 0xcf, 0x50, 0xbe, 0xfb, 0xf4, 0xe3, 0xdd, \n  0x2f, 0xaf, 0x3b, 0xe4, 0xf4, 0x68, 0xd8, 0x30, 0x43, 0xf6, 0x84, 0x7f, 0x2c, 0xb7, 0xa4, 0xdb, \n  0xca, 0xbb, 0xb7, 0xc8, 0x63, 0x96, 0x76, 0xa9, 0x57, 0xdb, 0xb5, 0xfe, 0x3c, 0x89, 0x56, 0xa1, \n  0x71, 0xd0, 0xef, 0xf7, 0xf5, 0x0e, 0xef, 0xf3, 0x25, 0xcf, 0x3e, 0x34, 0xc3, 0x84, 0xaa, 0xc7, \n  0xb6, 0x3a, 0xe2, 0x22, 0x5f, 0xf6, 0xa6, 0x49, 0x2a, 0x79, 0x11, 0x4c, 0xd2, 0x55, 0xe1, 0x0e, \n  0x96, 0x37, 0x5e, 0x43, 0xeb, 0xf6, 0xf1, 0xff, 0xce, 0x9e, 0xda, 0x76, 0x97, 0x01, 0x6e, 0x0f, \n  0x14, 0x3d, 0xb8, 0x33, 0xb9, 0x69, 0x7b, 0x72, 0x0a, 0x2f, 0x11, 0x4b, 0x23, 0x17, 0x6e, 0xc4, \n  0xe9, 0x39, 0xa7, 0xfe, 0x19, 0x5f, 0x78, 0xa5, 0xbf, 0x88, 0x28, 0x4c, 0x68, 0x9a, 0xcf, 0x95, \n  0x4f, 0x07, 0x83, 0x60, 0x8c, 0x09, 0x12, 0x80, 0xc8, 0xec, 0xbc, 0x45, 0xf0, 0x90, 0xa7, 0x69, \n  0xbe, 0xd6, 0xb6, 0x73, 0xc6, 0xae, 0x77, 0x62, 0x66, 0x3c, 0x8b, 0x47, 0x7f, 0x6e, 0xe0, 0xf8, \n  0x50, 0xec, 0x52, 0xdb, 0x15, 0x00, 0xe2, 0x00, 0x4d, 0xb1, 0x8c, 0x5d, 0x3b, 0xac, 0x15, 0xfd, \n  0x6a, 0x0b, 0x86, 0x34, 0x67, 0x12, 0x6d, 0xd0, 0x76, 0x30, 0xc9, 0xa0, 0xe0, 0xb1, 0x8e, 0x93, \n  0xeb, 0xe5, 0x1a, 0x71, 0x67, 0xdb, 0xe3, 0xc7, 0x5e, 0xcd, 0x44, 0x77, 0x50, 0xb7, 0x0d, 0xbe, \n  0xbe, 0xd7, 0x1f, 0x3e, 0x7e, 0xf2, 0x64, 0xd0, 0x08, 0x8e, 0xa3, 0xb3, 0xde, 0xba, 0x46, 0xb5, \n  0x8b, 0x9f, 0x44, 0x79, 0xd6, 0x0c, 0x06, 0xc1, 0x73, 0x1d, 0x75, 0xec, 0xa9, 0xe0, 0xe9, 0x21, \n  0x22, 0xd7, 0x03, 0x55, 0xf4, 0x1a, 0xa3, 0xda, 0xc6, 0x6b, 0xeb, 0xe9, 0x50, 0x63, 0x6f, 0x80, \n  0x56, 0xf8, 0x3c, 0x6e, 0x85, 0x4e, 0x0b, 0x2e, 0xa3, 0x79, 0x67, 0x0c, 0xa6, 0x93, 0x23, 0x2d, \n  0xc1, 0x59, 0xc0, 0x19, 0x4a, 0x0c, 0xb6, 0x43, 0x78, 0xbd, 0x11, 0x35, 0xc2, 0x07, 0x1d, 0xf0, \n  0xb6, 0x16, 0x2d, 0xf2, 0xf5, 0x1e, 0x96, 0x2c, 0x7d, 0x31, 0x5f, 0xef, 0xdc, 0x49, 0xfb, 0xae, \n  0x77, 0x05, 0x63, 0xa1, 0xad, 0x83, 0x07, 0x98, 0x1a, 0x68, 0xbb, 0xbf, 0x60, 0x2c, 0xa3, 0x5a, \n  0x80, 0xb0, 0xf3, 0x3e, 0x61, 0x60, 0xbc, 0xf0, 0x27, 0x8d, 0x38, 0x08, 0xae, 0xe7, 0x47, 0xf9, \n  0x62, 0xc9, 0x22, 0x79, 0xe8, 0x2a, 0x30, 0xec, 0x62, 0x1c, 0x76, 0x3b, 0x22, 0x98, 0x44, 0xc6, \n  0x31, 0x28, 0x6a, 0x13, 0xc6, 0xb6, 0x08, 0x31, 0x7c, 0x6b, 0xc5, 0xee, 0x52, 0xb6, 0x14, 0x3c, \n  0x30, 0x3f, 0x46, 0x76, 0x34, 0x5b, 0x4d, 0x7b, 0x0a, 0xc7, 0x9c, 0x8b, 0x9d, 0xe0, 0x79, 0x17, \n  0x5a, 0x0d, 0xbf, 0x96, 0x3e, 0xe4, 0xba, 0x1a, 0x2e, 0xca, 0x59, 0xed, 0x3d, 0x9c, 0x3d, 0x50, \n  0xfd, 0x0e, 0x86, 0x16, 0xfd, 0x44, 0xf4, 0x0a, 0xce, 0xe2, 0x3c, 0x4b, 0x1b, 0x09, 0xa2, 0x8a, \n  0xf3, 0x14, 0x59, 0x9f, 0x7e, 0x71, 0x76, 0xf6, 0xf8, 0x69, 0x2b, 0xcc, 0x83, 0xc7, 0x11, 0x0d, \n  0x44, 0x55, 0x22, 0xa3, 0x19, 0x04, 0xef, 0xe3, 0xd0, 0x3b, 0x79, 0x48, 0x20, 0x36, 0x76, 0x4d, \n  0x03, 0xd4, 0x27, 0x7d, 0xa7, 0x82, 0xe7, 0xc9, 0xf4, 0xe9, 0xe0, 0xe9, 0xd9, 0xa8, 0x81, 0x6c, \n  0xf0, 0x90, 0x2a, 0x81, 0xa6, 0x1c, 0x36, 0x3b, 0x25, 0x80, 0xf2, 0x40, 0x11, 0x77, 0x35, 0x8a, \n  0xa7, 0x69, 0xb2, 0x14, 0x89, 0x18, 0xad, 0xe7, 0x89, 0xe4, 0x3d, 0xb1, 0x64, 0xe8, 0x72, 0x00, \n  0x21, 0x21, 0xc4, 0x1d, 0x92, 0xa1, 0xcb, 0x71, 0xd0, 0x41, 0x95, 0x03, 0x8a, 0xf2, 0xa0, 0x12, \n  0x55, 0xd3, 0x5b, 0xf1, 0xc8, 0x61, 0x2b, 0x94, 0xab, 0xc2, 0x6c, 0xbb, 0x3c, 0xdc, 0x61, 0xf8, \n  0x28, 0x69, 0x0f, 0x29, 0x17, 0x23, 0x5d, 0xa2, 0x82, 0xc7, 0xa2, 0x83, 0x84, 0x4c, 0xaa, 0x6b, \n  0x87, 0x94, 0xe0, 0x0a, 0x2d, 0x66, 0x97, 0xc5, 0x6e, 0x34, 0xb5, 0xe1, 0xb4, 0x36, 0x99, 0xb6, \n  0xbc, 0xb7, 0xee, 0xc9, 0x49, 0x4a, 0xef, 0xa1, 0xab, 0xfc, 0x89, 0x80, 0xf2, 0xf0, 0x80, 0x56, \n  0xb0, 0xa2, 0x55, 0x75, 0x1c, 0x18, 0x83, 0x64, 0xb8, 0xc1, 0xa6, 0x39, 0xac, 0x07, 0xa7, 0xbe, \n  0x27, 0xe6, 0xf9, 0xba, 0x97, 0x71, 0xb9, 0xce, 0x8b, 0x8f, 0x62, 0x97, 0x1d, 0xcb, 0x7b, 0x6c, \n  0x92, 0xaf, 0x64, 0x47, 0xd8, 0xdd, 0xd6, 0x25, 0xfd, 0xa5, 0xd4, 0x03, 0x3b, 0x55, 0xc7, 0xd0, \n  0x2b, 0xff, 0x76, 0xc1, 0xe3, 0x84, 0x39, 0x22, 0x2a, 0x38, 0xcf, 0x1c, 0x96, 0xc5, 0x8e, 0x5b, \n  0x27, 0xcd, 0xbe, 0x00, 0x07, 0xcf, 0xdb, 0x34, 0x73, 0x43, 0xb0, 0xf9, 0xe1, 0x69, 0x8f, 0xfb, \n  0x4f, 0x76, 0xa6, 0xa9, 0xc8, 0x0a, 0x5c, 0x20, 0xcf, 0x20, 0x39, 0x2d, 0x7a, 0x59, 0x2e, 0x39, \n  0xf5, 0x75, 0xe6, 0x5a, 0x70, 0x29, 0x93, 0x6c, 0x26, 0x36, 0xb5, 0x1d, 0x85, 0x2e, 0x96, 0x2d, \n  0xa9, 0x41, 0x4a, 0x5a, 0x6a, 0x56, 0x49, 0xa2, 0x2e, 0x5d, 0xdf, 0xc8, 0x8f, 0x35, 0x83, 0xa7, \n  0xe8, 0x29, 0xe8, 0x55, 0x2b, 0x63, 0xc1, 0x61, 0x9b, 0x36, 0x83, 0xed, 0x57, 0xa9, 0x28, 0x05, \n  0xaa, 0x64, 0x09, 0xd5, 0x8a, 0xd9, 0xb6, 0xba, 0xf1, 0x99, 0x5f, 0x70, 0xb1, 0xcc, 0x33, 0x81, \n  0x56, 0x6d, 0xb7, 0xce, 0x74, 0x58, 0xb3, 0x03, 0x83, 0x59, 0x65, 0x6b, 0xf2, 0x9e, 0x6c, 0x63, \n  0xeb, 0x04, 0xf6, 0x6e, 0xac, 0xb9, 0xa0, 0xb6, 0x32, 0x90, 0xef, 0x2c, 0x95, 0x88, 0x56, 0x47, \n  0x43, 0x0f, 0x5a, 0xb2, 0x56, 0x93, 0xd9, 0xb0, 0x95, 0x52, 0x6c, 0xae, 0xdb, 0x92, 0xcb, 0x75, \n  0xc2, 0xb3, 0x79, 0x85, 0x9f, 0xa0, 0x95, 0xd3, 0x2f, 0x9e, 0x02, 0xad, 0x60, 0x62, 0xdf, 0x4a, \n  0x91, 0xa8, 0x1c, 0x73, 0x9d, 0x87, 0x47, 0x92, 0x2f, 0xcf, 0x4f, 0x54, 0x11, 0x83, 0xaa, 0x67, \n  0x88, 0x8a, 0x64, 0x29, 0xc7, 0x51, 0x9e, 0x09, 0xe9, 0xf0, 0x90, 0xfc, 0xdf, 0xff, 0xfc, 0xef, \n  0xfe, 0x91, 0x50, 0x89, 0x3f, 0xfe, 0x89, 0xd0, 0x0c, 0x7e, 0xfc, 0xd3, 0x7f, 0x21, 0x54, 0xe0, \n  0x8f, 0xff, 0x4e, 0x68, 0x0e, 0x3f, 0xfe, 0xf1, 0x7f, 0x13, 0xca, 0x42, 0xf2, 0xa7, 0x7f, 0xf9, \n  0x3f, 0x09, 0x4d, 0xe0, 0xc1, 0xbf, 0xf9, 0x6f, 0x84, 0x16, 0xf0, 0xe3, 0x3f, 0xfe, 0x2b, 0x42, \n  0xa3, 0x90, 0xfc, 0xe9, 0x3f, 0xfc, 0x57, 0x42, 0x53, 0x78, 0xf0, 0xef, 0xff, 0xed, 0xff, 0xf9, \n  0x1f, 0xff, 0x9a, 0xd0, 0x38, 0x24, 0x7f, 0xfa, 0x4f, 0xff, 0x4c, 0xe8, 0x0a, 0xfe, 0xfd, 0x17, \n  0x84, 0x2e, 0x43, 0x1e, 0x8e, 0xe3, 0x3c, 0x5a, 0x2d, 0x78, 0x26, 0xfd, 0x19, 0x97, 0x2f, 0x53, \n  0x0e, 0x3f, 0xbf, 0xbc, 0x7d, 0x15, 0xbb, 0xdc, 0xa3, 0x53, 0xe8, 0xdf, 0x28, 0xb0, 0x64, 0xb8, \n  0x74, 0xb9, 0x37, 0x92, 0xc7, 0xc7, 0xd2, 0x8f, 0x52, 0x26, 0xc4, 0xeb, 0x44, 0x48, 0x9f, 0xc5, \n  0xb1, 0x4b, 0x80, 0x84, 0x88, 0x57, 0xd2, 0xc5, 0x27, 0x87, 0x17, 0x7c, 0x91, 0x5f, 0xf3, 0x7a, \n  0xc6, 0x3c, 0x74, 0x39, 0x95, 0x34, 0xf3, 0xaa, 0x79, 0x42, 0xcd, 0x2b, 0xb8, 0x5c, 0x15, 0x99, \n  0x23, 0x8e, 0x8f, 0x05, 0xec, 0xf1, 0x12, 0x52, 0x15, 0xb0, 0x02, 0xcf, 0x78, 0xe1, 0xc2, 0x04, \n  0x2a, 0x4a, 0xba, 0xc6, 0xd9, 0xe1, 0xa6, 0xac, 0xa7, 0x67, 0x61, 0x75, 0x9c, 0xa8, 0xe0, 0x4c, \n  0x72, 0x7d, 0x22, 0x58, 0x73, 0x9a, 0x17, 0x2e, 0x8e, 0x7a, 0xcf, 0xa9, 0xf8, 0x90, 0x4f, 0x9d, \n  0x6f, 0x27, 0x3f, 0xf0, 0x48, 0xfa, 0x3c, 0x93, 0x45, 0xc2, 0x85, 0x2b, 0x3d, 0x2f, 0xf3, 0x05, \n  0x97, 0xcf, 0xa4, 0x2c, 0x92, 0xc9, 0x4a, 0x72, 0x97, 0x53, 0x51, 0x81, 0x92, 0x95, 0x74, 0x12, \n  0xbe, 0xc1, 0x3c, 0xae, 0x9b, 0xe6, 0x11, 0x46, 0x05, 0x90, 0xd2, 0xb6, 0x5b, 0x97, 0xcc, 0xa5, \n  0x5c, 0x8a, 0x80, 0x84, 0x61, 0x58, 0x77, 0x15, 0xb9, 0xcc, 0xa3, 0x3c, 0xbd, 0x20, 0xa7, 0xa7, \n  0x8f, 0x48, 0x40, 0x9e, 0xf4, 0x89, 0x07, 0x01, 0x4e, 0xe9, 0xcc, 0xe8, 0x2d, 0xbd, 0xa6, 0x37, \n  0xf4, 0x39, 0xbd, 0x1f, 0x5e, 0xdd, 0xdf, 0xec, 0xcc, 0x28, 0x4f, 0x4e, 0xac, 0xa7, 0xf3, 0x5c, \n  0x48, 0x28, 0x99, 0x29, 0x83, 0xfb, 0x9b, 0x49, 0x79, 0x72, 0x45, 0x2f, 0xc3, 0xf7, 0x1f, 0xe8, \n  0xeb, 0xb0, 0x37, 0xa0, 0x1f, 0xc3, 0x3e, 0x7d, 0x19, 0x82, 0x06, 0x86, 0xa3, 0xbf, 0x0b, 0x33, \n  0xbe, 0x76, 0xbe, 0x61, 0x4b, 0xfa, 0x5d, 0xb8, 0x89, 0x84, 0x08, 0xa0, 0x79, 0xc9, 0x25, 0xfd, \n  0xa1, 0xfa, 0x59, 0x8e, 0x4c, 0xfc, 0xc2, 0x79, 0xa5, 0x11, 0xbf, 0x59, 0xba, 0x64, 0xc1, 0x85, \n  0x60, 0x33, 0x90, 0x02, 0x32, 0xe5, 0xc4, 0xf3, 0x93, 0x2c, 0xe3, 0xc5, 0xd7, 0xef, 0xbe, 0x79, \n  0x1d, 0x72, 0x6a, 0x75, 0x03, 0x61, 0x37, 0x7a, 0x25, 0xf6, 0x82, 0x3b, 0x46, 0x3c, 0x1f, 0x5c, \n  0xb5, 0xf0, 0xa8, 0x8f, 0x8f, 0x58, 0x92, 0x11, 0xcf, 0x47, 0x02, 0xf7, 0x95, 0x63, 0x16, 0x12, \n  0xf4, 0xcc, 0x1e, 0x2d, 0x6f, 0x3c, 0x42, 0xaf, 0x43, 0x62, 0xe0, 0x20, 0x61, 0x08, 0x89, 0xd9, \n  0x7c, 0xea, 0x64, 0x17, 0x59, 0x90, 0xad, 0xd2, 0x94, 0x66, 0x17, 0x0b, 0x97, 0xe4, 0x1f, 0x7b, \n  0x7a, 0xe1, 0x60, 0x6a, 0xb7, 0xca, 0xea, 0x00, 0xcf, 0x5c, 0xae, 0x80, 0x6f, 0xec, 0x3f, 0xd8, \n  0xbb, 0x3f, 0xa1, 0xfc, 0xf8, 0xf8, 0xfa, 0xf8, 0xf8, 0xda, 0xf5, 0xe8, 0x75, 0x08, 0x3b, 0xd5, \n  0x6b, 0xbd, 0x81, 0xb5, 0x0c, 0xdd, 0x5e, 0xdd, 0xdf, 0xf0, 0x32, 0xbc, 0x32, 0xf7, 0x5e, 0xd3, \n  0x53, 0x9e, 0x7f, 0x4c, 0xb8, 0x2f, 0x96, 0x69, 0x22, 0x5d, 0x32, 0x22, 0x9e, 0xbf, 0x60, 0x4b, \n  0x97, 0x87, 0x63, 0xee, 0xcb, 0x22, 0x59, 0xb8, 0x9e, 0xe7, 0x4f, 0x93, 0x2c, 0x56, 0x4f, 0x50, \n  0x6e, 0x8b, 0xef, 0x12, 0x39, 0x07, 0xb2, 0xba, 0xf0, 0x45, 0x9a, 0x44, 0xdc, 0x95, 0x7e, 0xca, \n  0xb3, 0x99, 0x9c, 0x7b, 0xdb, 0x2d, 0x21, 0xf5, 0xf6, 0x5f, 0xb9, 0xde, 0x26, 0x99, 0xba, 0x37, \n  0xea, 0xbf, 0x3e, 0x18, 0x7f, 0xb7, 0x97, 0x92, 0x49, 0x1e, 0x86, 0xe1, 0x77, 0x7c, 0x72, 0x99, \n  0x47, 0x1f, 0xb9, 0xf4, 0xbf, 0x7d, 0xfb, 0xf2, 0x8d, 0xa7, 0x81, 0x7a, 0x5b, 0xe4, 0x8b, 0x44, \n  0x70, 0x90, 0x95, 0x79, 0x7a, 0xcd, 0x5d, 0x6f, 0x74, 0x68, 0xe6, 0xf3, 0x6f, 0xdf, 0xbc, 0x79, \n  0xf9, 0xfc, 0xdd, 0xab, 0x37, 0xbf, 0x38, 0x3e, 0x7e, 0x6e, 0x96, 0x78, 0x5e, 0x9a, 0x1f, 0x48, \n  0x37, 0x7a, 0x45, 0x17, 0xc8, 0xc2, 0x66, 0x27, 0x03, 0xa4, 0x6b, 0x10, 0xc4, 0xc3, 0xc3, 0xd4, \n  0xbe, 0x16, 0x22, 0x20, 0x01, 0x59, 0x8b, 0x00, 0x24, 0x58, 0x83, 0x8c, 0xb7, 0xdb, 0xab, 0xbd, \n  0x84, 0x7d, 0x45, 0xb3, 0x70, 0x6f, 0xef, 0xc3, 0x61, 0x69, 0xee, 0x83, 0xc4, 0x3c, 0x4e, 0x22, \n  0x26, 0x79, 0x0c, 0xfb, 0xbf, 0x71, 0x09, 0x17, 0xcb, 0xef, 0xa7, 0xe2, 0xfb, 0xb5, 0xf8, 0x7e, \n  0x91, 0x83, 0x50, 0xb9, 0x78, 0x8f, 0x17, 0x08, 0x3c, 0x94, 0x95, 0x27, 0x57, 0x1f, 0x82, 0xba, \n  0x2d, 0xcb, 0x13, 0xc1, 0xe5, 0x6a, 0xd9, 0x5b, 0x8b, 0xab, 0x0f, 0xa5, 0xeb, 0x51, 0x11, 0xba, \n  0x19, 0xcd, 0xeb, 0xe3, 0x32, 0x44, 0x45, 0x85, 0x38, 0x37, 0x7b, 0x9f, 0x7f, 0x50, 0x3c, 0x9b, \n  0x84, 0x47, 0x83, 0xd1, 0x4d, 0xc8, 0x34, 0xaf, 0x15, 0xa1, 0x0b, 0xb3, 0x92, 0xed, 0xd6, 0xbd, \n  0x09, 0xc3, 0x90, 0x1d, 0x1f, 0xbb, 0x37, 0x48, 0x53, 0x1e, 0xcd, 0x1f, 0x0e, 0xce, 0x33, 0x7d, \n  0xcf, 0x17, 0x02, 0xd6, 0x7f, 0x38, 0xf0, 0x02, 0xf7, 0x39, 0x76, 0x53, 0xe9, 0xc2, 0x0e, 0x2f, \n  0x8b, 0x22, 0x2f, 0x5c, 0x52, 0x6d, 0x04, 0x35, 0x70, 0x99, 0xd2, 0x8a, 0xce, 0x94, 0x25, 0x29, \n  0x8f, 0x89, 0xe7, 0x79, 0x5e, 0x39, 0x62, 0x7e, 0x9e, 0x21, 0x61, 0xab, 0xed, 0x80, 0xbf, 0x3e, \n  0x1e, 0x1f, 0xbb, 0x51, 0xca, 0x59, 0xf1, 0x2e, 0x59, 0xf0, 0x7c, 0x25, 0xdd, 0x8f, 0x1e, 0x08, \n  0x02, 0x8f, 0x72, 0xd7, 0x2b, 0x29, 0x4c, 0xd0, 0xfc, 0x8a, 0xc2, 0x58, 0x16, 0xb7, 0x9b, 0xa3, \n  0xea, 0x0e, 0x39, 0x52, 0xd8, 0x11, 0xdf, 0x6e, 0x49, 0x8e, 0xb2, 0x8f, 0x1c, 0x19, 0xf6, 0xe3, \n  0x9a, 0x2e, 0x80, 0x90, 0x48, 0xc1, 0x05, 0x60, 0x97, 0xfb, 0xd0, 0x79, 0x7c, 0x0c, 0x74, 0xf6, \n  0xc7, 0x57, 0x71, 0xcd, 0x22, 0xef, 0x40, 0x57, 0xb8, 0xe6, 0x31, 0x4c, 0x39, 0x92, 0x66, 0xbe, \n  0xa6, 0xaa, 0x06, 0x88, 0xd2, 0x97, 0xc9, 0x82, 0x17, 0x1e, 0x7d, 0xe7, 0xc7, 0x3c, 0xe5, 0x20, \n  0x59, 0xf5, 0x5c, 0x7a, 0x9d, 0x27, 0xb1, 0xcb, 0xfd, 0xfc, 0xe3, 0x85, 0xac, 0xa8, 0x99, 0xfb, \n  0x4b, 0x76, 0x0b, 0xd9, 0xee, 0xed, 0x76, 0x53, 0x7a, 0x01, 0x74, 0x00, 0xb0, 0x16, 0xea, 0xb8, \n  0xcf, 0xe1, 0xdf, 0xed, 0xf6, 0xea, 0x79, 0xbe, 0x58, 0x80, 0x1e, 0x56, 0x68, 0x0b, 0x9c, 0xfb, \n  0x1b, 0xe9, 0x23, 0xdd, 0x5c, 0x01, 0x02, 0x09, 0xbf, 0x96, 0xf6, 0x49, 0x6a, 0x4c, 0x50, 0xe9, \n  0x55, 0x34, 0xfe, 0xfd, 0x7b, 0xfe, 0x61, 0x94, 0x1d, 0x1f, 0x67, 0xae, 0xf4, 0x4a, 0x97, 0xe3, \n  0x7c, 0xda, 0x04, 0xa2, 0x74, 0x7f, 0x79, 0xf9, 0xed, 0x1b, 0x7f, 0x09, 0x85, 0x8f, 0x2e, 0xf7, \n  0x63, 0x26, 0x99, 0xe7, 0x95, 0x11, 0x93, 0xd1, 0xdc, 0xc8, 0x8e, 0x3c, 0xd5, 0x50, 0xb9, 0xe4, \n  0x55, 0x76, 0xcd, 0xc0, 0x7c, 0xfe, 0xee, 0xd2, 0xd1, 0x97, 0x41, 0x28, 0xf7, 0x4a, 0x75, 0x3d, \n  0x38, 0xa6, 0xa2, 0x9f, 0xc2, 0xdc, 0x5a, 0x94, 0xe6, 0x82, 0xeb, 0xc7, 0x53, 0xf7, 0x28, 0x31, \n  0x7c, 0x0a, 0x08, 0x72, 0x0a, 0xd7, 0x1b, 0x19, 0xf6, 0x3b, 0xaa, 0x24, 0x92, 0xf6, 0xa8, 0xae, \n  0x59, 0xe1, 0xc8, 0x51, 0x93, 0x0e, 0xa9, 0xa1, 0xb7, 0xb0, 0x93, 0xde, 0xe2, 0x44, 0x68, 0x92, \n  0x03, 0x52, 0xa3, 0xef, 0xa0, 0x44, 0xee, 0x25, 0x83, 0xb3, 0x00, 0x2f, 0xd8, 0x57, 0xc7, 0xcd, \n  0xd5, 0x71, 0x73, 0x0b, 0xd2, 0x2b, 0x61, 0x06, 0x8e, 0x72, 0x3d, 0x0f, 0xc4, 0xaa, 0xfb, 0x31, \n  0x5c, 0x27, 0x59, 0x9c, 0xaf, 0x41, 0x7d, 0x9a, 0x99, 0x78, 0x94, 0xaf, 0x5c, 0xcf, 0x57, 0x58, \n  0xc2, 0x66, 0xe9, 0x95, 0x74, 0x70, 0xd6, 0xef, 0x7b, 0x5e, 0x59, 0x8e, 0x80, 0x39, 0xfa, 0xb0, \n  0xd8, 0xf3, 0x92, 0x89, 0xdb, 0x2c, 0x72, 0x2a, 0x81, 0xf8, 0xad, 0xeb, 0x6d, 0x80, 0xeb, 0x38, \n  0xea, 0x6c, 0xf8, 0x25, 0xc3, 0xfe, 0x48, 0x9e, 0x3f, 0x19, 0xc9, 0x87, 0x0f, 0x3d, 0xa0, 0xe9, \n  0x64, 0xea, 0xb2, 0x35, 0x4b, 0x24, 0xc8, 0x4e, 0x7a, 0x73, 0x7c, 0x7c, 0x17, 0xa9, 0x39, 0x92, \n  0xf3, 0x22, 0x5f, 0x3b, 0x9d, 0x08, 0xc9, 0x72, 0xe9, 0xe0, 0x0a, 0xc4, 0xdc, 0x6a, 0xe6, 0x6d, \n  0x78, 0x98, 0x51, 0x79, 0xfe, 0xc5, 0xf1, 0xb1, 0xda, 0xca, 0x16, 0x93, 0x80, 0xa7, 0xdd, 0x23, \n  0x73, 0xfa, 0xe8, 0xac, 0xff, 0x37, 0xae, 0x7c, 0x38, 0xf0, 0xbc, 0xd2, 0x2b, 0xd5, 0x7e, 0x7c, \n  0xbb, 0xbd, 0x2b, 0xcf, 0xb7, 0xf1, 0xf0, 0x8d, 0xb6, 0x6f, 0x68, 0x16, 0x0e, 0xce, 0xf8, 0x23, \n  0xaf, 0x3e, 0xf6, 0xb7, 0xae, 0x47, 0x8f, 0x6e, 0xb6, 0x5b, 0xfb, 0xdc, 0x47, 0x3b, 0xe7, 0xbe, \n  0xdb, 0x81, 0x47, 0xc6, 0xf2, 0xba, 0xba, 0xbf, 0x79, 0xc1, 0x24, 0xf7, 0xb3, 0x7c, 0xed, 0x7a, \n  0x65, 0xef, 0xfe, 0xe6, 0xe1, 0xc3, 0x97, 0xe5, 0x15, 0xcd, 0xc3, 0x0d, 0x70, 0x4f, 0x40, 0xa2, \n  0x45, 0x4c, 0x28, 0xf2, 0x6d, 0x20, 0x28, 0x30, 0x49, 0xc0, 0xa9, 0x66, 0x92, 0x40, 0x96, 0x95, \n  0xa1, 0x64, 0x6b, 0x13, 0x49, 0x59, 0x2d, 0x5e, 0x93, 0x7d, 0x54, 0x52, 0x89, 0x05, 0xe1, 0x51, \n  0x66, 0xb1, 0xf9, 0x95, 0x1e, 0xe5, 0x4c, 0xf3, 0xc2, 0x89, 0x34, 0xa7, 0x83, 0x34, 0xbf, 0xf2, \n  0xbc, 0x92, 0x66, 0xde, 0xe8, 0x1d, 0x2c, 0xe5, 0x0a, 0xba, 0xd1, 0xa2, 0x23, 0x90, 0x54, 0x51, \n  0x69, 0xc0, 0x28, 0xd2, 0x6d, 0x90, 0x68, 0x40, 0x4b, 0x8f, 0xde, 0xf8, 0x82, 0x67, 0xb1, 0x62, \n  0x64, 0x21, 0x8b, 0x24, 0x9b, 0x25, 0xd3, 0x5b, 0x37, 0xc7, 0xab, 0xaa, 0x50, 0xfe, 0x47, 0x60, \n  0x67, 0xee, 0x27, 0xcb, 0xe3, 0x63, 0xf7, 0x80, 0x79, 0x06, 0x23, 0x8c, 0x45, 0x06, 0xbc, 0x61, \n  0xd4, 0xd5, 0xf1, 0xf1, 0xd2, 0x25, 0xa6, 0x41, 0xbc, 0xe3, 0x63, 0xb7, 0xd1, 0xf6, 0xaf, 0x59, \n  0xba, 0xe2, 0x61, 0x3d, 0xde, 0x2b, 0x35, 0x4a, 0x40, 0xca, 0x7f, 0x03, 0xa6, 0x4c, 0xf8, 0x8a, \n  0xea, 0x47, 0x28, 0x11, 0xd4, 0xb3, 0x67, 0xfa, 0x8e, 0xbe, 0x0f, 0x37, 0x04, 0x3c, 0x78, 0x5f, \n  0x13, 0x8f, 0xb2, 0x33, 0x78, 0x4c, 0x02, 0x20, 0xc8, 0x85, 0x4b, 0xe0, 0x2e, 0x78, 0x41, 0x3c, \n  0xfa, 0xca, 0x25, 0xdf, 0x25, 0x5f, 0x25, 0x84, 0x5e, 0x3d, 0xd7, 0x74, 0x96, 0xcd, 0x1c, 0x99, \n  0x3b, 0xe7, 0x93, 0x31, 0x40, 0x2f, 0x44, 0x12, 0x6f, 0xb7, 0x4b, 0x97, 0xc0, 0x0f, 0x03, 0xd7, \n  0x76, 0xab, 0xe6, 0x94, 0xe7, 0x27, 0x93, 0xb1, 0xef, 0xfb, 0x57, 0x5e, 0x49, 0x5b, 0xdb, 0xad, \n  0xa2, 0x88, 0x0b, 0xa1, 0xb6, 0x9b, 0x5a, 0xdb, 0x01, 0xda, 0x28, 0xf7, 0xc1, 0x44, 0x04, 0x7d, \n  0x01, 0xff, 0xfa, 0x49, 0x16, 0xa5, 0xab, 0x98, 0x0b, 0x97, 0x30, 0xc4, 0x6d, 0xaf, 0xe0, 0x08, \n  0x6e, 0xaf, 0xe0, 0x7f, 0x5c, 0x25, 0x05, 0x10, 0xfc, 0x45, 0x05, 0xa6, 0x9a, 0x43, 0x5f, 0x78, \n  0x41, 0xeb, 0xd1, 0x76, 0x4b, 0x9e, 0x5b, 0xa2, 0x2a, 0xe7, 0xae, 0x47, 0x7f, 0xef, 0xee, 0x40, \n  0x86, 0x52, 0x75, 0x17, 0xae, 0xbd, 0x8b, 0xd9, 0x6c, 0x87, 0x8b, 0x96, 0xa5, 0xc6, 0xf1, 0x0f, \n  0x28, 0x84, 0x5f, 0xb9, 0xe4, 0xd7, 0x0a, 0x5c, 0x42, 0xcd, 0x2f, 0x27, 0xcb, 0xd7, 0x17, 0x84, \n  0xbe, 0xf0, 0x46, 0x2d, 0x3e, 0x7d, 0xe1, 0x7a, 0xa8, 0x69, 0x15, 0x6f, 0x7e, 0xe3, 0x12, 0x15, \n  0x0a, 0xf0, 0x0b, 0xb3, 0xc2, 0xa6, 0xa4, 0xc0, 0xbf, 0xb5, 0xc2, 0x28, 0x9f, 0xb9, 0x47, 0x03, \n  0x04, 0x4f, 0xaf, 0xcd, 0x63, 0x42, 0x09, 0x54, 0xcf, 0x17, 0x55, 0xdb, 0xa2, 0xca, 0xb7, 0x5a, \n  0x75, 0xab, 0xff, 0x2e, 0xd1, 0xea, 0xe9, 0x29, 0x7b, 0x47, 0x91, 0x58, 0xdd, 0xf6, 0xc1, 0x33, \n  0x7e, 0xae, 0x6b, 0xea, 0x39, 0xe4, 0x9d, 0xe0, 0x5e, 0x89, 0x07, 0x86, 0xf1, 0x34, 0x29, 0x16, \n  0x6b, 0x56, 0x54, 0xb3, 0xea, 0x76, 0x6b, 0x96, 0xe9, 0xa8, 0x66, 0x62, 0xe0, 0xc5, 0x4c, 0xd3, \n  0x0d, 0x7f, 0x5e, 0xf0, 0x69, 0xc8, 0xfd, 0x34, 0x99, 0xac, 0x8a, 0x74, 0xbb, 0x25, 0xf7, 0x88, \n  0x35, 0xb4, 0xb5, 0xa2, 0x1a, 0x74, 0x71, 0xf5, 0x1c, 0xbd, 0xb2, 0xd8, 0x59, 0x27, 0x72, 0x0e, \n  0xac, 0xac, 0x3b, 0xca, 0xab, 0x40, 0xef, 0x04, 0x27, 0x49, 0x96, 0xc4, 0xab, 0xad, 0x8d, 0x9a, \n  0x5b, 0xb6, 0xdb, 0x1d, 0xfb, 0x70, 0x64, 0x4d, 0xb1, 0x5c, 0x93, 0xab, 0x73, 0xe6, 0x20, 0x78, \n  0x68, 0xac, 0x06, 0xca, 0xfa, 0xf3, 0x61, 0x76, 0x7a, 0x42, 0xc6, 0x3b, 0xcf, 0xce, 0x4f, 0xd8, \n  0xb8, 0x9a, 0x71, 0x7f, 0x73, 0xbf, 0x24, 0x63, 0xc7, 0x55, 0x5c, 0x8e, 0xa6, 0xba, 0x07, 0x03, \n  0xae, 0xca, 0xdb, 0x10, 0xac, 0x02, 0x39, 0xa7, 0x9f, 0xc7, 0xe4, 0x15, 0x16, 0x81, 0xd9, 0x7a, \n  0xf6, 0x24, 0xeb, 0x41, 0x03, 0x5f, 0xe4, 0xf2, 0xf2, 0xd5, 0x0b, 0xe2, 0x95, 0x6d, 0x85, 0xf0, \n  0xb5, 0xf2, 0xda, 0xa8, 0x30, 0xd8, 0xc9, 0x43, 0x97, 0x01, 0x76, 0xd9, 0x84, 0xa7, 0xf4, 0x12, \n  0x85, 0x9a, 0xcb, 0x70, 0x3f, 0x5f, 0xe6, 0xaf, 0xf3, 0x35, 0x2f, 0x9e, 0x33, 0xc1, 0x5d, 0x4f, \n  0xbb, 0x28, 0x7e, 0xc1, 0xb1, 0xd0, 0xd5, 0x3d, 0x79, 0xff, 0x07, 0xd6, 0xfb, 0xb1, 0xdf, 0x7b, \n  0xfa, 0xe1, 0xe1, 0xc9, 0x8c, 0x92, 0x1e, 0xb1, 0xba, 0xfe, 0xd0, 0x7b, 0xb8, 0xed, 0x3d, 0xbc, \n  0x0f, 0xcf, 0xc1, 0x65, 0x05, 0xf3, 0xc2, 0xd8, 0xbd, 0x49, 0xb8, 0x74, 0x73, 0xef, 0xe2, 0xea, \n  0xfe, 0x26, 0x07, 0xfd, 0x90, 0x95, 0x57, 0x41, 0x8e, 0xf6, 0x71, 0x41, 0x23, 0xb4, 0x1c, 0xe1, \n  0x8d, 0x87, 0xda, 0xe0, 0x42, 0x6a, 0x2d, 0xc2, 0xb5, 0x4b, 0xe2, 0xe4, 0x9a, 0x80, 0x80, 0x40, \n  0xd4, 0xa0, 0x06, 0x37, 0xb7, 0xab, 0x78, 0x66, 0xca, 0x91, 0x31, 0x74, 0xff, 0xa8, 0xb0, 0xae, \n  0x52, 0x0d, 0x90, 0x88, 0x1f, 0xd7, 0x62, 0xa1, 0xa2, 0x89, 0x31, 0xd4, 0x18, 0xa4, 0x2c, 0x79, \n  0x2a, 0xb8, 0x03, 0xa0, 0x4c, 0xf2, 0x3c, 0xe5, 0x2c, 0xb3, 0xa1, 0x31, 0x7b, 0x6a, 0x80, 0xe8, \n  0x06, 0x23, 0x13, 0x01, 0x91, 0x93, 0x35, 0x29, 0xbd, 0x11, 0x42, 0x8a, 0xa8, 0xac, 0xbb, 0xd2, \n  0x49, 0xea, 0xc8, 0x59, 0x4a, 0xe8, 0x14, 0xde, 0x8f, 0x28, 0x3d, 0x1a, 0xc1, 0x20, 0x0c, 0xd4, \n  0x13, 0xba, 0x49, 0xe2, 0x20, 0xa1, 0x5a, 0x39, 0xea, 0x2a, 0x65, 0x42, 0xcd, 0xa2, 0x91, 0x93, \n  0x64, 0x04, 0x66, 0xf8, 0xba, 0x5e, 0x2a, 0x3c, 0x3a, 0xd2, 0x27, 0xa4, 0x85, 0x0f, 0x15, 0x35, \n  0x59, 0xec, 0x46, 0x74, 0x07, 0x18, 0x01, 0xc0, 0x50, 0x1d, 0x9c, 0x60, 0x42, 0x24, 0xb3, 0xcc, \n  0x5d, 0xbb, 0x44, 0x2c, 0x59, 0x66, 0x8d, 0x4a, 0x27, 0x29, 0x0c, 0xdb, 0x58, 0x48, 0x08, 0x34, \n  0x21, 0x28, 0x92, 0xf5, 0xa8, 0xd4, 0x9b, 0x3c, 0x9f, 0x27, 0x69, 0xec, 0x16, 0x1e, 0x2d, 0x42, \n  0x59, 0xe1, 0xa7, 0xd8, 0xc5, 0x02, 0xee, 0x5b, 0x34, 0x26, 0xed, 0x40, 0xb1, 0x8b, 0x9e, 0x1a, \n  0x35, 0x87, 0x60, 0x21, 0xaa, 0xca, 0xd9, 0xba, 0x0d, 0x44, 0xa4, 0x7e, 0xaa, 0x31, 0xa9, 0x17, \n  0x55, 0x68, 0x03, 0xaf, 0x60, 0x09, 0x54, 0x2f, 0xb6, 0xdb, 0xf7, 0x1f, 0xbc, 0xca, 0x44, 0x95, \n  0xe1, 0x38, 0xc2, 0xb8, 0x13, 0xd8, 0x09, 0xdf, 0xe2, 0x08, 0x57, 0x52, 0x49, 0x8f, 0x06, 0x86, \n  0x01, 0xa4, 0x17, 0x86, 0xa1, 0xfe, 0x6d, 0x48, 0xca, 0xf3, 0xda, 0x47, 0x8b, 0xbc, 0x51, 0x45, \n  0x2b, 0xaa, 0x06, 0x6a, 0x97, 0x54, 0xc4, 0x0e, 0x92, 0x44, 0x8a, 0xa4, 0xb2, 0x8f, 0x0a, 0x0a, \n  0x96, 0x81, 0xb1, 0x5f, 0x1d, 0xc4, 0x11, 0x29, 0xa1, 0x8b, 0x24, 0x0b, 0xb8, 0xbf, 0x48, 0xb2, \n  0x8b, 0x8b, 0x3e, 0x5d, 0xb0, 0x1b, 0x68, 0xb0, 0x9b, 0x8b, 0x8b, 0x41, 0xbf, 0x4f, 0x85, 0xe4, \n  0xcb, 0x00, 0xe2, 0x05, 0x7c, 0x79, 0x71, 0x31, 0x40, 0x6a, 0x31, 0xe2, 0x03, 0xff, 0xbd, 0xb8, \n  0x30, 0x33, 0x47, 0x86, 0xe3, 0x9b, 0x3b, 0x5f, 0xdd, 0xdf, 0x24, 0xa5, 0x4a, 0x18, 0xad, 0xe4, \n  0x95, 0x06, 0x43, 0xbd, 0x6f, 0xd0, 0x84, 0xa3, 0x50, 0x80, 0x44, 0xb0, 0x1c, 0x42, 0x11, 0x01, \n  0x14, 0x0a, 0x80, 0x08, 0x01, 0x28, 0xbd, 0x51, 0xae, 0x77, 0xd7, 0x50, 0xd0, 0x1c, 0x5d, 0x1d, \n  0xc1, 0xa5, 0x2f, 0x78, 0xf4, 0x0a, 0x32, 0xe3, 0xa1, 0xb4, 0x1e, 0xf2, 0x54, 0x3d, 0xcb, 0xa8, \n  0xa8, 0x09, 0x3a, 0x6f, 0x63, 0x5a, 0x78, 0x48, 0x75, 0x15, 0xfb, 0x19, 0xf0, 0x2a, 0x74, 0x5f, \n  0x98, 0x27, 0x01, 0x01, 0x12, 0x22, 0xfb, 0xd0, 0x2b, 0x9b, 0x14, 0x62, 0xad, 0x23, 0x8f, 0x8f, \n  0x5d, 0x70, 0x78, 0x8e, 0x42, 0x44, 0x17, 0xf8, 0xc8, 0xf0, 0xaf, 0x6a, 0x79, 0xb4, 0xea, 0x62, \n  0x37, 0xaa, 0x8b, 0xdd, 0xa8, 0x56, 0xdd, 0x05, 0x08, 0xc0, 0x3e, 0xf8, 0xa1, 0xdb, 0x5e, 0xc7, \n  0x75, 0x10, 0xb2, 0x43, 0x47, 0x65, 0x32, 0x75, 0x23, 0x9c, 0xdb, 0x81, 0xac, 0x68, 0x17, 0x59, \n  0x68, 0x30, 0xa2, 0xef, 0x76, 0x7c, 0x5c, 0x74, 0x07, 0x53, 0x29, 0x87, 0xac, 0x27, 0x78, 0x79, \n  0xde, 0xa7, 0x65, 0x59, 0x53, 0x32, 0x44, 0x0b, 0x48, 0x24, 0x42, 0x94, 0x1b, 0x08, 0x55, 0xb6, \n  0xf4, 0xb0, 0x5e, 0xb5, 0x75, 0x06, 0xd9, 0xba, 0xa1, 0x36, 0xd5, 0x47, 0x0b, 0xf9, 0x59, 0x8b, \n  0x89, 0x96, 0xe4, 0x69, 0xeb, 0xb1, 0x1f, 0x55, 0xc4, 0xab, 0xf2, 0x64, 0xff, 0xb8, 0xe2, 0xc5, \n  0xed, 0x25, 0xca, 0x82, 0xbc, 0x78, 0x96, 0xa6, 0x2e, 0xf1, 0x27, 0xf9, 0x8d, 0x1f, 0xdf, 0x42, \n  0x1c, 0xcf, 0xf2, 0x4d, 0xb9, 0x89, 0x20, 0x7b, 0x1e, 0x3d, 0x30, 0xf9, 0x5e, 0xc6, 0xae, 0x7b, \n  0x69, 0x92, 0x7d, 0x74, 0xd8, 0x7b, 0xc0, 0x7e, 0x2f, 0xbe, 0xcd, 0xd8, 0x22, 0x89, 0xc2, 0xbf, \n  0x1e, 0xfc, 0xf5, 0x87, 0xfd, 0x2b, 0x1e, 0xcd, 0xb6, 0xdb, 0xa3, 0x67, 0x45, 0xc1, 0x6e, 0xfd, \n  0x44, 0xe0, 0xbf, 0xee, 0x0c, 0xae, 0x12, 0x85, 0x90, 0x67, 0xbc, 0x48, 0xe3, 0x87, 0x57, 0x81, \n  0x46, 0x0a, 0xb1, 0x6e, 0x32, 0x91, 0x59, 0x6f, 0x5e, 0x10, 0x3b, 0xcc, 0x9c, 0xa9, 0x30, 0x73, \n  0xbd, 0x46, 0x15, 0x6a, 0xf6, 0x6a, 0xfd, 0x7d, 0xa5, 0x84, 0x1c, 0xbc, 0xc9, 0xa2, 0x34, 0x2a, \n  0x65, 0xbb, 0xe8, 0x97, 0x99, 0x33, 0xc9, 0x6f, 0x9c, 0xf8, 0x36, 0x73, 0x90, 0x3e, 0x68, 0x12, \n  0x07, 0x79, 0xe9, 0xd1, 0x04, 0x86, 0xce, 0x87, 0xc4, 0x1b, 0x25, 0x8d, 0xab, 0x11, 0xaa, 0x1c, \n  0x61, 0xbb, 0xbd, 0x52, 0x02, 0x52, 0x38, 0xf7, 0x37, 0xd9, 0xc3, 0x01, 0x2c, 0xde, 0xb8, 0x99, \n  0x04, 0x08, 0x0d, 0x2a, 0x61, 0x0a, 0xf9, 0x25, 0x16, 0xd4, 0xb8, 0x8c, 0x4a, 0xaf, 0x0a, 0x72, \n  0xad, 0x5d, 0xc2, 0x6a, 0x28, 0x18, 0xa2, 0x14, 0xf7, 0xbe, 0x12, 0x5c, 0xf6, 0xf2, 0xa5, 0xd4, \n  0x10, 0x13, 0x44, 0x32, 0x28, 0xbf, 0x20, 0xd7, 0x0d, 0x8d, 0x71, 0x12, 0x90, 0x01, 0xaa, 0xd6, \n  0x3b, 0x82, 0x57, 0x60, 0x24, 0x24, 0x89, 0x3e, 0x86, 0x19, 0xc6, 0x9e, 0xcd, 0x45, 0x12, 0xaf, \n  0x45, 0x51, 0x68, 0x75, 0x44, 0x18, 0xe4, 0x68, 0x04, 0xf6, 0xe5, 0x87, 0x7c, 0xea, 0x0a, 0x9f, \n  0xab, 0xb0, 0xbf, 0x56, 0x1d, 0x3b, 0x68, 0x17, 0xa1, 0xcd, 0x52, 0x52, 0x47, 0x87, 0x8e, 0x06, \n  0x47, 0xd0, 0x80, 0xec, 0xd8, 0x72, 0x24, 0x8e, 0x8f, 0x61, 0xf1, 0x30, 0x0c, 0x91, 0xb5, 0x77, \n  0xb5, 0xe5, 0x0c, 0x35, 0x41, 0x0b, 0xa1, 0x91, 0xe7, 0x51, 0x65, 0xb0, 0x7c, 0x0d, 0x69, 0x0a, \n  0xca, 0xa9, 0xb8, 0x88, 0x02, 0xe6, 0x95, 0x65, 0x6d, 0xcc, 0xff, 0xda, 0x8e, 0x36, 0x73, 0x5f, \n  0xb2, 0x62, 0xc6, 0xa5, 0x0a, 0xa2, 0x59, 0xc2, 0x00, 0xf2, 0xbe, 0x2c, 0xc9, 0x04, 0xc8, 0x41, \n  0x52, 0x11, 0x1f, 0x9c, 0x3a, 0x0b, 0x71, 0xb4, 0xf4, 0x93, 0x18, 0xb2, 0x2b, 0x49, 0xec, 0xf3, \n  0x2c, 0x56, 0xb1, 0x66, 0x62, 0x14, 0x41, 0x6d, 0x3f, 0x03, 0xa1, 0xe2, 0xa0, 0xca, 0xbe, 0x33, \n  0x63, 0xee, 0x9f, 0x28, 0x03, 0x0f, 0x02, 0x38, 0x59, 0xc8, 0xbd, 0xd2, 0xe0, 0x26, 0xdb, 0x91, \n  0x63, 0x34, 0xb7, 0x1e, 0x6a, 0x39, 0x06, 0x30, 0x28, 0x0c, 0x89, 0xed, 0x56, 0xfd, 0xc8, 0xb7, \n  0xdb, 0xcf, 0xe1, 0x21, 0x16, 0xba, 0x56, 0xe7, 0x7b, 0x9d, 0x5c, 0x11, 0xde, 0x07, 0x0c, 0xb9, \n  0x35, 0xef, 0xd0, 0xf4, 0xe6, 0xde, 0x87, 0x11, 0x84, 0xb9, 0xec, 0xfb, 0x63, 0x4a, 0x89, 0x30, \n  0x2d, 0xaa, 0x8f, 0x8e, 0x32, 0x63, 0x71, 0x05, 0x96, 0x66, 0x57, 0xa3, 0xb6, 0x5b, 0x4b, 0x6b, \n  0xb4, 0x26, 0xea, 0x2d, 0x32, 0x6d, 0x32, 0x04, 0xe6, 0xb9, 0x79, 0x50, 0x5f, 0xe1, 0x97, 0xfb, \n  0xae, 0x50, 0x1b, 0x00, 0x16, 0x51, 0x1d, 0xb8, 0x53, 0xeb, 0x86, 0xae, 0x20, 0x6e, 0x99, 0xc4, \n  0xb5, 0x22, 0x57, 0xd7, 0xa2, 0xb5, 0x4e, 0x28, 0x0d, 0x04, 0x40, 0x3b, 0x35, 0x18, 0xbf, 0xac, \n  0x22, 0xba, 0x06, 0xb1, 0x2d, 0x8f, 0x3e, 0x34, 0x9e, 0x3e, 0x21, 0xf4, 0x80, 0xbb, 0x51, 0x8d, \n  0x42, 0xb7, 0x63, 0x54, 0x1f, 0x0c, 0xc2, 0x20, 0xa4, 0xef, 0xe3, 0xff, 0x93, 0xa3, 0x10, 0x9f, \n  0xc0, 0x1e, 0xf1, 0x3c, 0x02, 0x7f, 0xab, 0xb2, 0x6b, 0x25, 0x95, 0x17, 0xee, 0xc2, 0x25, 0x51, \n  0x9e, 0x4d, 0xb1, 0x1a, 0x5f, 0xf9, 0x3b, 0xe8, 0x94, 0x19, 0x40, 0x94, 0x2b, 0x05, 0x8f, 0x67, \n  0x4c, 0xf2, 0x35, 0xbb, 0xb5, 0xfa, 0xf4, 0x93, 0x0a, 0xce, 0xd5, 0x24, 0xe3, 0xd2, 0x3e, 0x04, \n  0x3e, 0x30, 0xdd, 0x71, 0x26, 0x06, 0x56, 0x27, 0x34, 0xad, 0xae, 0x61, 0xb3, 0x6b, 0x88, 0xde, \n  0x50, 0xe0, 0x4e, 0x0f, 0x41, 0xd7, 0x0d, 0x57, 0x27, 0x30, 0x1d, 0x20, 0x74, 0x6c, 0xad, 0x1d, \n  0x3e, 0xf3, 0x92, 0xa7, 0xf5, 0xbc, 0xbe, 0xbd, 0xdf, 0xb8, 0xde, 0xe6, 0xf5, 0x79, 0x7f, 0xbb, \n  0x7d, 0x3d, 0x0e, 0x2f, 0x75, 0xc6, 0x60, 0xbb, 0xfd, 0xa5, 0x7b, 0xf9, 0xfe, 0xf5, 0x07, 0xeb, \n  0x92, 0x7f, 0x81, 0x41, 0xc0, 0xd7, 0xde, 0xe6, 0x32, 0xe4, 0xc0, 0x0a, 0xf4, 0x75, 0x35, 0xfa, \n  0xe2, 0x1b, 0x26, 0xe7, 0x60, 0xe1, 0xb8, 0x72, 0x1c, 0xf6, 0x2f, 0x64, 0xd0, 0xa7, 0xa6, 0xab, \n  0x37, 0xf0, 0x82, 0xde, 0x80, 0xfe, 0xce, 0xf5, 0xe8, 0x6f, 0x5c, 0x6b, 0xb5, 0xdf, 0xb9, 0x36, \n  0xd9, 0x61, 0x0c, 0x05, 0x6b, 0x51, 0x7a, 0x50, 0x38, 0x44, 0x54, 0x2c, 0xbf, 0xa2, 0x26, 0x6e, \n  0xb9, 0x61, 0xa4, 0xa6, 0x0b, 0x33, 0x0f, 0x04, 0xbe, 0xe7, 0xe7, 0xd3, 0xa9, 0xe0, 0xf2, 0x3b, \n  0x48, 0x81, 0x6f, 0xb7, 0x15, 0x40, 0x3a, 0x86, 0x85, 0xf3, 0xb1, 0x8f, 0x3e, 0x39, 0xed, 0x7b, \n  0x34, 0x0b, 0x71, 0xc4, 0x34, 0xcd, 0xf3, 0xc2, 0x75, 0x65, 0xef, 0x71, 0xdf, 0x3b, 0x19, 0x9c, \n  0xf6, 0x71, 0x5f, 0x3d, 0x65, 0x01, 0x0e, 0xde, 0x37, 0x90, 0x67, 0x77, 0x89, 0x95, 0x5d, 0x77, \n  0x54, 0x29, 0x06, 0xa6, 0xdd, 0x64, 0x34, 0xe7, 0x62, 0xbb, 0x35, 0x67, 0x1d, 0x67, 0x4d, 0x73, \n  0xc8, 0xf8, 0x14, 0xb5, 0xb1, 0x48, 0x31, 0x17, 0x18, 0x90, 0xdd, 0x22, 0x16, 0x50, 0x4e, 0x97, \n  0x95, 0x41, 0xe0, 0xf2, 0x66, 0x2e, 0x7a, 0xed, 0x12, 0xa5, 0x9e, 0x09, 0xdd, 0xe0, 0x05, 0x06, \n  0x59, 0xe9, 0x8d, 0x44, 0x37, 0xf3, 0xb8, 0x7c, 0xb1, 0x94, 0xb7, 0x0e, 0xf0, 0x90, 0x07, 0x49, \n  0xfb, 0x30, 0x7c, 0x7d, 0x7c, 0xec, 0x0a, 0x5f, 0xc1, 0x03, 0x6c, 0xd2, 0x6f, 0xfb, 0x64, 0x02, \n  0xc2, 0xdf, 0x72, 0x37, 0xc3, 0x4d, 0xa2, 0xb9, 0x72, 0x24, 0x20, 0x9e, 0xf5, 0x3a, 0xc4, 0xb4, \n  0xc3, 0xab, 0x0c, 0xa3, 0xf1, 0x28, 0x6a, 0xb4, 0x75, 0x3e, 0xe8, 0xab, 0xdb, 0x05, 0x2d, 0xde, \n  0x61, 0xce, 0x39, 0xd6, 0xc1, 0xe4, 0xce, 0xc1, 0x26, 0x2b, 0x29, 0x73, 0xdb, 0xa3, 0x64, 0x13, \n  0xb2, 0x73, 0x38, 0x79, 0xf0, 0x70, 0xa2, 0x6d, 0xbc, 0xaa, 0xb2, 0x11, 0xe2, 0x51, 0x51, 0xe9, \n  0x71, 0x11, 0x8e, 0x37, 0xbc, 0xcb, 0xc0, 0x83, 0xfd, 0x5a, 0xa6, 0xd8, 0x6e, 0xa1, 0x80, 0x59, \n  0x10, 0x56, 0xd4, 0x47, 0xdf, 0xb7, 0xe5, 0xeb, 0x30, 0xa3, 0xbf, 0x84, 0xa3, 0xb7, 0x90, 0x01, \n  0x38, 0x1e, 0x1d, 0x75, 0xe4, 0x31, 0x2b, 0xf2, 0xe9, 0x8f, 0xde, 0x13, 0x15, 0x80, 0x46, 0x56, \n  0x20, 0x94, 0x60, 0x72, 0x43, 0xd5, 0x68, 0x41, 0x0b, 0xf8, 0x43, 0xc5, 0x30, 0x45, 0x4f, 0x9b, \n  0xd6, 0x1f, 0x6c, 0x7f, 0xb4, 0xca, 0x24, 0x2d, 0x5d, 0xe9, 0x61, 0x26, 0xc9, 0x82, 0x52, 0xe6, \n  0xb3, 0x59, 0x6a, 0x6a, 0x1e, 0xe8, 0x11, 0x87, 0x70, 0xb3, 0xbb, 0x63, 0x11, 0xff, 0x5e, 0x87, \n  0x10, 0x7f, 0xe1, 0xba, 0x55, 0x14, 0xd1, 0x2a, 0x31, 0x82, 0x84, 0x1b, 0xf1, 0x3c, 0xbb, 0xea, \n  0x68, 0x6f, 0xfa, 0x09, 0x32, 0x4d, 0xd5, 0xba, 0xbf, 0x52, 0x96, 0x76, 0x5b, 0xce, 0x18, 0x0e, \n  0x7f, 0xe5, 0x92, 0x17, 0x78, 0x72, 0x47, 0x47, 0x8b, 0x75, 0x0b, 0xc3, 0xc4, 0x20, 0x88, 0xf0, \n  0xfe, 0x31, 0x20, 0x7c, 0x71, 0x45, 0x11, 0x66, 0x8c, 0xd8, 0x1f, 0x02, 0x55, 0xa1, 0x12, 0x1c, \n  0x37, 0xac, 0xa4, 0x7e, 0x5d, 0x36, 0xe1, 0xa6, 0x7d, 0x8f, 0x62, 0xf4, 0xb3, 0x86, 0xff, 0x95, \n  0xab, 0xc3, 0x37, 0x94, 0x7c, 0x85, 0x31, 0x59, 0x88, 0x54, 0xeb, 0x65, 0xbc, 0xd2, 0x0e, 0xcf, \n  0xff, 0x9d, 0xeb, 0x6d, 0xcc, 0x19, 0x8e, 0x8f, 0x5f, 0xb9, 0xe4, 0x39, 0xdc, 0x94, 0xf3, 0x2c, \n  0x4d, 0x09, 0x35, 0x27, 0x61, 0x69, 0x8a, 0xa7, 0x71, 0xac, 0x4d, 0x2f, 0xc8, 0x1d, 0x81, 0xc7, \n  0x9b, 0x6f, 0x61, 0x9a, 0xf6, 0x06, 0x77, 0x03, 0x59, 0x4f, 0x06, 0x88, 0x5b, 0xd7, 0xfb, 0x5b, \n  0xd7, 0xb3, 0x88, 0x84, 0x88, 0x88, 0x65, 0x5a, 0x13, 0x8d, 0xb2, 0x36, 0x45, 0x43, 0xf0, 0x9a, \n  0x78, 0xa3, 0x3a, 0x48, 0x96, 0x85, 0x15, 0xa8, 0x18, 0xf3, 0x86, 0xd9, 0x18, 0x53, 0x1e, 0xf0, \n  0x53, 0x94, 0x9c, 0x99, 0x5f, 0x70, 0x98, 0xd5, 0xc8, 0x19, 0xee, 0xe6, 0x5a, 0x7e, 0xab, 0x72, \n  0x6f, 0x16, 0x33, 0x64, 0x9e, 0x55, 0x76, 0x43, 0xd6, 0xbd, 0x54, 0x48, 0x02, 0x22, 0xa0, 0x43, \n  0xec, 0xe7, 0xed, 0xca, 0x9a, 0x17, 0xba, 0xf9, 0x55, 0xc1, 0x66, 0x58, 0x62, 0x03, 0x47, 0x11, \n  0x79, 0x21, 0x4d, 0x0d, 0x01, 0x64, 0x27, 0x0a, 0xa5, 0x8c, 0x78, 0xf5, 0xd3, 0xa3, 0x59, 0x2d, \n  0x94, 0x20, 0x90, 0x69, 0x25, 0xdf, 0xd7, 0x2e, 0x91, 0x85, 0x8e, 0x6a, 0xa0, 0x82, 0xb9, 0xbf, \n  0x11, 0xe5, 0x55, 0xe9, 0x8d, 0x58, 0x23, 0xb2, 0x2b, 0xe3, 0xf1, 0x39, 0xc6, 0x06, 0x1c, 0x7c, \n  0xa7, 0x9f, 0x40, 0x4d, 0x6a, 0x4e, 0xf4, 0x77, 0x65, 0xb4, 0xf0, 0x77, 0x92, 0xd8, 0xfc, 0xee, \n  0x55, 0x4b, 0x91, 0xf1, 0xf9, 0x09, 0x4c, 0x96, 0xf1, 0xf8, 0xfe, 0x26, 0x33, 0x94, 0xad, 0x9e, \n  0x38, 0x78, 0x09, 0x21, 0xa9, 0xca, 0xe1, 0x88, 0x1a, 0xa3, 0xc1, 0x2e, 0x9d, 0xf8, 0xcb, 0x45, \n  0x73, 0x36, 0x8f, 0x56, 0x45, 0x22, 0x6f, 0x2f, 0x78, 0x20, 0xd5, 0x2a, 0x57, 0x3a, 0x8f, 0x0b, \n  0x52, 0x0f, 0xe9, 0xac, 0x6d, 0x8c, 0xa9, 0x2d, 0x0f, 0x19, 0x62, 0xf5, 0x08, 0xcb, 0x72, 0xc0, \n  0x4f, 0x2a, 0xb8, 0x8a, 0x20, 0x96, 0xee, 0x55, 0xfb, 0x54, 0x57, 0x96, 0x1d, 0xd6, 0xb7, 0x32, \n  0x0c, 0x78, 0xa1, 0x72, 0x02, 0xb5, 0x31, 0xdd, 0x21, 0x86, 0x85, 0x4b, 0x1a, 0x95, 0x99, 0x50, \n  0x90, 0x95, 0x37, 0x44, 0x27, 0x03, 0xd5, 0xd2, 0xf4, 0xe5, 0x73, 0x9c, 0xa7, 0x17, 0x2e, 0xdd, \n  0xcc, 0x37, 0xb3, 0xd1, 0x4a, 0x3f, 0x20, 0x92, 0xa6, 0x49, 0xc6, 0xd2, 0xf4, 0x76, 0x93, 0x75, \n  0x48, 0x79, 0x45, 0xf1, 0xd4, 0x4a, 0xdc, 0xec, 0x44, 0xbe, 0xff, 0xde, 0xb6, 0xb8, 0x5b, 0x88, \n  0xd5, 0xc1, 0x6d, 0x8a, 0xac, 0xd5, 0x36, 0xb9, 0xd0, 0x55, 0x02, 0x0f, 0x67, 0xbb, 0xb5, 0xeb, \n  0x20, 0x34, 0xa7, 0x5c, 0xfa, 0x22, 0x5f, 0x70, 0x90, 0xe1, 0x4a, 0xd3, 0x41, 0x80, 0xc5, 0x2b, \n  0xa1, 0x66, 0xc7, 0xb3, 0xd3, 0x69, 0x07, 0x98, 0x51, 0x27, 0xa0, 0x08, 0xb5, 0x57, 0x37, 0x80, \n  0x6e, 0x60, 0xcd, 0x60, 0x0f, 0xb8, 0x06, 0xd2, 0xa0, 0x03, 0x6a, 0xba, 0xe4, 0x85, 0x40, 0x6b, \n  0x40, 0x62, 0x77, 0xd5, 0xaa, 0xad, 0x6e, 0x0a, 0x46, 0x78, 0xb0, 0x63, 0x8c, 0x53, 0xb0, 0x72, \n  0x93, 0x62, 0xc1, 0xe3, 0xe0, 0xe8, 0x88, 0x97, 0x0a, 0x29, 0x75, 0x72, 0xe2, 0xa2, 0x23, 0x55, \n  0xa1, 0x01, 0xc2, 0xb7, 0x62, 0xa6, 0x2e, 0x04, 0xcf, 0x64, 0x95, 0xbb, 0x80, 0x68, 0xd5, 0x91, \n  0xf4, 0x61, 0x0f, 0x0f, 0x5c, 0x94, 0xe5, 0xf7, 0x2c, 0x8e, 0x0b, 0x2e, 0x50, 0x60, 0xd4, 0x16, \n  0xb4, 0x39, 0x93, 0x34, 0xa6, 0x7c, 0xb8, 0x6b, 0x4f, 0xd7, 0x63, 0x94, 0x49, 0x1d, 0xee, 0x18, \n  0xd7, 0x7a, 0x84, 0x1d, 0x62, 0xb1, 0x0d, 0xed, 0xc6, 0x3d, 0xdb, 0xe6, 0xb6, 0x99, 0xc7, 0x11, \n  0x74, 0x98, 0x12, 0x72, 0x8f, 0x66, 0xa6, 0x35, 0x0c, 0x33, 0xcf, 0x54, 0x3b, 0xc9, 0x12, 0x32, \n  0x95, 0xb5, 0xe0, 0xd4, 0xd8, 0xfa, 0xb5, 0x4e, 0x49, 0x1a, 0x09, 0x6a, 0xe7, 0x10, 0x51, 0x9a, \n  0xd6, 0xf9, 0xd4, 0xdf, 0xe7, 0x2b, 0x87, 0x15, 0xdc, 0xc1, 0x3c, 0x17, 0x48, 0x7d, 0xb6, 0x5c, \n  0xa6, 0xb7, 0x98, 0xf6, 0x56, 0x5a, 0x07, 0x56, 0x9c, 0xad, 0xd4, 0x07, 0x93, 0x30, 0x73, 0x8d, \n  0xca, 0x34, 0xd3, 0x96, 0x94, 0x54, 0xd9, 0xd5, 0xf3, 0x49, 0x31, 0xc6, 0xff, 0x25, 0xe3, 0x77, \n  0xf3, 0x44, 0x38, 0x4b, 0x36, 0xe3, 0xce, 0x82, 0xdd, 0x3a, 0x90, 0xf4, 0xb5, 0x6b, 0x03, 0xe2, \n  0x15, 0xc4, 0xb1, 0x9d, 0x82, 0x37, 0xd6, 0xf5, 0xcf, 0x4f, 0x92, 0xb1, 0x59, 0xe3, 0x45, 0xee, \n  0xdc, 0xe6, 0x2b, 0x67, 0xcd, 0x32, 0x84, 0x67, 0x59, 0xe4, 0x11, 0xe7, 0xf1, 0xc5, 0x15, 0x05, \n  0x29, 0xf4, 0xf7, 0xee, 0x51, 0xdf, 0x03, 0xd1, 0xcc, 0xa2, 0x88, 0x2f, 0x25, 0x8f, 0x51, 0x6d, \n  0xea, 0xb3, 0x3c, 0x03, 0xd0, 0x61, 0xf9, 0x3d, 0x40, 0x4b, 0x2b, 0x15, 0x5c, 0xb1, 0x76, 0x2b, \n  0xbf, 0xaa, 0xd5, 0x20, 0xf7, 0x75, 0xa9, 0x4b, 0x77, 0x8e, 0x55, 0x27, 0x66, 0xaa, 0xad, 0xc9, \n  0x2b, 0x8c, 0x3a, 0xa1, 0x41, 0xe9, 0x1c, 0x3b, 0x6f, 0x2b, 0xfa, 0xaf, 0x75, 0x3d, 0xe7, 0x4d, \n  0x5f, 0x05, 0x3e, 0xde, 0xd2, 0x53, 0x41, 0x61, 0x28, 0xb5, 0x4b, 0xb9, 0x78, 0xdf, 0xff, 0xd0, \n  0x74, 0x58, 0xec, 0x0c, 0xf8, 0xc2, 0x25, 0xcb, 0x59, 0x81, 0xef, 0xa1, 0x68, 0x67, 0xac, 0x6a, \n  0xed, 0xb5, 0x21, 0x97, 0x2e, 0xd1, 0xdf, 0x7a, 0x48, 0xf3, 0x59, 0x3b, 0x27, 0xf7, 0x1b, 0xe8, \n  0x49, 0xb2, 0x99, 0xef, 0xfb, 0xb5, 0x2f, 0x04, 0xd7, 0xfe, 0x55, 0x5e, 0x2c, 0x5e, 0x30, 0xc9, \n  0x46, 0xc6, 0xb8, 0x37, 0xab, 0x40, 0xd5, 0xcf, 0xc8, 0x88, 0x0d, 0x18, 0xf9, 0xbb, 0x6f, 0x5e, \n  0x7f, 0x2d, 0xe5, 0x12, 0xa8, 0x8d, 0x0b, 0x39, 0xca, 0x30, 0xed, 0xef, 0x92, 0xb7, 0xdf, 0x5e, \n  0xbe, 0x23, 0xf4, 0xea, 0x44, 0xcd, 0xba, 0x80, 0x2a, 0xf3, 0x10, 0xb3, 0xf4, 0xc9, 0x8f, 0x50, \n  0xeb, 0x40, 0x33, 0x3f, 0xcf, 0xe0, 0x5c, 0x4a, 0xad, 0xd8, 0xf8, 0xdf, 0x77, 0xac, 0xb6, 0xfd, \n  0x7c, 0xf0, 0x64, 0xc3, 0x7e, 0x3f, 0x0c, 0x41, 0xf7, 0x48, 0x26, 0x57, 0xe2, 0x22, 0x33, 0x25, \n  0xd8, 0xfc, 0x1d, 0xbf, 0x91, 0x81, 0xbe, 0xe0, 0xd1, 0x9e, 0x9b, 0x50, 0xd1, 0xcf, 0xe9, 0xba, \n  0x07, 0xa9, 0xa0, 0x66, 0xd4, 0x02, 0x9c, 0x60, 0x89, 0x4c, 0xd8, 0x74, 0x29, 0x4c, 0x20, 0x49, \n  0xbf, 0xb4, 0x02, 0xbb, 0x40, 0xec, 0x01, 0xf5, 0x9a, 0x63, 0x52, 0xcf, 0xce, 0x24, 0xc9, 0x58, \n  0x71, 0xeb, 0xc0, 0x5e, 0x84, 0x9a, 0x13, 0xd5, 0xb5, 0xb9, 0x44, 0x57, 0xab, 0x82, 0x38, 0xc1, \n  0x42, 0xcf, 0xba, 0xb0, 0xdd, 0x78, 0xdf, 0xf5, 0x67, 0x3b, 0x20, 0xdf, 0xda, 0x7a, 0xd0, 0xac, \n  0xf4, 0x25, 0x8d, 0x4f, 0xaa, 0x10, 0x4a, 0xe0, 0xa3, 0x2a, 0x35, 0x7e, 0xe1, 0x3d, 0x60, 0x93, \n  0xb3, 0xad, 0xdb, 0x7a, 0x63, 0xf5, 0xdd, 0x3a, 0xd2, 0x7f, 0xa0, 0x54, 0x58, 0x7d, 0x1f, 0x25, \n  0xcd, 0xfc, 0xd5, 0x12, 0xee, 0xca, 0xcf, 0xb3, 0x65, 0x91, 0xcf, 0x50, 0x6e, 0x72, 0x74, 0x88, \n  0x94, 0x09, 0xfb, 0x3c, 0x5f, 0x2c, 0x57, 0xf8, 0x06, 0x12, 0xc2, 0x77, 0xc7, 0xad, 0xae, 0xee, \n  0x6f, 0xd0, 0x8b, 0xc6, 0xc3, 0xba, 0x1c, 0x3f, 0x0c, 0xc3, 0xe3, 0x13, 0xee, 0xcb, 0x5c, 0xb2, \n  0xf4, 0x6f, 0x06, 0xfd, 0xbe, 0x57, 0x3e, 0xb8, 0xc2, 0xed, 0xb1, 0xe2, 0x45, 0xee, 0x98, 0xa3, \n  0x50, 0x81, 0x57, 0x6b, 0x29, 0x15, 0xae, 0x9b, 0x16, 0xf9, 0xa2, 0x76, 0x30, 0x91, 0xbf, 0x50, \n  0xa7, 0xeb, 0xc8, 0x64, 0xd3, 0x6f, 0xb0, 0x6c, 0xd9, 0x34, 0x11, 0xc0, 0x15, 0xe0, 0xf4, 0xa1, \n  0xfe, 0x4d, 0xa2, 0x8f, 0x9a, 0x16, 0xb4, 0x32, 0xc9, 0x1a, 0xe6, 0xa4, 0x07, 0xc9, 0x6b, 0x71, \n  0x37, 0xec, 0x8b, 0x8e, 0xab, 0x9d, 0x14, 0xf9, 0x3a, 0xab, 0xa9, 0x51, 0x74, 0x53, 0x53, 0xc3, \n  0x8b, 0x6d, 0xf9, 0xb4, 0x58, 0x0f, 0x0a, 0xe9, 0x7d, 0x73, 0xa8, 0x12, 0x89, 0xac, 0x6e, 0x8f, \n  0x07, 0x17, 0x44, 0x90, 0x80, 0x90, 0xd2, 0x31, 0xbe, 0xbb, 0x77, 0x55, 0x42, 0xe8, 0x35, 0x0f, \n  0xfb, 0x94, 0x85, 0xfd, 0x3a, 0xe2, 0xec, 0x70, 0x27, 0x9f, 0x3a, 0xd2, 0x0e, 0x06, 0xaa, 0x2f, \n  0x28, 0xfc, 0x5a, 0xbf, 0xfb, 0xf3, 0x16, 0x2f, 0xaa, 0x4a, 0xad, 0x03, 0xb0, 0xff, 0x70, 0x82, \n  0x81, 0x57, 0x8a, 0x5e, 0x78, 0x9a, 0x90, 0x1d, 0x9f, 0x9b, 0xa2, 0x07, 0xd9, 0xf4, 0x61, 0xab, \n  0x04, 0x7c, 0x43, 0xe0, 0x24, 0x95, 0xc0, 0x81, 0x85, 0x09, 0xe5, 0xf4, 0x0a, 0xab, 0x1a, 0xae, \n  0x94, 0xc5, 0xe2, 0xda, 0xc9, 0x75, 0x72, 0xc2, 0xe3, 0x04, 0xc2, 0x22, 0x0b, 0x2e, 0xe7, 0x79, \n  0x1c, 0x68, 0xa9, 0x03, 0x65, 0x31, 0x90, 0xbd, 0xf5, 0xa0, 0x06, 0x33, 0x7f, 0xf8, 0x30, 0x70, \n  0xd9, 0xc3, 0x87, 0x15, 0xe6, 0xf1, 0x25, 0x9b, 0x90, 0x60, 0xb1, 0x4e, 0xad, 0x01, 0xf6, 0x8c, \n  0x28, 0x4b, 0x43, 0x3e, 0x28, 0x04, 0xec, 0x50, 0x85, 0xba, 0x78, 0x0c, 0x87, 0x34, 0x13, 0x01, \n  0x07, 0x65, 0x81, 0x9c, 0x73, 0x67, 0x8a, 0xdf, 0xe4, 0x72, 0x74, 0xe8, 0x14, 0x74, 0xd5, 0x6d, \n  0xbe, 0x2a, 0xf0, 0xc6, 0x04, 0xe9, 0xa4, 0x10, 0x78, 0xba, 0x23, 0x2d, 0x9a, 0x14, 0xe6, 0xa1, \n  0xe6, 0xfa, 0xea, 0xd2, 0xf9, 0x0d, 0xb2, 0x27, 0xa1, 0x57, 0xa0, 0x47, 0x55, 0x83, 0xc7, 0x4a, \n  0xf7, 0xe5, 0xa8, 0xfb, 0x34, 0x69, 0x40, 0x66, 0x20, 0x37, 0x54, 0x71, 0x7f, 0xc3, 0x2e, 0xae, \n  0xa8, 0x1a, 0xc5, 0xf4, 0x28, 0x54, 0x73, 0x57, 0x01, 0x71, 0x74, 0x89, 0xd4, 0x74, 0x95, 0xa6, \n  0xb7, 0xa4, 0x04, 0xc5, 0x59, 0x71, 0x5d, 0xc6, 0xbb, 0xc2, 0xc6, 0x34, 0x0b, 0xf1, 0xdd, 0x0b, \n  0x0b, 0xd8, 0x2a, 0x9d, 0x82, 0x3c, 0xb4, 0x74, 0x89, 0xcc, 0x97, 0xbd, 0x8c, 0x5d, 0x77, 0x8a, \n  0xf8, 0xfa, 0x7d, 0x19, 0x72, 0x30, 0x29, 0xc6, 0x3e, 0x27, 0xda, 0xb2, 0x3f, 0xcc, 0xf2, 0x89, \n  0x9c, 0xdd, 0xfe, 0x4d, 0x2c, 0xd7, 0x03, 0x94, 0x73, 0x86, 0x35, 0x01, 0xca, 0x8f, 0x21, 0x98, \n  0x77, 0x49, 0x62, 0x5b, 0xdd, 0x67, 0xde, 0xa8, 0x19, 0xc7, 0xb1, 0x72, 0x6c, 0xed, 0x78, 0x97, \n  0xee, 0xc2, 0xed, 0x51, 0xf1, 0x57, 0x2d, 0xd3, 0x80, 0x59, 0xca, 0x02, 0x99, 0xda, 0xbd, 0x53, \n  0xab, 0xb7, 0x25, 0x22, 0xa1, 0x6a, 0x06, 0x63, 0xeb, 0xb3, 0x76, 0xe2, 0xcf, 0xaa, 0x04, 0x6e, \n  0xd5, 0x12, 0xce, 0x3c, 0x73, 0x61, 0xf8, 0x69, 0x53, 0x0f, 0x4a, 0xe1, 0xbe, 0x87, 0x8f, 0x9e, \n  0x1e, 0x1f, 0x13, 0xc1, 0x16, 0xbc, 0x97, 0x17, 0xc9, 0x2c, 0xc9, 0xc8, 0x51, 0x57, 0xe1, 0xba, \n  0xe5, 0xde, 0x98, 0x70, 0x5f, 0xb5, 0x50, 0x1d, 0xef, 0x1b, 0xa9, 0x24, 0x85, 0x9f, 0x88, 0x37, \n  0xec, 0x8d, 0x2b, 0xbd, 0xed, 0xd6, 0xd5, 0x7b, 0xe0, 0xcb, 0x28, 0xa1, 0xf4, 0xca, 0xdd, 0xea, \n  0x9a, 0x6f, 0x54, 0xec, 0x3b, 0x99, 0xf9, 0x82, 0x5d, 0x43, 0x64, 0x46, 0xb5, 0x02, 0xae, 0x43, \n  0x08, 0xb3, 0x50, 0x2a, 0x43, 0x78, 0x76, 0x61, 0x7e, 0x04, 0x1c, 0x38, 0xe4, 0x12, 0x87, 0x23, \n  0x73, 0xc0, 0xcf, 0x58, 0xdb, 0x89, 0xba, 0x90, 0xf0, 0xe4, 0xfe, 0xe6, 0x16, 0xc9, 0xff, 0xaa, \n  0x33, 0x30, 0x72, 0x05, 0x53, 0xac, 0xc2, 0x6c, 0xcb, 0x42, 0x04, 0xab, 0x66, 0xc7, 0xc5, 0x83, \n  0x2a, 0xbc, 0x4d, 0x0d, 0x3c, 0xaf, 0x81, 0x57, 0x86, 0x89, 0x8a, 0x81, 0x8d, 0xfe, 0xe8, 0x72, \n  0x6d, 0xa9, 0x60, 0x9a, 0x88, 0xbe, 0x6d, 0xb6, 0x0f, 0x45, 0xc5, 0x5a, 0x1b, 0x32, 0xd8, 0xb0, \n  0xed, 0xe5, 0x75, 0x45, 0x0a, 0xab, 0x7b, 0x00, 0xf3, 0xa6, 0x71, 0x93, 0xdd, 0xef, 0x20, 0xa0, \n  0xf8, 0x83, 0x4c, 0xce, 0xb7, 0x59, 0x7a, 0x0b, 0x45, 0xfb, 0xbc, 0x4b, 0xdf, 0x99, 0x97, 0x7c, \n  0x09, 0x85, 0xc0, 0x30, 0xef, 0x08, 0x19, 0x5a, 0x6f, 0x02, 0x9b, 0x41, 0x68, 0xf5, 0x84, 0xe4, \n  0xeb, 0x77, 0xef, 0xde, 0x3a, 0xea, 0x9d, 0x61, 0xe1, 0xe0, 0x97, 0x0b, 0x9c, 0xf5, 0x9c, 0x67, \n  0xce, 0x33, 0x38, 0xe1, 0x57, 0x02, 0xea, 0x7c, 0xf1, 0xf3, 0xbb, 0xd0, 0x8d, 0xa7, 0x28, 0x56, \n  0xaa, 0x80, 0x12, 0x5e, 0x7b, 0xa8, 0x4a, 0x88, 0x47, 0x3b, 0x64, 0xd2, 0xc2, 0xb4, 0x6c, 0x61, \n  0xba, 0xd1, 0x1e, 0xed, 0x38, 0xc4, 0x9a, 0xc8, 0xd4, 0xec, 0x59, 0xa8, 0xbd, 0xab, 0x19, 0x8c, \n  0xa6, 0x9f, 0xf5, 0x12, 0x42, 0x2d, 0x23, 0x91, 0xb4, 0x71, 0x01, 0x34, 0x36, 0x92, 0xc5, 0x0c, \n  0x0c, 0xd8, 0x9c, 0x78, 0x17, 0x4d, 0x01, 0x84, 0x5d, 0x95, 0xcd, 0x71, 0x94, 0xf9, 0xa2, 0x88, \n  0xd0, 0xf6, 0x10, 0x45, 0x14, 0x4a, 0xfc, 0x92, 0xdc, 0x76, 0xab, 0x01, 0x3c, 0x81, 0x96, 0x2f, \n  0xae, 0x67, 0x98, 0xb7, 0xa9, 0x97, 0xdc, 0x13, 0x99, 0x40, 0xe3, 0x46, 0x47, 0xee, 0xbf, 0x47, \n  0xfc, 0xd7, 0x94, 0x61, 0x3d, 0xac, 0xb5, 0xbc, 0x7b, 0xee, 0xbe, 0xff, 0xc3, 0xf8, 0xc3, 0x43, \n  0x6f, 0xec, 0x9d, 0xcc, 0x12, 0xd0, 0xf5, 0x23, 0x74, 0xf1, 0xad, 0x57, 0xac, 0x1a, 0x09, 0x84, \n  0x5a, 0xa6, 0xaa, 0xdb, 0xe5, 0xa5, 0x25, 0x41, 0xea, 0x74, 0xa5, 0x46, 0x92, 0x44, 0x46, 0xd7, \n  0x06, 0xa2, 0x25, 0x1d, 0x42, 0xd5, 0x81, 0x7e, 0x4f, 0x5e, 0xc8, 0xde, 0x9a, 0x78, 0x5e, 0xe9, \n  0xce, 0xcc, 0x75, 0xff, 0xe8, 0x42, 0x49, 0x55, 0x38, 0xf3, 0xbf, 0x67, 0x42, 0x70, 0xa9, 0xae, \n  0xf0, 0xf8, 0xd8, 0xdc, 0x41, 0xb5, 0x3c, 0x18, 0xf0, 0x2e, 0xf7, 0x23, 0xd1, 0x2a, 0xbb, 0x02, \n  0xb3, 0xf5, 0x3b, 0x78, 0xec, 0xcf, 0x99, 0x70, 0x39, 0x08, 0x1e, 0xd5, 0x04, 0xb9, 0xce, 0x2d, \n  0xbd, 0x00, 0xdf, 0x4d, 0x68, 0x48, 0x67, 0xb4, 0x72, 0xa0, 0x70, 0x60, 0x53, 0xf0, 0x34, 0x20, \n  0xa8, 0xb0, 0xc5, 0x9c, 0x73, 0x49, 0x28, 0x14, 0x5c, 0x42, 0x6d, 0x36, 0xd4, 0x60, 0x43, 0xc1, \n  0xd7, 0x0f, 0x9d, 0x9b, 0xfe, 0xd0, 0xd8, 0xf3, 0x87, 0xdd, 0x2d, 0xb1, 0xdc, 0xb8, 0xb5, 0xa5, \n  0x7a, 0xa7, 0x92, 0xd0, 0x8d, 0x28, 0x22, 0xb3, 0x85, 0xc1, 0x05, 0xd4, 0x0f, 0x9b, 0x78, 0x94, \n  0xed, 0x4d, 0x35, 0x17, 0xdc, 0xa5, 0x86, 0x49, 0x9e, 0xcb, 0x5e, 0x45, 0x12, 0x25, 0x54, 0x46, \n  0xf2, 0x5a, 0xbd, 0x27, 0x20, 0x4e, 0x3a, 0xa3, 0x6e, 0x8d, 0x14, 0x80, 0xaa, 0x04, 0x6d, 0x06, \n  0xde, 0xf6, 0x8e, 0xad, 0x57, 0x2f, 0x5a, 0xde, 0x70, 0x1d, 0x31, 0x82, 0x17, 0x45, 0xab, 0x56, \n  0x55, 0x21, 0x34, 0x52, 0xff, 0x84, 0xf2, 0x42, 0xd5, 0x54, 0x05, 0xf5, 0x18, 0x9d, 0xac, 0x45, \n  0x18, 0xe0, 0xa1, 0x52, 0x84, 0x18, 0xf9, 0x54, 0x4d, 0x95, 0x2e, 0xb5, 0xfb, 0x17, 0xcd, 0xfe, \n  0x1a, 0xac, 0xa8, 0x05, 0x56, 0x47, 0xa0, 0x6b, 0x74, 0x28, 0xf3, 0x6c, 0x92, 0xce, 0xf5, 0x8a, \n  0xa9, 0x46, 0x63, 0x33, 0xac, 0x75, 0xd1, 0xcc, 0xdf, 0x06, 0xcd, 0x64, 0x73, 0x3d, 0x3b, 0x6e, \n  0x07, 0x0d, 0xb4, 0x63, 0xaa, 0xdd, 0x54, 0xcb, 0x71, 0x55, 0x7e, 0xcd, 0x76, 0x7b, 0x64, 0xbf, \n  0xfa, 0x74, 0x54, 0x79, 0x3e, 0xe6, 0xd7, 0x1e, 0xc7, 0x47, 0xd6, 0x11, 0x08, 0xde, 0xf6, 0x2e, \n  0x32, 0xf5, 0x06, 0x12, 0xb8, 0x18, 0x99, 0xf2, 0xe1, 0x9d, 0xc9, 0xad, 0xe4, 0xc2, 0xbb, 0xaa, \n  0x04, 0x78, 0x66, 0x5e, 0x32, 0xda, 0xe7, 0xe0, 0x7c, 0xda, 0x7f, 0xdd, 0x17, 0x46, 0xad, 0x71, \n  0xb1, 0xe2, 0xcd, 0xa0, 0xa9, 0x7a, 0x1f, 0xf7, 0xa8, 0xd3, 0x01, 0x57, 0xb1, 0xb2, 0xdd, 0xe7, \n  0x61, 0xc3, 0x79, 0xaf, 0x5e, 0x57, 0xd8, 0xcd, 0x58, 0x16, 0x1c, 0x4e, 0x4a, 0x30, 0xe6, 0x64, \n  0xa5, 0x68, 0x7e, 0x87, 0xe9, 0xc9, 0xbd, 0xb3, 0x94, 0xf5, 0xed, 0xd6, 0x59, 0x00, 0xab, 0xae, \n  0x4a, 0xc7, 0x12, 0xf6, 0x27, 0x47, 0x7f, 0x8d, 0x76, 0xe0, 0x4e, 0xb7, 0xae, 0x1b, 0xfc, 0xd2, \n  0xf3, 0xe8, 0xfb, 0xf7, 0x64, 0xbe, 0x5a, 0x20, 0xc2, 0x70, 0x93, 0x3d, 0xa6, 0xb4, 0xe1, 0x39, \n  0xdb, 0x94, 0xfe, 0x40, 0xdf, 0x5b, 0x69, 0x1a, 0xfa, 0x5b, 0x68, 0xeb, 0xb0, 0x9c, 0x7e, 0xa4, \n  0xa3, 0x6b, 0x03, 0x35, 0x94, 0x5d, 0x03, 0x77, 0x14, 0x6c, 0x01, 0x5e, 0x09, 0xc7, 0x47, 0xc6, \n  0xba, 0xa5, 0x59, 0xd5, 0x36, 0x71, 0x21, 0xf5, 0xa4, 0x7a, 0x3f, 0xe0, 0x07, 0x68, 0x59, 0xb7, \n  0x4b, 0x39, 0xf6, 0x57, 0xef, 0xbd, 0xe2, 0x5e, 0xcf, 0x20, 0x92, 0x87, 0x60, 0x40, 0x8c, 0xb0, \n  0xd5, 0xa3, 0xa0, 0x68, 0x64, 0x31, 0x7f, 0xa5, 0xc6, 0x5a, 0x99, 0xcc, 0xbf, 0x43, 0x30, 0x1a, \n  0xa2, 0x87, 0x26, 0xbc, 0x7a, 0x58, 0x71, 0xb8, 0x92, 0x12, 0x05, 0xff, 0x50, 0x67, 0x39, 0x5d, \n  0xac, 0xad, 0xf2, 0xc2, 0xf1, 0xdc, 0xe5, 0x90, 0x1f, 0x4d, 0xa2, 0x8f, 0x84, 0x4a, 0xcf, 0xa3, \n  0x73, 0xe5, 0xf2, 0x43, 0xb5, 0x69, 0x75, 0x31, 0x92, 0xe3, 0x73, 0x94, 0x05, 0xd4, 0xdc, 0x47, \n  0xa4, 0x1e, 0x22, 0x5b, 0xd7, 0x43, 0x53, 0xf5, 0xd4, 0x62, 0xcc, 0xba, 0x2f, 0xe6, 0x1e, 0x5d, \n  0x71, 0x8b, 0x87, 0xa1, 0x51, 0xc7, 0x17, 0x3e, 0x87, 0x45, 0xac, 0xc4, 0x1a, 0xed, 0x30, 0xf0, \n  0x36, 0x44, 0x5c, 0xcf, 0x7a, 0x0b, 0x9e, 0xad, 0x48, 0x10, 0x51, 0x6c, 0xf0, 0x5b, 0x4e, 0x82, \n  0x44, 0xfd, 0xce, 0x72, 0xd5, 0x2c, 0x54, 0x13, 0xf3, 0x6f, 0x41, 0xa6, 0x1a, 0x26, 0xfe, 0x1f, \n  0x08, 0xdd, 0x09, 0x26, 0x33, 0x94, 0xc1, 0x41, 0xc3, 0x5c, 0x70, 0xc0, 0x54, 0x5b, 0xe7, 0x34, \n  0x83, 0x54, 0xcf, 0xc5, 0x7c, 0x61, 0x10, 0xab, 0x96, 0xa6, 0x8d, 0x60, 0x55, 0x5a, 0x15, 0x6d, \n  0x92, 0x66, 0x1d, 0xaf, 0xaa, 0xf3, 0x66, 0x79, 0x97, 0x61, 0x95, 0x46, 0x2a, 0xc9, 0x2b, 0xc1, \n  0xd6, 0xeb, 0x38, 0x6c, 0x83, 0xdb, 0xba, 0xdd, 0x39, 0x97, 0xa8, 0xaf, 0x1f, 0xc1, 0xa7, 0x0b, \n  0x50, 0x76, 0xf2, 0x1d, 0x73, 0xcb, 0x37, 0xf1, 0xb7, 0x2c, 0x94, 0xbb, 0x9d, 0xf3, 0xd8, 0xc4, \n  0x83, 0x2a, 0x7e, 0x03, 0x18, 0xb3, 0xe3, 0x63, 0x08, 0x02, 0xec, 0x14, 0x87, 0x9c, 0x87, 0x8f, \n  0xfb, 0x4f, 0x2e, 0x5c, 0x01, 0x6e, 0x15, 0xcf, 0xe4, 0x9b, 0x3c, 0x86, 0xb7, 0xd9, 0xf0, 0xd5, \n  0xd3, 0x46, 0x75, 0xa2, 0xa0, 0xd2, 0xcf, 0xf8, 0x8d, 0xbc, 0x4c, 0x26, 0x69, 0x92, 0xcd, 0x20, \n  0x42, 0xb4, 0x2b, 0x06, 0x1b, 0xdf, 0x6f, 0x42, 0x65, 0xd6, 0x5a, 0xb7, 0x23, 0xbc, 0x42, 0x77, \n  0x4a, 0x19, 0xda, 0xab, 0x78, 0xe5, 0x08, 0x5e, 0x3d, 0xfa, 0xa4, 0xfc, 0xe3, 0xca, 0xc4, 0xe6, \n  0xd5, 0x8b, 0x8f, 0xe6, 0x9b, 0x08, 0xb6, 0x2b, 0x42, 0x7f, 0xba, 0xd5, 0x81, 0x6e, 0x19, 0xbc, \n  0xc5, 0x4c, 0x28, 0xf9, 0x4d, 0x06, 0xe1, 0x0c, 0xf0, 0xc6, 0xf4, 0xdf, 0xc9, 0x48, 0x7e, 0xe4, \n  0x0e, 0xbe, 0xe2, 0xec, 0xac, 0xf9, 0x44, 0xa8, 0x37, 0x01, 0x09, 0x14, 0x1c, 0x78, 0xa3, 0xf3, \n  0x13, 0xfd, 0x95, 0x89, 0x9f, 0xff, 0xec, 0xfc, 0x44, 0xff, 0x41, 0x8f, 0x73, 0xd8, 0xd1, 0x64, \n  0x3e, 0x1b, 0xdb, 0xe8, 0xbf, 0xd7, 0xc1, 0x92, 0x0c, 0x33, 0xa9, 0x28, 0x90, 0xd5, 0x33, 0x85, \n  0x10, 0x33, 0x09, 0x2a, 0x53, 0xad, 0xbf, 0x32, 0xa0, 0xc6, 0x38, 0xce, 0x79, 0x9c, 0x5c, 0x9b, \n  0x21, 0x8a, 0x4e, 0x74, 0x87, 0xee, 0x82, 0x35, 0x2b, 0xbb, 0xdb, 0x0c, 0x4c, 0xe3, 0x19, 0x19, \n  0x9f, 0x27, 0x8b, 0x59, 0xf5, 0x00, 0x7b, 0x59, 0x2a, 0x43, 0xf2, 0x1a, 0x7e, 0x8e, 0xcf, 0x4f, \n  0xe2, 0xe4, 0xba, 0xb9, 0x92, 0x49, 0xdb, 0xc6, 0xf5, 0x0e, 0x00, 0xe4, 0x00, 0xb7, 0xb0, 0x4c, \n  0xee, 0xae, 0xbf, 0x4c, 0x32, 0x1f, 0xd8, 0x73, 0x0c, 0x5c, 0xfa, 0x6d, 0xa2, 0xd6, 0x66, 0x6a, \n  0xc0, 0xf8, 0x1c, 0x6a, 0xad, 0xab, 0x61, 0xe8, 0xed, 0x8d, 0xcf, 0x4f, 0xe0, 0xe1, 0xd8, 0xe9, \n  0x39, 0xd7, 0x75, 0x77, 0xf5, 0x6a, 0x95, 0xe9, 0x6e, 0xc1, 0x6e, 0xb5, 0x1a, 0xbf, 0xad, 0x33, \n  0x01, 0xd7, 0xe0, 0x5a, 0x86, 0x85, 0xea, 0xd9, 0xcc, 0x8c, 0x51, 0x35, 0xb9, 0x0e, 0x7c, 0x7c, \n  0x45, 0x8d, 0x35, 0xea, 0x6e, 0x6c, 0xaf, 0x94, 0x44, 0x4e, 0xb2, 0x20, 0x16, 0xf0, 0x95, 0xb4, \n  0x6b, 0x42, 0x07, 0x6f, 0x39, 0xed, 0xdc, 0x53, 0x55, 0x82, 0x0b, 0x6f, 0x48, 0xa9, 0xb4, 0xba, \n  0xd6, 0x6b, 0x2d, 0x20, 0x74, 0x88, 0xc8, 0x31, 0xd1, 0xab, 0xb0, 0xae, 0x12, 0x1b, 0x63, 0x66, \n  0x0c, 0xc9, 0x16, 0x5f, 0xb5, 0xda, 0xbd, 0x47, 0x58, 0x78, 0xb5, 0x8c, 0x77, 0x77, 0xd2, 0x52, \n  0xb1, 0xb9, 0x97, 0xbd, 0x89, 0x91, 0xf8, 0xb0, 0x0d, 0xa6, 0x68, 0xb8, 0x73, 0xec, 0x7c, 0x75, \n  0xd9, 0xde, 0xa7, 0xfe, 0xad, 0xc8, 0x9f, 0x17, 0xe3, 0x9f, 0xff, 0x0c, 0x9b, 0x66, 0xfb, 0x0a, \n  0x5a, 0x9b, 0xb8, 0x71, 0x59, 0x43, 0x02, 0xce, 0xee, 0x25, 0xcd, 0xc5, 0x9a, 0xb4, 0xa9, 0xc8, \n  0x74, 0xe5, 0x42, 0xda, 0x7d, 0xd8, 0x8f, 0x2f, 0xc5, 0x40, 0x82, 0x2d, 0xac, 0xd3, 0xb0, 0x15, \n  0xbd, 0x4f, 0x52, 0x32, 0xfe, 0x5a, 0x3f, 0x3d, 0x3f, 0xc1, 0xa1, 0xed, 0xf9, 0x76, 0xe1, 0x03, \n  0xda, 0xf5, 0x8e, 0xf5, 0x97, 0x0b, 0x42, 0x62, 0x66, 0x63, 0x0a, 0x4f, 0xbf, 0xe6, 0x66, 0xf6, \n  0x51, 0xaf, 0xba, 0x69, 0x32, 0x69, 0xef, 0x5d, 0xf3, 0x6e, 0x9b, 0x24, 0xdb, 0xc7, 0x52, 0x7f, \n  0xb5, 0x07, 0xd9, 0x4b, 0xb9, 0x99, 0xdd, 0x47, 0xb4, 0xce, 0xa4, 0x8e, 0x8b, 0xf3, 0xc6, 0x6f, \n  0xf3, 0x42, 0x1e, 0x3c, 0x5a, 0x0d, 0x8f, 0x3e, 0xa5, 0xf6, 0x7f, 0xab, 0x1d, 0xad, 0xeb, 0x70, \n  0xda, 0x10, 0x5b, 0x77, 0xd4, 0x6e, 0xcf, 0x87, 0xd5, 0xbd, 0x0c, 0x89, 0x83, 0x66, 0x77, 0x48, \n  0xaa, 0xcf, 0x99, 0xf7, 0x47, 0x9a, 0x46, 0x9f, 0xd7, 0x65, 0x3f, 0xe7, 0x27, 0xf3, 0xe1, 0xb8, \n  0x75, 0xeb, 0x15, 0x9d, 0xd4, 0x35, 0x93, 0x95, 0x9c, 0x83, 0xc6, 0x3e, 0xd2, 0x56, 0x3e, 0xa1, \n  0x5d, 0x64, 0xd2, 0x40, 0x37, 0xa6, 0x80, 0xac, 0x85, 0x52, 0xde, 0xc4, 0xea, 0xb9, 0x44, 0x49, \n  0xad, 0x56, 0x82, 0x22, 0x1d, 0xa8, 0x64, 0x81, 0x47, 0x8d, 0x3b, 0xc3, 0x89, 0xf6, 0x93, 0x49, \n  0xd1, 0x29, 0x70, 0x5a, 0x67, 0x21, 0x9a, 0x11, 0x76, 0xaf, 0x5a, 0xb6, 0x2e, 0xd7, 0xa6, 0x5e, \n  0x34, 0xee, 0x1a, 0x94, 0x0b, 0x7e, 0x5c, 0xd7, 0xd5, 0x36, 0x88, 0x47, 0xac, 0xe3, 0x36, 0xc1, \n  0x7c, 0x9a, 0xaa, 0x5f, 0xc2, 0x27, 0xcc, 0x54, 0xdc, 0xff, 0xbb, 0xe4, 0xab, 0x57, 0x2a, 0x69, \n  0xac, 0xc8, 0x17, 0xc5, 0x84, 0x0d, 0x4a, 0x93, 0x90, 0xab, 0x1d, 0x50, 0xf4, 0xe9, 0x21, 0x62, \n  0xbe, 0xd6, 0x13, 0x2b, 0xab, 0xb7, 0xb2, 0x87, 0x3b, 0xe6, 0xda, 0xf2, 0x56, 0xcc, 0xf1, 0x05, \n  0x0c, 0x27, 0x9a, 0xf3, 0xeb, 0x22, 0xcf, 0xea, 0x79, 0xf5, 0x7a, 0xf5, 0x52, 0x7f, 0xfa, 0xe7, \n  0xff, 0x65, 0x4b, 0x54, 0x6b, 0x3d, 0x25, 0x74, 0x1b, 0x48, 0x6a, 0x73, 0xdc, 0x01, 0x0e, 0x3c, \n  0x74, 0x2d, 0x95, 0xf9, 0xde, 0xb8, 0x1a, 0x93, 0x5e, 0xff, 0x29, 0xd7, 0xd3, 0xb8, 0x9c, 0x7a, \n  0xf9, 0x03, 0x17, 0x94, 0x38, 0xf5, 0x30, 0xa5, 0x86, 0x4d, 0x0b, 0xbe, 0xf2, 0x05, 0x5f, 0x73, \n  0x04, 0x2b, 0x38, 0x24, 0xd1, 0xaa, 0x00, 0xab, 0xac, 0xb7, 0x03, 0xf3, 0xee, 0x1d, 0x7e, 0xea, \n  0xfe, 0xaa, 0x25, 0x76, 0x69, 0xab, 0x7d, 0x77, 0xd6, 0x54, 0x8c, 0x66, 0xb4, 0x34, 0xa5, 0x51, \n  0x93, 0x66, 0x12, 0xb8, 0x02, 0xa4, 0x52, 0x9a, 0xe0, 0x06, 0xec, 0xd7, 0x99, 0x5d, 0x7b, 0x6a, \n  0x79, 0x5b, 0x05, 0x4f, 0x3e, 0x6b, 0x3b, 0xed, 0x78, 0x7c, 0x6a, 0x47, 0x63, 0x81, 0x38, 0x9f, \n  0x43, 0x51, 0x7b, 0x68, 0x6b, 0xd2, 0x4d, 0x5c, 0x35, 0x35, 0xe1, 0xeb, 0xb0, 0xdd, 0x34, 0x02, \n  0x60, 0xa3, 0x7f, 0xa7, 0xa9, 0xa5, 0x7a, 0x27, 0xb6, 0x5a, 0x1d, 0xdf, 0x89, 0x75, 0x74, 0x4c, \n  0xa7, 0xbd, 0x88, 0x0d, 0x06, 0xa8, 0xd5, 0x36, 0xc4, 0x3b, 0x84, 0x80, 0x2f, 0xc1, 0x8e, 0x5f, \n  0x7c, 0xfd, 0xfc, 0xad, 0xc3, 0xd1, 0x14, 0x8e, 0x3b, 0x39, 0xab, 0x4d, 0xf3, 0x77, 0xc3, 0x44, \n  0x4d, 0x2b, 0x75, 0xa0, 0x69, 0xec, 0xec, 0xe3, 0x1c, 0xb9, 0xee, 0x20, 0x3d, 0x8b, 0x2f, 0x93, \n  0x65, 0x93, 0x23, 0x5f, 0xbd, 0x75, 0x9e, 0xa9, 0x42, 0xaa, 0x6e, 0x6d, 0xf8, 0x69, 0xa1, 0x58, \n  0xaf, 0xa0, 0xa0, 0x84, 0x1d, 0x74, 0xae, 0x75, 0xf0, 0x74, 0xe8, 0x0f, 0x1e, 0x3f, 0xf1, 0xfb, \n  0xfe, 0xa0, 0xbf, 0x97, 0xa9, 0x76, 0xb1, 0xfb, 0x39, 0xe7, 0x31, 0xb5, 0x5d, 0x8d, 0x43, 0xfd, \n  0x42, 0x3d, 0xfc, 0xa9, 0x27, 0xd2, 0xd3, 0x1d, 0x66, 0x1f, 0xab, 0xda, 0x68, 0xf7, 0x6c, 0xff, \n  0x9f, 0x8e, 0xa6, 0x6b, 0xd2, 0x9a, 0xba, 0x0d, 0x9f, 0xfd, 0xd4, 0x83, 0xa9, 0xd9, 0xce, 0x82, \n  0x89, 0x8f, 0x9a, 0xb7, 0xf5, 0x16, 0xfa, 0x4c, 0xc3, 0xb3, 0x33, 0xdf, 0xfc, 0xef, 0x2f, 0x77, \n  0x61, 0xf6, 0x99, 0xb0, 0x8c, 0xae, 0x71, 0xa2, 0x17, 0x6f, 0x2e, 0x9d, 0x81, 0xe3, 0xaa, 0xd7, \n  0x1b, 0x58, 0xea, 0xfd, 0x34, 0x7b, 0xf3, 0x6d, 0x91, 0x2c, 0xa0, 0x4a, 0x07, 0x56, 0x13, 0xe8, \n  0x56, 0xa9, 0x03, 0x36, 0xf6, 0xfb, 0x0b, 0x1f, 0x64, 0xb8, 0x7b, 0x90, 0xe1, 0x9f, 0x7d, 0x90, \n  0x4b, 0xa8, 0xaf, 0x8b, 0xf7, 0x1c, 0x65, 0x78, 0xe7, 0xa3, 0x74, 0x89, 0xac, 0x7d, 0x4a, 0xdc, \n  0xb1, 0x3f, 0x05, 0x4b, 0x76, 0xf1, 0x81, 0xa2, 0xa7, 0xe3, 0xd5, 0x83, 0xca, 0x61, 0x67, 0xf8, \n  0x51, 0x1c, 0xf8, 0x34, 0xb2, 0xd3, 0xb6, 0x28, 0xb5, 0x16, 0x42, 0xf8, 0xad, 0x98, 0x60, 0x35, \n  0x55, 0x66, 0x0e, 0xfe, 0xb5, 0xa7, 0xce, 0x89, 0x4d, 0x40, 0x6b, 0x1d, 0x65, 0xb4, 0x92, 0x8e, \n  0x62, 0x75, 0x7b, 0xb6, 0x0d, 0x41, 0x3d, 0xd6, 0x25, 0xf9, 0xca, 0x34, 0xdc, 0x11, 0xcd, 0x80, \n  0x2f, 0xd6, 0x0d, 0xb5, 0x1d, 0xb7, 0xfc, 0xcb, 0x40, 0xad, 0x82, 0x6d, 0x9f, 0x01, 0xf4, 0xb3, \n  0xd7, 0xaf, 0xef, 0x0a, 0x73, 0x5b, 0x47, 0xa2, 0xb6, 0xe8, 0x52, 0x94, 0x0d, 0x55, 0x69, 0x55, \n  0xf5, 0xfe, 0x24, 0x85, 0x79, 0x47, 0x95, 0xd9, 0xa9, 0x34, 0x31, 0x37, 0x0f, 0xb6, 0xda, 0x9e, \n  0x23, 0x76, 0xd8, 0x89, 0x07, 0x49, 0xbd, 0xd5, 0x9a, 0x17, 0x9d, 0x61, 0x9a, 0x09, 0xdc, 0x40, \n  0x63, 0x51, 0xed, 0xe8, 0x57, 0x81, 0x76, 0xeb, 0xae, 0xc9, 0x01, 0xfb, 0x60, 0xf7, 0x7a, 0x31, \n  0x28, 0x7b, 0x97, 0xdb, 0xbd, 0x8c, 0x58, 0xa6, 0xac, 0x54, 0x63, 0xa9, 0x77, 0x1a, 0x0d, 0xac, \n  0x0b, 0xce, 0x46, 0x02, 0xe0, 0x27, 0x83, 0x6a, 0x42, 0xc6, 0x77, 0x81, 0x56, 0x97, 0xd2, 0x62, \n  0xc5, 0x45, 0xbd, 0x48, 0x95, 0x4d, 0x1b, 0x5b, 0x9c, 0xf5, 0x89, 0x63, 0xd8, 0x01, 0x10, 0xf5, \n  0xab, 0x11, 0xfd, 0xb0, 0xc2, 0x28, 0xad, 0xf8, 0x47, 0x83, 0xdb, 0x9a, 0x7e, 0xb4, 0x09, 0xb8, \n  0x60, 0x70, 0x4d, 0x47, 0xbc, 0xd0, 0x6d, 0xee, 0x08, 0x03, 0x5a, 0x62, 0xfd, 0x1c, 0xfe, 0x6c, \n  0x89, 0xbd, 0x2b, 0xb4, 0xab, 0x6d, 0x55, 0x43, 0x09, 0xbd, 0x90, 0x9c, 0x98, 0x00, 0x90, 0x2a, \n  0x5b, 0x83, 0x18, 0x80, 0x90, 0xc4, 0xe1, 0x59, 0xa4, 0x58, 0x66, 0xb1, 0x4a, 0x65, 0xb2, 0x64, \n  0x85, 0x3c, 0x81, 0x79, 0x3d, 0xac, 0x83, 0xdb, 0xe3, 0x37, 0x4d, 0x0f, 0xf8, 0x4d, 0x56, 0x02, \n  0x42, 0xc5, 0xef, 0x54, 0xce, 0xa1, 0x9e, 0xba, 0x22, 0xe3, 0x03, 0x15, 0xaa, 0x9d, 0x0c, 0x53, \n  0x73, 0xba, 0xbd, 0xba, 0x02, 0x1b, 0xcb, 0x5a, 0xbb, 0x48, 0xcc, 0x4a, 0x60, 0xd8, 0x72, 0x0f, \n  0x02, 0xb3, 0x8e, 0x58, 0x4d, 0x16, 0x89, 0xc4, 0x68, 0x9f, 0x1a, 0xa0, 0xab, 0x41, 0x54, 0xe1, \n  0x9a, 0x53, 0xc7, 0x1c, 0xbb, 0x6c, 0x66, 0x43, 0x7d, 0x1a, 0x9f, 0xe3, 0x4f, 0x52, 0x7d, 0x93, \n  0x2c, 0xff, 0xaa, 0xe1, 0x27, 0xce, 0xd6, 0x96, 0x2f, 0x55, 0x55, 0xbc, 0xaa, 0xa8, 0x1c, 0x34, \n  0x75, 0x7c, 0x65, 0x9a, 0xa4, 0x69, 0x0f, 0x92, 0x9b, 0x35, 0x48, 0x7f, 0xb5, 0x4b, 0x73, 0x50, \n  0x91, 0xdc, 0x62, 0x82, 0xbf, 0x6a, 0xf9, 0xa5, 0xb3, 0x7a, 0x27, 0x2c, 0x88, 0x6d, 0x0f, 0x6f, \n  0x0b, 0x23, 0xa0, 0x85, 0x8e, 0xf0, 0x6a, 0x27, 0x0d, 0xbf, 0x56, 0xb8, 0x53, 0x81, 0x74, 0x45, \n  0xc9, 0x29, 0xbf, 0xbc, 0x15, 0x92, 0x2f, 0xee, 0x4c, 0xcb, 0xdd, 0xa4, 0x8b, 0x05, 0x97, 0x3f, \n  0x95, 0x70, 0x57, 0x29, 0x1e, 0xd9, 0x94, 0xd7, 0x8e, 0xcf, 0x4f, 0x56, 0x0d, 0x27, 0xc6, 0x76, \n  0xfa, 0x55, 0x02, 0x4e, 0xa1, 0xc8, 0xe4, 0xc7, 0xba, 0x48, 0xf7, 0x53, 0x05, 0x95, 0x1d, 0xbe, \n  0x92, 0x6d, 0x37, 0x21, 0xd1, 0x56, 0xbb, 0xc0, 0x8e, 0xea, 0xa5, 0x2b, 0x78, 0x0e, 0x29, 0x0b, \n  0xe2, 0xa8, 0xe2, 0x57, 0xf5, 0x8d, 0xef, 0xbc, 0xb8, 0x75, 0xd4, 0x21, 0x9b, 0x51, 0xa9, 0xa5, \n  0x81, 0x4c, 0xd5, 0x46, 0xe3, 0xd7, 0xd3, 0xc9, 0xf8, 0x8d, 0xff, 0xa5, 0x1f, 0x38, 0xc9, 0xd4, \n  0xc0, 0x87, 0x51, 0xcc, 0x44, 0x40, 0x9d, 0x19, 0x39, 0x41, 0xe4, 0x40, 0xa5, 0x19, 0x55, 0x47, \n  0x00, 0x58, 0x9d, 0x75, 0x92, 0xa6, 0xce, 0x84, 0x3b, 0x2b, 0x53, 0xb6, 0xc9, 0xa4, 0x03, 0x7f, \n  0x12, 0xdc, 0x21, 0x27, 0xc4, 0x49, 0xf9, 0x35, 0x4f, 0xcf, 0x4f, 0x96, 0x9f, 0xa2, 0x89, 0xfa, \n  0x97, 0xca, 0x6e, 0x14, 0x78, 0x3c, 0x5d, 0x14, 0xb8, 0x1b, 0xaf, 0xab, 0x48, 0xd7, 0xd4, 0x10, \n  0xda, 0xc6, 0x58, 0x43, 0x4e, 0x2a, 0x6e, 0x36, 0x09, 0xc0, 0x4e, 0x5d, 0x71, 0x58, 0x4b, 0x98, \n  0xa9, 0xfb, 0xb4, 0x84, 0xd2, 0x0f, 0xe6, 0xd3, 0x74, 0x2f, 0x2f, 0xdf, 0x36, 0x18, 0xba, 0x66, \n  0x65, 0xa3, 0x62, 0xad, 0x04, 0xf5, 0x4f, 0x80, 0x06, 0xd3, 0x9a, 0x87, 0x41, 0x41, 0x93, 0xa2, \n  0xf1, 0x22, 0x49, 0x27, 0x44, 0x36, 0xee, 0x21, 0x67, 0x54, 0x23, 0xdf, 0x02, 0x20, 0x8d, 0x35, \n  0x39, 0xeb, 0x2c, 0xd9, 0x78, 0x77, 0xc0, 0x4c, 0x71, 0xae, 0x7a, 0x21, 0xc3, 0x5a, 0xd4, 0x1a, \n  0x24, 0xbe, 0xb6, 0x05, 0x45, 0xb3, 0xc7, 0x11, 0xc3, 0x83, 0xbd, 0x8f, 0x0e, 0xf6, 0x9e, 0x1e, \n  0xec, 0x3d, 0x6b, 0xf6, 0x36, 0x49, 0x2c, 0xe6, 0x92, 0x25, 0xa9, 0x50, 0x79, 0x35, 0xcc, 0xe1, \n  0x1b, 0x04, 0x89, 0xd5, 0x02, 0x9c, 0x29, 0xc0, 0xb2, 0xfe, 0xb5, 0x2b, 0x7a, 0x16, 0x79, 0xbc, \n  0x4f, 0xbd, 0x2d, 0x80, 0x56, 0xe6, 0x43, 0xb5, 0x70, 0xe3, 0x93, 0xde, 0x63, 0x89, 0x82, 0xec, \n  0x50, 0x4c, 0x71, 0x01, 0x37, 0xbe, 0x6c, 0xcc, 0xc5, 0xef, 0x7d, 0x8f, 0x81, 0x87, 0x0e, 0x4d, \n  0xdc, 0x31, 0xe9, 0x2a, 0x8a, 0xab, 0xaa, 0x17, 0x6c, 0x3d, 0xa6, 0x78, 0x44, 0x11, 0xcc, 0xb7, \n  0xbf, 0xaa, 0xe8, 0x69, 0xaf, 0x17, 0x50, 0x57, 0x3a, 0x34, 0x88, 0x56, 0x1b, 0x47, 0xd0, 0xdd, \n  0xb9, 0xc6, 0xbe, 0xcc, 0x8f, 0x46, 0xbe, 0x6a, 0xc1, 0x1f, 0x93, 0xaf, 0x53, 0x99, 0xd0, 0xaa, \n  0xad, 0x9c, 0xa2, 0xc9, 0x3d, 0xea, 0xa3, 0x86, 0x8e, 0xaa, 0xd8, 0x0e, 0xc9, 0xf7, 0x93, 0x94, \n  0x41, 0x16, 0xaa, 0xe0, 0x69, 0x48, 0xb2, 0x1c, 0xde, 0xe2, 0xe1, 0xe8, 0x5b, 0x18, 0x12, 0x57, \n  0x4b, 0x63, 0xae, 0x55, 0x87, 0xe9, 0xcf, 0x4f, 0xe0, 0xdb, 0x78, 0xe3, 0x9f, 0xff, 0xec, 0xff, \n  0x01, 0x0e, 0x00, 0x1a, 0xba, 0x71, 0x81, 0x00, 0x00\n};\n"
  },
  {
    "path": "src/compat/mbedtls_aes.h",
    "content": "#pragma once\n\n#if defined(ESP8266)\n#include <bearssl/bearssl.h>\n#include <stdint.h>\n#include <stddef.h>\n#include <string.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#define MBEDTLS_AES_ENCRYPT 1\n#define MBEDTLS_AES_DECRYPT 0\n\ntypedef struct {\n    br_aes_ct_cbcenc_keys enc;\n    br_aes_ct_cbcdec_keys dec;\n    uint8_t has_enc;\n    uint8_t has_dec;\n} mbedtls_aes_context;\n\nstatic inline void mbedtls_aes_init(mbedtls_aes_context *ctx) {\n    if (ctx) {\n        memset(ctx, 0, sizeof(*ctx));\n    }\n}\n\nstatic inline void mbedtls_aes_free(mbedtls_aes_context *ctx) {\n    (void)ctx;\n}\n\nstatic inline int mbedtls_aes_setkey_enc(mbedtls_aes_context *ctx, const unsigned char *key, unsigned int keybits) {\n    if (!ctx || !key || (keybits % 8) != 0) {\n        return -1;\n    }\n    br_aes_ct_cbcenc_init(&ctx->enc, key, keybits / 8);\n    ctx->has_enc = 1;\n    return 0;\n}\n\nstatic inline int mbedtls_aes_setkey_dec(mbedtls_aes_context *ctx, const unsigned char *key, unsigned int keybits) {\n    if (!ctx || !key || (keybits % 8) != 0) {\n        return -1;\n    }\n    br_aes_ct_cbcdec_init(&ctx->dec, key, keybits / 8);\n    ctx->has_dec = 1;\n    return 0;\n}\n\nstatic inline int mbedtls_aes_crypt_cbc(mbedtls_aes_context *ctx, int mode, size_t length,\n                                       unsigned char iv[16], const unsigned char *input, unsigned char *output) {\n    if (!ctx || !iv || !input || !output) {\n        return -1;\n    }\n    if (length % 16 != 0) {\n        return -1;\n    }\n\n    if (input != output) {\n        memcpy(output, input, length);\n    }\n\n    if (mode == MBEDTLS_AES_ENCRYPT) {\n        if (!ctx->has_enc) {\n            return -1;\n        }\n        br_aes_ct_cbcenc_run(&ctx->enc, iv, output, length);\n        return 0;\n    }\n\n    if (mode == MBEDTLS_AES_DECRYPT) {\n        if (!ctx->has_dec) {\n            return -1;\n        }\n        br_aes_ct_cbcdec_run(&ctx->dec, iv, output, length);\n        return 0;\n    }\n\n    return -1;\n}\n\n#ifdef __cplusplus\n}\n#endif\n\n#else\n#include_next <mbedtls/aes.h>\n#endif\n"
  },
  {
    "path": "src/esp-fs-webserver.h",
    "content": "\n#ifndef ESP_FS_WEBSERVER_H\n#define ESP_FS_WEBSERVER_H\n\n#include \"FSWebServer.h\"\n#endif"
  },
  {
    "path": "src/json/cJSON.c",
    "content": "/*\n  Copyright (c) 2009-2017 Dave Gamble and cJSON contributors\n\n  Permission is hereby granted, free of charge, to any person obtaining a copy\n  of this software and associated documentation files (the \"Software\"), to deal\n  in the Software without restriction, including without limitation the rights\n  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n  copies of the Software, and to permit persons to whom the Software is\n  furnished to do so, subject to the following conditions:\n\n  The above copyright notice and this permission notice shall be included in\n  all copies or substantial portions of the Software.\n\n  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n  THE SOFTWARE.\n*/\n\n/* cJSON */\n/* JSON parser in C. */\n\n/* disable warnings about old C89 functions in MSVC */\n#if !defined(_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER)\n#define _CRT_SECURE_NO_DEPRECATE\n#endif\n\n#ifdef __GNUC__\n#pragma GCC visibility push(default)\n#endif\n#if defined(_MSC_VER)\n#pragma warning (push)\n/* disable warning about single line comments in system headers */\n#pragma warning (disable : 4001)\n#endif\n\n#include <string.h>\n#include <stdio.h>\n#include <math.h>\n#include <stdlib.h>\n#include <limits.h>\n#include <ctype.h>\n#include <float.h>\n\n#ifdef ENABLE_LOCALES\n#include <locale.h>\n#endif\n\n#if defined(_MSC_VER)\n#pragma warning (pop)\n#endif\n#ifdef __GNUC__\n#pragma GCC visibility pop\n#endif\n\n#include \"cJSON.h\"\n\n/* define our own boolean type */\n#ifdef true\n#undef true\n#endif\n#define true ((cJSON_bool)1)\n\n#ifdef false\n#undef false\n#endif\n#define false ((cJSON_bool)0)\n\n/* define isnan and isinf for ANSI C, if in C99 or above, isnan and isinf has been defined in math.h */\n#ifndef isinf\n#define isinf(d) (isnan((d - d)) && !isnan(d))\n#endif\n#ifndef isnan\n#define isnan(d) (d != d)\n#endif\n\n#ifndef NAN\n#ifdef _WIN32\n#define NAN sqrt(-1.0)\n#else\n#define NAN 0.0/0.0\n#endif\n#endif\n\ntypedef struct {\n    const unsigned char *json;\n    size_t position;\n} error;\nstatic error global_error = { NULL, 0 };\n\nCJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void)\n{\n    return (const char*) (global_error.json + global_error.position);\n}\n\nCJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item)\n{\n    if (!cJSON_IsString(item))\n    {\n        return NULL;\n    }\n\n    return item->valuestring;\n}\n\nCJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item)\n{\n    if (!cJSON_IsNumber(item))\n    {\n        return (double) NAN;\n    }\n\n    return item->valuedouble;\n}\n\n/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */\n#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 19)\n    #error cJSON.h and cJSON.c have different versions. Make sure that both have the same.\n#endif\n\nCJSON_PUBLIC(const char*) cJSON_Version(void)\n{\n    static char version[15];\n    sprintf(version, \"%i.%i.%i\", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH);\n\n    return version;\n}\n\n/* Case insensitive string comparison, doesn't consider two NULL pointers equal though */\nstatic int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2)\n{\n    if ((string1 == NULL) || (string2 == NULL))\n    {\n        return 1;\n    }\n\n    if (string1 == string2)\n    {\n        return 0;\n    }\n\n    for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++)\n    {\n        if (*string1 == '\\0')\n        {\n            return 0;\n        }\n    }\n\n    return tolower(*string1) - tolower(*string2);\n}\n\ntypedef struct internal_hooks\n{\n    void *(CJSON_CDECL *allocate)(size_t size);\n    void (CJSON_CDECL *deallocate)(void *pointer);\n    void *(CJSON_CDECL *reallocate)(void *pointer, size_t size);\n} internal_hooks;\n\n#if defined(_MSC_VER)\n/* work around MSVC error C2322: '...' address of dllimport '...' is not static */\nstatic void * CJSON_CDECL internal_malloc(size_t size)\n{\n    return malloc(size);\n}\nstatic void CJSON_CDECL internal_free(void *pointer)\n{\n    free(pointer);\n}\nstatic void * CJSON_CDECL internal_realloc(void *pointer, size_t size)\n{\n    return realloc(pointer, size);\n}\n#else\n#define internal_malloc malloc\n#define internal_free free\n#define internal_realloc realloc\n#endif\n\n/* strlen of character literals resolved at compile time */\n#define static_strlen(string_literal) (sizeof(string_literal) - sizeof(\"\"))\n\nstatic internal_hooks global_hooks = { internal_malloc, internal_free, internal_realloc };\n\nstatic unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks)\n{\n    size_t length = 0;\n    unsigned char *copy = NULL;\n\n    if (string == NULL)\n    {\n        return NULL;\n    }\n\n    length = strlen((const char*)string) + sizeof(\"\");\n    copy = (unsigned char*)hooks->allocate(length);\n    if (copy == NULL)\n    {\n        return NULL;\n    }\n    memcpy(copy, string, length);\n\n    return copy;\n}\n\nCJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks)\n{\n    if (hooks == NULL)\n    {\n        /* Reset hooks */\n        global_hooks.allocate = malloc;\n        global_hooks.deallocate = free;\n        global_hooks.reallocate = realloc;\n        return;\n    }\n\n    global_hooks.allocate = malloc;\n    if (hooks->malloc_fn != NULL)\n    {\n        global_hooks.allocate = hooks->malloc_fn;\n    }\n\n    global_hooks.deallocate = free;\n    if (hooks->free_fn != NULL)\n    {\n        global_hooks.deallocate = hooks->free_fn;\n    }\n\n    /* use realloc only if both free and malloc are used */\n    global_hooks.reallocate = NULL;\n    if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free))\n    {\n        global_hooks.reallocate = realloc;\n    }\n}\n\n/* Internal constructor. */\nstatic cJSON *cJSON_New_Item(const internal_hooks * const hooks)\n{\n    cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON));\n    if (node)\n    {\n        memset(node, '\\0', sizeof(cJSON));\n    }\n\n    return node;\n}\n\n/* Delete a cJSON structure. */\nCJSON_PUBLIC(void) cJSON_Delete(cJSON *item)\n{\n    cJSON *next = NULL;\n    while (item != NULL)\n    {\n        next = item->next;\n        if (!(item->type & cJSON_IsReference) && (item->child != NULL))\n        {\n            cJSON_Delete(item->child);\n        }\n        if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL))\n        {\n            global_hooks.deallocate(item->valuestring);\n            item->valuestring = NULL;\n        }\n        if (!(item->type & cJSON_StringIsConst) && (item->string != NULL))\n        {\n            global_hooks.deallocate(item->string);\n            item->string = NULL;\n        }\n        global_hooks.deallocate(item);\n        item = next;\n    }\n}\n\n/* get the decimal point character of the current locale */\nstatic unsigned char get_decimal_point(void)\n{\n#ifdef ENABLE_LOCALES\n    struct lconv *lconv = localeconv();\n    return (unsigned char) lconv->decimal_point[0];\n#else\n    return '.';\n#endif\n}\n\ntypedef struct\n{\n    const unsigned char *content;\n    size_t length;\n    size_t offset;\n    size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */\n    internal_hooks hooks;\n} parse_buffer;\n\n/* check if the given size is left to read in a given parse buffer (starting with 1) */\n#define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length))\n/* check if the buffer can be accessed at the given index (starting with 0) */\n#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length))\n#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index))\n/* get a pointer to the buffer at the position */\n#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset)\n\n/* Parse the input text to generate a number, and populate the result into item. */\nstatic cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer)\n{\n    double number = 0;\n    unsigned char *after_end = NULL;\n    unsigned char *number_c_string;\n    unsigned char decimal_point = get_decimal_point();\n    size_t i = 0;\n    size_t number_string_length = 0;\n    cJSON_bool has_decimal_point = false;\n\n    if ((input_buffer == NULL) || (input_buffer->content == NULL))\n    {\n        return false;\n    }\n\n    /* copy the number into a temporary buffer and replace '.' with the decimal point\n     * of the current locale (for strtod)\n     * This also takes care of '\\0' not necessarily being available for marking the end of the input */\n    for (i = 0; can_access_at_index(input_buffer, i); i++)\n    {\n        switch (buffer_at_offset(input_buffer)[i])\n        {\n            case '0':\n            case '1':\n            case '2':\n            case '3':\n            case '4':\n            case '5':\n            case '6':\n            case '7':\n            case '8':\n            case '9':\n            case '+':\n            case '-':\n            case 'e':\n            case 'E':\n                number_string_length++;\n                break;\n\n            case '.':\n                number_string_length++;\n                has_decimal_point = true;\n                break;\n\n            default:\n                goto loop_end;\n        }\n    }\nloop_end:\n    /* malloc for temporary buffer, add 1 for '\\0' */\n    number_c_string = (unsigned char *) input_buffer->hooks.allocate(number_string_length + 1);\n    if (number_c_string == NULL)\n    {\n        return false; /* allocation failure */\n    }\n\n    memcpy(number_c_string, buffer_at_offset(input_buffer), number_string_length);\n    number_c_string[number_string_length] = '\\0';\n\n    if (has_decimal_point)\n    {\n        for (i = 0; i < number_string_length; i++)\n        {\n            if (number_c_string[i] == '.')\n            {\n                /* replace '.' with the decimal point of the current locale (for strtod) */\n                number_c_string[i] = decimal_point;\n            }\n        }\n    }\n\n    number = strtod((const char*)number_c_string, (char**)&after_end);\n    if (number_c_string == after_end)\n    {\n        /* free the temporary buffer */\n        input_buffer->hooks.deallocate(number_c_string);\n        return false; /* parse_error */\n    }\n\n    item->valuedouble = number;\n\n    /* use saturation in case of overflow */\n    if (number >= INT_MAX)\n    {\n        item->valueint = INT_MAX;\n    }\n    else if (number <= (double)INT_MIN)\n    {\n        item->valueint = INT_MIN;\n    }\n    else\n    {\n        item->valueint = (int)number;\n    }\n\n    item->type = cJSON_Number;\n\n    input_buffer->offset += (size_t)(after_end - number_c_string);\n    /* free the temporary buffer */\n    input_buffer->hooks.deallocate(number_c_string);\n    return true;\n}\n\n/* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */\nCJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number)\n{\n    if (number >= INT_MAX)\n    {\n        object->valueint = INT_MAX;\n    }\n    else if (number <= (double)INT_MIN)\n    {\n        object->valueint = INT_MIN;\n    }\n    else\n    {\n        object->valueint = (int)number;\n    }\n\n    return object->valuedouble = number;\n}\n\n/* Note: when passing a NULL valuestring, cJSON_SetValuestring treats this as an error and return NULL */\nCJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring)\n{\n    char *copy = NULL;\n    size_t v1_len;\n    size_t v2_len;\n    /* if object's type is not cJSON_String or is cJSON_IsReference, it should not set valuestring */\n    if ((object == NULL) || !(object->type & cJSON_String) || (object->type & cJSON_IsReference))\n    {\n        return NULL;\n    }\n    /* return NULL if the object is corrupted or valuestring is NULL */\n    if (object->valuestring == NULL || valuestring == NULL)\n    {\n        return NULL;\n    }\n\n    v1_len = strlen(valuestring);\n    v2_len = strlen(object->valuestring);\n\n    if (v1_len <= v2_len)\n    {\n        /* strcpy does not handle overlapping string: [X1, X2] [Y1, Y2] => X2 < Y1 or Y2 < X1 */\n        if (!( valuestring + v1_len < object->valuestring || object->valuestring + v2_len < valuestring ))\n        {\n            return NULL;\n        }\n        strcpy(object->valuestring, valuestring);\n        return object->valuestring;\n    }\n    copy = (char*) cJSON_strdup((const unsigned char*)valuestring, &global_hooks);\n    if (copy == NULL)\n    {\n        return NULL;\n    }\n    if (object->valuestring != NULL)\n    {\n        cJSON_free(object->valuestring);\n    }\n    object->valuestring = copy;\n\n    return copy;\n}\n\ntypedef struct\n{\n    unsigned char *buffer;\n    size_t length;\n    size_t offset;\n    size_t depth; /* current nesting depth (for formatted printing) */\n    cJSON_bool noalloc;\n    cJSON_bool format; /* is this print a formatted print */\n    internal_hooks hooks;\n} printbuffer;\n\n/* realloc printbuffer if necessary to have at least \"needed\" bytes more */\nstatic unsigned char* ensure(printbuffer * const p, size_t needed)\n{\n    unsigned char *newbuffer = NULL;\n    size_t newsize = 0;\n\n    if ((p == NULL) || (p->buffer == NULL))\n    {\n        return NULL;\n    }\n\n    if ((p->length > 0) && (p->offset >= p->length))\n    {\n        /* make sure that offset is valid */\n        return NULL;\n    }\n\n    if (needed > INT_MAX)\n    {\n        /* sizes bigger than INT_MAX are currently not supported */\n        return NULL;\n    }\n\n    needed += p->offset + 1;\n    if (needed <= p->length)\n    {\n        return p->buffer + p->offset;\n    }\n\n    if (p->noalloc) {\n        return NULL;\n    }\n\n    /* calculate new buffer size */\n    if (needed > (INT_MAX / 2))\n    {\n        /* overflow of int, use INT_MAX if possible */\n        if (needed <= INT_MAX)\n        {\n            newsize = INT_MAX;\n        }\n        else\n        {\n            return NULL;\n        }\n    }\n    else\n    {\n        newsize = needed * 2;\n    }\n\n    if (p->hooks.reallocate != NULL)\n    {\n        /* reallocate with realloc if available */\n        newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize);\n        if (newbuffer == NULL)\n        {\n            p->hooks.deallocate(p->buffer);\n            p->length = 0;\n            p->buffer = NULL;\n\n            return NULL;\n        }\n    }\n    else\n    {\n        /* otherwise reallocate manually */\n        newbuffer = (unsigned char*)p->hooks.allocate(newsize);\n        if (!newbuffer)\n        {\n            p->hooks.deallocate(p->buffer);\n            p->length = 0;\n            p->buffer = NULL;\n\n            return NULL;\n        }\n\n        memcpy(newbuffer, p->buffer, p->offset + 1);\n        p->hooks.deallocate(p->buffer);\n    }\n    p->length = newsize;\n    p->buffer = newbuffer;\n\n    return newbuffer + p->offset;\n}\n\n/* calculate the new length of the string in a printbuffer and update the offset */\nstatic void update_offset(printbuffer * const buffer)\n{\n    const unsigned char *buffer_pointer = NULL;\n    if ((buffer == NULL) || (buffer->buffer == NULL))\n    {\n        return;\n    }\n    buffer_pointer = buffer->buffer + buffer->offset;\n\n    buffer->offset += strlen((const char*)buffer_pointer);\n}\n\n/* securely comparison of floating-point variables */\nstatic cJSON_bool compare_double(double a, double b)\n{\n    double maxVal = fabs(a) > fabs(b) ? fabs(a) : fabs(b);\n    return (fabs(a - b) <= maxVal * DBL_EPSILON);\n}\n\n/* Render the number nicely from the given item into a string. */\nstatic cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer)\n{\n    unsigned char *output_pointer = NULL;\n    double d = item->valuedouble;\n    int length = 0;\n    size_t i = 0;\n    unsigned char number_buffer[26] = {0}; /* temporary buffer to print the number into */\n    unsigned char decimal_point = get_decimal_point();\n    double test = 0.0;\n\n    if (output_buffer == NULL)\n    {\n        return false;\n    }\n\n    /* This checks for NaN and Infinity */\n    if (isnan(d) || isinf(d))\n    {\n        length = sprintf((char*)number_buffer, \"null\");\n    }\n    else if(d == (double)item->valueint)\n    {\n        length = sprintf((char*)number_buffer, \"%d\", item->valueint);\n    }\n    else\n    {\n        /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */\n        length = sprintf((char*)number_buffer, \"%1.15g\", d);\n\n        /* Check whether the original double can be recovered */\n        if ((sscanf((char*)number_buffer, \"%lg\", &test) != 1) || !compare_double((double)test, d))\n        {\n            /* If not, print with 17 decimal places of precision */\n            length = sprintf((char*)number_buffer, \"%1.17g\", d);\n        }\n    }\n\n    /* sprintf failed or buffer overrun occurred */\n    if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1)))\n    {\n        return false;\n    }\n\n    /* reserve appropriate space in the output */\n    output_pointer = ensure(output_buffer, (size_t)length + sizeof(\"\"));\n    if (output_pointer == NULL)\n    {\n        return false;\n    }\n\n    /* copy the printed number to the output and replace locale\n     * dependent decimal point with '.' */\n    for (i = 0; i < ((size_t)length); i++)\n    {\n        if (number_buffer[i] == decimal_point)\n        {\n            output_pointer[i] = '.';\n            continue;\n        }\n\n        output_pointer[i] = number_buffer[i];\n    }\n    output_pointer[i] = '\\0';\n\n    output_buffer->offset += (size_t)length;\n\n    return true;\n}\n\n/* parse 4 digit hexadecimal number */\nstatic unsigned parse_hex4(const unsigned char * const input)\n{\n    unsigned int h = 0;\n    size_t i = 0;\n\n    for (i = 0; i < 4; i++)\n    {\n        /* parse digit */\n        if ((input[i] >= '0') && (input[i] <= '9'))\n        {\n            h += (unsigned int) input[i] - '0';\n        }\n        else if ((input[i] >= 'A') && (input[i] <= 'F'))\n        {\n            h += (unsigned int) 10 + input[i] - 'A';\n        }\n        else if ((input[i] >= 'a') && (input[i] <= 'f'))\n        {\n            h += (unsigned int) 10 + input[i] - 'a';\n        }\n        else /* invalid */\n        {\n            return 0;\n        }\n\n        if (i < 3)\n        {\n            /* shift left to make place for the next nibble */\n            h = h << 4;\n        }\n    }\n\n    return h;\n}\n\n/* converts a UTF-16 literal to UTF-8\n * A literal can be one or two sequences of the form \\uXXXX */\nstatic unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer)\n{\n    long unsigned int codepoint = 0;\n    unsigned int first_code = 0;\n    const unsigned char *first_sequence = input_pointer;\n    unsigned char utf8_length = 0;\n    unsigned char utf8_position = 0;\n    unsigned char sequence_length = 0;\n    unsigned char first_byte_mark = 0;\n\n    if ((input_end - first_sequence) < 6)\n    {\n        /* input ends unexpectedly */\n        goto fail;\n    }\n\n    /* get the first utf16 sequence */\n    first_code = parse_hex4(first_sequence + 2);\n\n    /* check that the code is valid */\n    if (((first_code >= 0xDC00) && (first_code <= 0xDFFF)))\n    {\n        goto fail;\n    }\n\n    /* UTF16 surrogate pair */\n    if ((first_code >= 0xD800) && (first_code <= 0xDBFF))\n    {\n        const unsigned char *second_sequence = first_sequence + 6;\n        unsigned int second_code = 0;\n        sequence_length = 12; /* \\uXXXX\\uXXXX */\n\n        if ((input_end - second_sequence) < 6)\n        {\n            /* input ends unexpectedly */\n            goto fail;\n        }\n\n        if ((second_sequence[0] != '\\\\') || (second_sequence[1] != 'u'))\n        {\n            /* missing second half of the surrogate pair */\n            goto fail;\n        }\n\n        /* get the second utf16 sequence */\n        second_code = parse_hex4(second_sequence + 2);\n        /* check that the code is valid */\n        if ((second_code < 0xDC00) || (second_code > 0xDFFF))\n        {\n            /* invalid second half of the surrogate pair */\n            goto fail;\n        }\n\n\n        /* calculate the unicode codepoint from the surrogate pair */\n        codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF));\n    }\n    else\n    {\n        sequence_length = 6; /* \\uXXXX */\n        codepoint = first_code;\n    }\n\n    /* encode as UTF-8\n     * takes at maximum 4 bytes to encode:\n     * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */\n    if (codepoint < 0x80)\n    {\n        /* normal ascii, encoding 0xxxxxxx */\n        utf8_length = 1;\n    }\n    else if (codepoint < 0x800)\n    {\n        /* two bytes, encoding 110xxxxx 10xxxxxx */\n        utf8_length = 2;\n        first_byte_mark = 0xC0; /* 11000000 */\n    }\n    else if (codepoint < 0x10000)\n    {\n        /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */\n        utf8_length = 3;\n        first_byte_mark = 0xE0; /* 11100000 */\n    }\n    else if (codepoint <= 0x10FFFF)\n    {\n        /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */\n        utf8_length = 4;\n        first_byte_mark = 0xF0; /* 11110000 */\n    }\n    else\n    {\n        /* invalid unicode codepoint */\n        goto fail;\n    }\n\n    /* encode as utf8 */\n    for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--)\n    {\n        /* 10xxxxxx */\n        (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF);\n        codepoint >>= 6;\n    }\n    /* encode first byte */\n    if (utf8_length > 1)\n    {\n        (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF);\n    }\n    else\n    {\n        (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F);\n    }\n\n    *output_pointer += utf8_length;\n\n    return sequence_length;\n\nfail:\n    return 0;\n}\n\n/* Parse the input text into an unescaped cinput, and populate item. */\nstatic cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer)\n{\n    const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1;\n    const unsigned char *input_end = buffer_at_offset(input_buffer) + 1;\n    unsigned char *output_pointer = NULL;\n    unsigned char *output = NULL;\n\n    /* not a string */\n    if (buffer_at_offset(input_buffer)[0] != '\\\"')\n    {\n        goto fail;\n    }\n\n    {\n        /* calculate approximate size of the output (overestimate) */\n        size_t allocation_length = 0;\n        size_t skipped_bytes = 0;\n        while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\\\"'))\n        {\n            /* is escape sequence */\n            if (input_end[0] == '\\\\')\n            {\n                if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length)\n                {\n                    /* prevent buffer overflow when last input character is a backslash */\n                    goto fail;\n                }\n                skipped_bytes++;\n                input_end++;\n            }\n            input_end++;\n        }\n        if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\\\"'))\n        {\n            goto fail; /* string ended unexpectedly */\n        }\n\n        /* This is at most how much we need for the output */\n        allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes;\n        output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof(\"\"));\n        if (output == NULL)\n        {\n            goto fail; /* allocation failure */\n        }\n    }\n\n    output_pointer = output;\n    /* loop through the string literal */\n    while (input_pointer < input_end)\n    {\n        if (*input_pointer != '\\\\')\n        {\n            *output_pointer++ = *input_pointer++;\n        }\n        /* escape sequence */\n        else\n        {\n            unsigned char sequence_length = 2;\n            if ((input_end - input_pointer) < 1)\n            {\n                goto fail;\n            }\n\n            switch (input_pointer[1])\n            {\n                case 'b':\n                    *output_pointer++ = '\\b';\n                    break;\n                case 'f':\n                    *output_pointer++ = '\\f';\n                    break;\n                case 'n':\n                    *output_pointer++ = '\\n';\n                    break;\n                case 'r':\n                    *output_pointer++ = '\\r';\n                    break;\n                case 't':\n                    *output_pointer++ = '\\t';\n                    break;\n                case '\\\"':\n                case '\\\\':\n                case '/':\n                    *output_pointer++ = input_pointer[1];\n                    break;\n\n                /* UTF-16 literal */\n                case 'u':\n                    sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer);\n                    if (sequence_length == 0)\n                    {\n                        /* failed to convert UTF16-literal to UTF-8 */\n                        goto fail;\n                    }\n                    break;\n\n                default:\n                    goto fail;\n            }\n            input_pointer += sequence_length;\n        }\n    }\n\n    /* zero terminate the output */\n    *output_pointer = '\\0';\n\n    item->type = cJSON_String;\n    item->valuestring = (char*)output;\n\n    input_buffer->offset = (size_t) (input_end - input_buffer->content);\n    input_buffer->offset++;\n\n    return true;\n\nfail:\n    if (output != NULL)\n    {\n        input_buffer->hooks.deallocate(output);\n        output = NULL;\n    }\n\n    if (input_pointer != NULL)\n    {\n        input_buffer->offset = (size_t)(input_pointer - input_buffer->content);\n    }\n\n    return false;\n}\n\n/* Render the cstring provided to an escaped version that can be printed. */\nstatic cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer)\n{\n    const unsigned char *input_pointer = NULL;\n    unsigned char *output = NULL;\n    unsigned char *output_pointer = NULL;\n    size_t output_length = 0;\n    /* numbers of additional characters needed for escaping */\n    size_t escape_characters = 0;\n\n    if (output_buffer == NULL)\n    {\n        return false;\n    }\n\n    /* empty string */\n    if (input == NULL)\n    {\n        output = ensure(output_buffer, sizeof(\"\\\"\\\"\"));\n        if (output == NULL)\n        {\n            return false;\n        }\n        strcpy((char*)output, \"\\\"\\\"\");\n\n        return true;\n    }\n\n    /* set \"flag\" to 1 if something needs to be escaped */\n    for (input_pointer = input; *input_pointer; input_pointer++)\n    {\n        switch (*input_pointer)\n        {\n            case '\\\"':\n            case '\\\\':\n            case '\\b':\n            case '\\f':\n            case '\\n':\n            case '\\r':\n            case '\\t':\n                /* one character escape sequence */\n                escape_characters++;\n                break;\n            default:\n                if (*input_pointer < 32)\n                {\n                    /* UTF-16 escape sequence uXXXX */\n                    escape_characters += 5;\n                }\n                break;\n        }\n    }\n    output_length = (size_t)(input_pointer - input) + escape_characters;\n\n    output = ensure(output_buffer, output_length + sizeof(\"\\\"\\\"\"));\n    if (output == NULL)\n    {\n        return false;\n    }\n\n    /* no characters have to be escaped */\n    if (escape_characters == 0)\n    {\n        output[0] = '\\\"';\n        memcpy(output + 1, input, output_length);\n        output[output_length + 1] = '\\\"';\n        output[output_length + 2] = '\\0';\n\n        return true;\n    }\n\n    output[0] = '\\\"';\n    output_pointer = output + 1;\n    /* copy the string */\n    for (input_pointer = input; *input_pointer != '\\0'; (void)input_pointer++, output_pointer++)\n    {\n        if ((*input_pointer > 31) && (*input_pointer != '\\\"') && (*input_pointer != '\\\\'))\n        {\n            /* normal character, copy */\n            *output_pointer = *input_pointer;\n        }\n        else\n        {\n            /* character needs to be escaped */\n            *output_pointer++ = '\\\\';\n            switch (*input_pointer)\n            {\n                case '\\\\':\n                    *output_pointer = '\\\\';\n                    break;\n                case '\\\"':\n                    *output_pointer = '\\\"';\n                    break;\n                case '\\b':\n                    *output_pointer = 'b';\n                    break;\n                case '\\f':\n                    *output_pointer = 'f';\n                    break;\n                case '\\n':\n                    *output_pointer = 'n';\n                    break;\n                case '\\r':\n                    *output_pointer = 'r';\n                    break;\n                case '\\t':\n                    *output_pointer = 't';\n                    break;\n                default:\n                    /* escape and print as unicode codepoint */\n                    sprintf((char*)output_pointer, \"u%04x\", *input_pointer);\n                    output_pointer += 4;\n                    break;\n            }\n        }\n    }\n    output[output_length + 1] = '\\\"';\n    output[output_length + 2] = '\\0';\n\n    return true;\n}\n\n/* Invoke print_string_ptr (which is useful) on an item. */\nstatic cJSON_bool print_string(const cJSON * const item, printbuffer * const p)\n{\n    return print_string_ptr((unsigned char*)item->valuestring, p);\n}\n\n/* Predeclare these prototypes. */\nstatic cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer);\nstatic cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer);\nstatic cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer);\nstatic cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer);\nstatic cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer);\nstatic cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer);\n\n/* Utility to jump whitespace and cr/lf */\nstatic parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer)\n{\n    if ((buffer == NULL) || (buffer->content == NULL))\n    {\n        return NULL;\n    }\n\n    if (cannot_access_at_index(buffer, 0))\n    {\n        return buffer;\n    }\n\n    while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32))\n    {\n       buffer->offset++;\n    }\n\n    if (buffer->offset == buffer->length)\n    {\n        buffer->offset--;\n    }\n\n    return buffer;\n}\n\n/* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */\nstatic parse_buffer *skip_utf8_bom(parse_buffer * const buffer)\n{\n    if ((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0))\n    {\n        return NULL;\n    }\n\n    if (can_access_at_index(buffer, 4) && (strncmp((const char*)buffer_at_offset(buffer), \"\\xEF\\xBB\\xBF\", 3) == 0))\n    {\n        buffer->offset += 3;\n    }\n\n    return buffer;\n}\n\nCJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated)\n{\n    size_t buffer_length;\n\n    if (NULL == value)\n    {\n        return NULL;\n    }\n\n    /* Adding null character size due to require_null_terminated. */\n    buffer_length = strlen(value) + sizeof(\"\");\n\n    return cJSON_ParseWithLengthOpts(value, buffer_length, return_parse_end, require_null_terminated);\n}\n\n/* Parse an object - create a new root, and populate. */\nCJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated)\n{\n    parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } };\n    cJSON *item = NULL;\n\n    /* reset error position */\n    global_error.json = NULL;\n    global_error.position = 0;\n\n    if (value == NULL || 0 == buffer_length)\n    {\n        goto fail;\n    }\n\n    buffer.content = (const unsigned char*)value;\n    buffer.length = buffer_length;\n    buffer.offset = 0;\n    buffer.hooks = global_hooks;\n\n    item = cJSON_New_Item(&global_hooks);\n    if (item == NULL) /* memory fail */\n    {\n        goto fail;\n    }\n\n    if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer))))\n    {\n        /* parse failure. ep is set. */\n        goto fail;\n    }\n\n    /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */\n    if (require_null_terminated)\n    {\n        buffer_skip_whitespace(&buffer);\n        if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\\0')\n        {\n            goto fail;\n        }\n    }\n    if (return_parse_end)\n    {\n        *return_parse_end = (const char*)buffer_at_offset(&buffer);\n    }\n\n    return item;\n\nfail:\n    if (item != NULL)\n    {\n        cJSON_Delete(item);\n    }\n\n    if (value != NULL)\n    {\n        error local_error;\n        local_error.json = (const unsigned char*)value;\n        local_error.position = 0;\n\n        if (buffer.offset < buffer.length)\n        {\n            local_error.position = buffer.offset;\n        }\n        else if (buffer.length > 0)\n        {\n            local_error.position = buffer.length - 1;\n        }\n\n        if (return_parse_end != NULL)\n        {\n            *return_parse_end = (const char*)local_error.json + local_error.position;\n        }\n\n        global_error = local_error;\n    }\n\n    return NULL;\n}\n\n/* Default options for cJSON_Parse */\nCJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value)\n{\n    return cJSON_ParseWithOpts(value, 0, 0);\n}\n\nCJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length)\n{\n    return cJSON_ParseWithLengthOpts(value, buffer_length, 0, 0);\n}\n\n#define cjson_min(a, b) (((a) < (b)) ? (a) : (b))\n\nstatic unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks)\n{\n    static const size_t default_buffer_size = 256;\n    printbuffer buffer[1];\n    unsigned char *printed = NULL;\n\n    memset(buffer, 0, sizeof(buffer));\n\n    /* create buffer */\n    buffer->buffer = (unsigned char*) hooks->allocate(default_buffer_size);\n    buffer->length = default_buffer_size;\n    buffer->format = format;\n    buffer->hooks = *hooks;\n    if (buffer->buffer == NULL)\n    {\n        goto fail;\n    }\n\n    /* print the value */\n    if (!print_value(item, buffer))\n    {\n        goto fail;\n    }\n    update_offset(buffer);\n\n    /* check if reallocate is available */\n    if (hooks->reallocate != NULL)\n    {\n        printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->offset + 1);\n        if (printed == NULL) {\n            goto fail;\n        }\n        buffer->buffer = NULL;\n    }\n    else /* otherwise copy the JSON over to a new buffer */\n    {\n        printed = (unsigned char*) hooks->allocate(buffer->offset + 1);\n        if (printed == NULL)\n        {\n            goto fail;\n        }\n        memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1));\n        printed[buffer->offset] = '\\0'; /* just to be sure */\n\n        /* free the buffer */\n        hooks->deallocate(buffer->buffer);\n        buffer->buffer = NULL;\n    }\n\n    return printed;\n\nfail:\n    if (buffer->buffer != NULL)\n    {\n        hooks->deallocate(buffer->buffer);\n        buffer->buffer = NULL;\n    }\n\n    if (printed != NULL)\n    {\n        hooks->deallocate(printed);\n        printed = NULL;\n    }\n\n    return NULL;\n}\n\n/* Render a cJSON item/entity/structure to text. */\nCJSON_PUBLIC(char *) cJSON_Print(const cJSON *item)\n{\n    return (char*)print(item, true, &global_hooks);\n}\n\nCJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item)\n{\n    return (char*)print(item, false, &global_hooks);\n}\n\nCJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt)\n{\n    printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } };\n\n    if (prebuffer < 0)\n    {\n        return NULL;\n    }\n\n    p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer);\n    if (!p.buffer)\n    {\n        return NULL;\n    }\n\n    p.length = (size_t)prebuffer;\n    p.offset = 0;\n    p.noalloc = false;\n    p.format = fmt;\n    p.hooks = global_hooks;\n\n    if (!print_value(item, &p))\n    {\n        global_hooks.deallocate(p.buffer);\n        p.buffer = NULL;\n        return NULL;\n    }\n\n    return (char*)p.buffer;\n}\n\nCJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format)\n{\n    printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } };\n\n    if ((length < 0) || (buffer == NULL))\n    {\n        return false;\n    }\n\n    p.buffer = (unsigned char*)buffer;\n    p.length = (size_t)length;\n    p.offset = 0;\n    p.noalloc = true;\n    p.format = format;\n    p.hooks = global_hooks;\n\n    return print_value(item, &p);\n}\n\n/* Parser core - when encountering text, process appropriately. */\nstatic cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer)\n{\n    if ((input_buffer == NULL) || (input_buffer->content == NULL))\n    {\n        return false; /* no input */\n    }\n\n    /* parse the different types of values */\n    /* null */\n    if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), \"null\", 4) == 0))\n    {\n        item->type = cJSON_NULL;\n        input_buffer->offset += 4;\n        return true;\n    }\n    /* false */\n    if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), \"false\", 5) == 0))\n    {\n        item->type = cJSON_False;\n        input_buffer->offset += 5;\n        return true;\n    }\n    /* true */\n    if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), \"true\", 4) == 0))\n    {\n        item->type = cJSON_True;\n        item->valueint = 1;\n        input_buffer->offset += 4;\n        return true;\n    }\n    /* string */\n    if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\\\"'))\n    {\n        return parse_string(item, input_buffer);\n    }\n    /* number */\n    if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9'))))\n    {\n        return parse_number(item, input_buffer);\n    }\n    /* array */\n    if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '['))\n    {\n        return parse_array(item, input_buffer);\n    }\n    /* object */\n    if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{'))\n    {\n        return parse_object(item, input_buffer);\n    }\n\n    return false;\n}\n\n/* Render a value to text. */\nstatic cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer)\n{\n    unsigned char *output = NULL;\n\n    if ((item == NULL) || (output_buffer == NULL))\n    {\n        return false;\n    }\n\n    switch ((item->type) & 0xFF)\n    {\n        case cJSON_NULL:\n            output = ensure(output_buffer, 5);\n            if (output == NULL)\n            {\n                return false;\n            }\n            strcpy((char*)output, \"null\");\n            return true;\n\n        case cJSON_False:\n            output = ensure(output_buffer, 6);\n            if (output == NULL)\n            {\n                return false;\n            }\n            strcpy((char*)output, \"false\");\n            return true;\n\n        case cJSON_True:\n            output = ensure(output_buffer, 5);\n            if (output == NULL)\n            {\n                return false;\n            }\n            strcpy((char*)output, \"true\");\n            return true;\n\n        case cJSON_Number:\n            return print_number(item, output_buffer);\n\n        case cJSON_Raw:\n        {\n            size_t raw_length = 0;\n            if (item->valuestring == NULL)\n            {\n                return false;\n            }\n\n            raw_length = strlen(item->valuestring) + sizeof(\"\");\n            output = ensure(output_buffer, raw_length);\n            if (output == NULL)\n            {\n                return false;\n            }\n            memcpy(output, item->valuestring, raw_length);\n            return true;\n        }\n\n        case cJSON_String:\n            return print_string(item, output_buffer);\n\n        case cJSON_Array:\n            return print_array(item, output_buffer);\n\n        case cJSON_Object:\n            return print_object(item, output_buffer);\n\n        default:\n            return false;\n    }\n}\n\n/* Build an array from input text. */\nstatic cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer)\n{\n    cJSON *head = NULL; /* head of the linked list */\n    cJSON *current_item = NULL;\n\n    if (input_buffer->depth >= CJSON_NESTING_LIMIT)\n    {\n        return false; /* to deeply nested */\n    }\n    input_buffer->depth++;\n\n    if (buffer_at_offset(input_buffer)[0] != '[')\n    {\n        /* not an array */\n        goto fail;\n    }\n\n    input_buffer->offset++;\n    buffer_skip_whitespace(input_buffer);\n    if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']'))\n    {\n        /* empty array */\n        goto success;\n    }\n\n    /* check if we skipped to the end of the buffer */\n    if (cannot_access_at_index(input_buffer, 0))\n    {\n        input_buffer->offset--;\n        goto fail;\n    }\n\n    /* step back to character in front of the first element */\n    input_buffer->offset--;\n    /* loop through the comma separated array elements */\n    do\n    {\n        /* allocate next item */\n        cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks));\n        if (new_item == NULL)\n        {\n            goto fail; /* allocation failure */\n        }\n\n        /* attach next item to list */\n        if (head == NULL)\n        {\n            /* start the linked list */\n            current_item = head = new_item;\n        }\n        else\n        {\n            /* add to the end and advance */\n            current_item->next = new_item;\n            new_item->prev = current_item;\n            current_item = new_item;\n        }\n\n        /* parse next value */\n        input_buffer->offset++;\n        buffer_skip_whitespace(input_buffer);\n        if (!parse_value(current_item, input_buffer))\n        {\n            goto fail; /* failed to parse value */\n        }\n        buffer_skip_whitespace(input_buffer);\n    }\n    while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ','));\n\n    if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']')\n    {\n        goto fail; /* expected end of array */\n    }\n\nsuccess:\n    input_buffer->depth--;\n\n    if (head != NULL) {\n        head->prev = current_item;\n    }\n\n    item->type = cJSON_Array;\n    item->child = head;\n\n    input_buffer->offset++;\n\n    return true;\n\nfail:\n    if (head != NULL)\n    {\n        cJSON_Delete(head);\n    }\n\n    return false;\n}\n\n/* Render an array to text */\nstatic cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer)\n{\n    unsigned char *output_pointer = NULL;\n    size_t length = 0;\n    cJSON *current_element = item->child;\n\n    if (output_buffer == NULL)\n    {\n        return false;\n    }\n\n    /* Compose the output array. */\n    /* opening square bracket */\n    output_pointer = ensure(output_buffer, 1);\n    if (output_pointer == NULL)\n    {\n        return false;\n    }\n\n    *output_pointer = '[';\n    output_buffer->offset++;\n    output_buffer->depth++;\n\n    while (current_element != NULL)\n    {\n        if (!print_value(current_element, output_buffer))\n        {\n            return false;\n        }\n        update_offset(output_buffer);\n        if (current_element->next)\n        {\n            length = (size_t) (output_buffer->format ? 2 : 1);\n            output_pointer = ensure(output_buffer, length + 1);\n            if (output_pointer == NULL)\n            {\n                return false;\n            }\n            *output_pointer++ = ',';\n            if(output_buffer->format)\n            {\n                *output_pointer++ = ' ';\n            }\n            *output_pointer = '\\0';\n            output_buffer->offset += length;\n        }\n        current_element = current_element->next;\n    }\n\n    output_pointer = ensure(output_buffer, 2);\n    if (output_pointer == NULL)\n    {\n        return false;\n    }\n    *output_pointer++ = ']';\n    *output_pointer = '\\0';\n    output_buffer->depth--;\n\n    return true;\n}\n\n/* Build an object from the text. */\nstatic cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer)\n{\n    cJSON *head = NULL; /* linked list head */\n    cJSON *current_item = NULL;\n\n    if (input_buffer->depth >= CJSON_NESTING_LIMIT)\n    {\n        return false; /* to deeply nested */\n    }\n    input_buffer->depth++;\n\n    if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{'))\n    {\n        goto fail; /* not an object */\n    }\n\n    input_buffer->offset++;\n    buffer_skip_whitespace(input_buffer);\n    if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}'))\n    {\n        goto success; /* empty object */\n    }\n\n    /* check if we skipped to the end of the buffer */\n    if (cannot_access_at_index(input_buffer, 0))\n    {\n        input_buffer->offset--;\n        goto fail;\n    }\n\n    /* step back to character in front of the first element */\n    input_buffer->offset--;\n    /* loop through the comma separated array elements */\n    do\n    {\n        /* allocate next item */\n        cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks));\n        if (new_item == NULL)\n        {\n            goto fail; /* allocation failure */\n        }\n\n        /* attach next item to list */\n        if (head == NULL)\n        {\n            /* start the linked list */\n            current_item = head = new_item;\n        }\n        else\n        {\n            /* add to the end and advance */\n            current_item->next = new_item;\n            new_item->prev = current_item;\n            current_item = new_item;\n        }\n\n        if (cannot_access_at_index(input_buffer, 1))\n        {\n            goto fail; /* nothing comes after the comma */\n        }\n\n        /* parse the name of the child */\n        input_buffer->offset++;\n        buffer_skip_whitespace(input_buffer);\n        if (!parse_string(current_item, input_buffer))\n        {\n            goto fail; /* failed to parse name */\n        }\n        buffer_skip_whitespace(input_buffer);\n\n        /* swap valuestring and string, because we parsed the name */\n        current_item->string = current_item->valuestring;\n        current_item->valuestring = NULL;\n\n        if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':'))\n        {\n            goto fail; /* invalid object */\n        }\n\n        /* parse the value */\n        input_buffer->offset++;\n        buffer_skip_whitespace(input_buffer);\n        if (!parse_value(current_item, input_buffer))\n        {\n            goto fail; /* failed to parse value */\n        }\n        buffer_skip_whitespace(input_buffer);\n    }\n    while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ','));\n\n    if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}'))\n    {\n        goto fail; /* expected end of object */\n    }\n\nsuccess:\n    input_buffer->depth--;\n\n    if (head != NULL) {\n        head->prev = current_item;\n    }\n\n    item->type = cJSON_Object;\n    item->child = head;\n\n    input_buffer->offset++;\n    return true;\n\nfail:\n    if (head != NULL)\n    {\n        cJSON_Delete(head);\n    }\n\n    return false;\n}\n\n/* Render an object to text. */\nstatic cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer)\n{\n    unsigned char *output_pointer = NULL;\n    size_t length = 0;\n    cJSON *current_item = item->child;\n\n    if (output_buffer == NULL)\n    {\n        return false;\n    }\n\n    /* Compose the output: */\n    length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\\n */\n    output_pointer = ensure(output_buffer, length + 1);\n    if (output_pointer == NULL)\n    {\n        return false;\n    }\n\n    *output_pointer++ = '{';\n    output_buffer->depth++;\n    if (output_buffer->format)\n    {\n        *output_pointer++ = '\\n';\n    }\n    output_buffer->offset += length;\n\n    while (current_item)\n    {\n        if (output_buffer->format)\n        {\n            size_t i;\n            output_pointer = ensure(output_buffer, output_buffer->depth);\n            if (output_pointer == NULL)\n            {\n                return false;\n            }\n            for (i = 0; i < output_buffer->depth; i++)\n            {\n                *output_pointer++ = '\\t';\n            }\n            output_buffer->offset += output_buffer->depth;\n        }\n\n        /* print key */\n        if (!print_string_ptr((unsigned char*)current_item->string, output_buffer))\n        {\n            return false;\n        }\n        update_offset(output_buffer);\n\n        length = (size_t) (output_buffer->format ? 2 : 1);\n        output_pointer = ensure(output_buffer, length);\n        if (output_pointer == NULL)\n        {\n            return false;\n        }\n        *output_pointer++ = ':';\n        if (output_buffer->format)\n        {\n            *output_pointer++ = '\\t';\n        }\n        output_buffer->offset += length;\n\n        /* print value */\n        if (!print_value(current_item, output_buffer))\n        {\n            return false;\n        }\n        update_offset(output_buffer);\n\n        /* print comma if not last */\n        length = ((size_t)(output_buffer->format ? 1 : 0) + (size_t)(current_item->next ? 1 : 0));\n        output_pointer = ensure(output_buffer, length + 1);\n        if (output_pointer == NULL)\n        {\n            return false;\n        }\n        if (current_item->next)\n        {\n            *output_pointer++ = ',';\n        }\n\n        if (output_buffer->format)\n        {\n            *output_pointer++ = '\\n';\n        }\n        *output_pointer = '\\0';\n        output_buffer->offset += length;\n\n        current_item = current_item->next;\n    }\n\n    output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2);\n    if (output_pointer == NULL)\n    {\n        return false;\n    }\n    if (output_buffer->format)\n    {\n        size_t i;\n        for (i = 0; i < (output_buffer->depth - 1); i++)\n        {\n            *output_pointer++ = '\\t';\n        }\n    }\n    *output_pointer++ = '}';\n    *output_pointer = '\\0';\n    output_buffer->depth--;\n\n    return true;\n}\n\n/* Get Array size/item / object item. */\nCJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array)\n{\n    cJSON *child = NULL;\n    size_t size = 0;\n\n    if (array == NULL)\n    {\n        return 0;\n    }\n\n    child = array->child;\n\n    while(child != NULL)\n    {\n        size++;\n        child = child->next;\n    }\n\n    /* FIXME: Can overflow here. Cannot be fixed without breaking the API */\n\n    return (int)size;\n}\n\nstatic cJSON* get_array_item(const cJSON *array, size_t index)\n{\n    cJSON *current_child = NULL;\n\n    if (array == NULL)\n    {\n        return NULL;\n    }\n\n    current_child = array->child;\n    while ((current_child != NULL) && (index > 0))\n    {\n        index--;\n        current_child = current_child->next;\n    }\n\n    return current_child;\n}\n\nCJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index)\n{\n    if (index < 0)\n    {\n        return NULL;\n    }\n\n    return get_array_item(array, (size_t)index);\n}\n\nstatic cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive)\n{\n    cJSON *current_element = NULL;\n\n    if ((object == NULL) || (name == NULL))\n    {\n        return NULL;\n    }\n\n    current_element = object->child;\n    if (case_sensitive)\n    {\n        while ((current_element != NULL) && (current_element->string != NULL) && (strcmp(name, current_element->string) != 0))\n        {\n            current_element = current_element->next;\n        }\n    }\n    else\n    {\n        while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0))\n        {\n            current_element = current_element->next;\n        }\n    }\n\n    if ((current_element == NULL) || (current_element->string == NULL)) {\n        return NULL;\n    }\n\n    return current_element;\n}\n\nCJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string)\n{\n    return get_object_item(object, string, false);\n}\n\nCJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string)\n{\n    return get_object_item(object, string, true);\n}\n\nCJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string)\n{\n    return cJSON_GetObjectItem(object, string) ? 1 : 0;\n}\n\n/* Utility for array list handling. */\nstatic void suffix_object(cJSON *prev, cJSON *item)\n{\n    prev->next = item;\n    item->prev = prev;\n}\n\n/* Utility for handling references. */\nstatic cJSON *create_reference(const cJSON *item, const internal_hooks * const hooks)\n{\n    cJSON *reference = NULL;\n    if (item == NULL)\n    {\n        return NULL;\n    }\n\n    reference = cJSON_New_Item(hooks);\n    if (reference == NULL)\n    {\n        return NULL;\n    }\n\n    memcpy(reference, item, sizeof(cJSON));\n    reference->string = NULL;\n    reference->type |= cJSON_IsReference;\n    reference->next = reference->prev = NULL;\n    return reference;\n}\n\nstatic cJSON_bool add_item_to_array(cJSON *array, cJSON *item)\n{\n    cJSON *child = NULL;\n\n    if ((item == NULL) || (array == NULL) || (array == item))\n    {\n        return false;\n    }\n\n    child = array->child;\n    /*\n     * To find the last item in array quickly, we use prev in array\n     */\n    if (child == NULL)\n    {\n        /* list is empty, start new one */\n        array->child = item;\n        item->prev = item;\n        item->next = NULL;\n    }\n    else\n    {\n        /* append to the end */\n        if (child->prev)\n        {\n            suffix_object(child->prev, item);\n            array->child->prev = item;\n        }\n    }\n\n    return true;\n}\n\n/* Add item to array/object. */\nCJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item)\n{\n    return add_item_to_array(array, item);\n}\n\n#if defined(__clang__) || (defined(__GNUC__)  && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5))))\n    #pragma GCC diagnostic push\n#endif\n#ifdef __GNUC__\n#pragma GCC diagnostic ignored \"-Wcast-qual\"\n#endif\n/* helper function to cast away const */\nstatic void* cast_away_const(const void* string)\n{\n    return (void*)string;\n}\n#if defined(__clang__) || (defined(__GNUC__)  && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5))))\n    #pragma GCC diagnostic pop\n#endif\n\n\nstatic cJSON_bool add_item_to_object(cJSON * const object, const char * const string, cJSON * const item, const internal_hooks * const hooks, const cJSON_bool constant_key)\n{\n    char *new_key = NULL;\n    int new_type = cJSON_Invalid;\n\n    if ((object == NULL) || (string == NULL) || (item == NULL) || (object == item))\n    {\n        return false;\n    }\n\n    if (constant_key)\n    {\n        new_key = (char*)cast_away_const(string);\n        new_type = item->type | cJSON_StringIsConst;\n    }\n    else\n    {\n        new_key = (char*)cJSON_strdup((const unsigned char*)string, hooks);\n        if (new_key == NULL)\n        {\n            return false;\n        }\n\n        new_type = item->type & ~cJSON_StringIsConst;\n    }\n\n    if (!(item->type & cJSON_StringIsConst) && (item->string != NULL))\n    {\n        hooks->deallocate(item->string);\n    }\n\n    item->string = new_key;\n    item->type = new_type;\n\n    return add_item_to_array(object, item);\n}\n\nCJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item)\n{\n    return add_item_to_object(object, string, item, &global_hooks, false);\n}\n\n/* Add an item to an object with constant string as key */\nCJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item)\n{\n    return add_item_to_object(object, string, item, &global_hooks, true);\n}\n\nCJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item)\n{\n    if (array == NULL)\n    {\n        return false;\n    }\n\n    return add_item_to_array(array, create_reference(item, &global_hooks));\n}\n\nCJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item)\n{\n    if ((object == NULL) || (string == NULL))\n    {\n        return false;\n    }\n\n    return add_item_to_object(object, string, create_reference(item, &global_hooks), &global_hooks, false);\n}\n\nCJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name)\n{\n    cJSON *null = cJSON_CreateNull();\n    if (add_item_to_object(object, name, null, &global_hooks, false))\n    {\n        return null;\n    }\n\n    cJSON_Delete(null);\n    return NULL;\n}\n\nCJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name)\n{\n    cJSON *true_item = cJSON_CreateTrue();\n    if (add_item_to_object(object, name, true_item, &global_hooks, false))\n    {\n        return true_item;\n    }\n\n    cJSON_Delete(true_item);\n    return NULL;\n}\n\nCJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name)\n{\n    cJSON *false_item = cJSON_CreateFalse();\n    if (add_item_to_object(object, name, false_item, &global_hooks, false))\n    {\n        return false_item;\n    }\n\n    cJSON_Delete(false_item);\n    return NULL;\n}\n\nCJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean)\n{\n    cJSON *bool_item = cJSON_CreateBool(boolean);\n    if (add_item_to_object(object, name, bool_item, &global_hooks, false))\n    {\n        return bool_item;\n    }\n\n    cJSON_Delete(bool_item);\n    return NULL;\n}\n\nCJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number)\n{\n    cJSON *number_item = cJSON_CreateNumber(number);\n    if (add_item_to_object(object, name, number_item, &global_hooks, false))\n    {\n        return number_item;\n    }\n\n    cJSON_Delete(number_item);\n    return NULL;\n}\n\nCJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string)\n{\n    cJSON *string_item = cJSON_CreateString(string);\n    if (add_item_to_object(object, name, string_item, &global_hooks, false))\n    {\n        return string_item;\n    }\n\n    cJSON_Delete(string_item);\n    return NULL;\n}\n\nCJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw)\n{\n    cJSON *raw_item = cJSON_CreateRaw(raw);\n    if (add_item_to_object(object, name, raw_item, &global_hooks, false))\n    {\n        return raw_item;\n    }\n\n    cJSON_Delete(raw_item);\n    return NULL;\n}\n\nCJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name)\n{\n    cJSON *object_item = cJSON_CreateObject();\n    if (add_item_to_object(object, name, object_item, &global_hooks, false))\n    {\n        return object_item;\n    }\n\n    cJSON_Delete(object_item);\n    return NULL;\n}\n\nCJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name)\n{\n    cJSON *array = cJSON_CreateArray();\n    if (add_item_to_object(object, name, array, &global_hooks, false))\n    {\n        return array;\n    }\n\n    cJSON_Delete(array);\n    return NULL;\n}\n\nCJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item)\n{\n    if ((parent == NULL) || (item == NULL) || (item != parent->child && item->prev == NULL))\n    {\n        return NULL;\n    }\n\n    if (item != parent->child)\n    {\n        /* not the first element */\n        item->prev->next = item->next;\n    }\n    if (item->next != NULL)\n    {\n        /* not the last element */\n        item->next->prev = item->prev;\n    }\n\n    if (item == parent->child)\n    {\n        /* first element */\n        parent->child = item->next;\n    }\n    else if (item->next == NULL)\n    {\n        /* last element */\n        parent->child->prev = item->prev;\n    }\n\n    /* make sure the detached item doesn't point anywhere anymore */\n    item->prev = NULL;\n    item->next = NULL;\n\n    return item;\n}\n\nCJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which)\n{\n    if (which < 0)\n    {\n        return NULL;\n    }\n\n    return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which));\n}\n\nCJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which)\n{\n    cJSON_Delete(cJSON_DetachItemFromArray(array, which));\n}\n\nCJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string)\n{\n    cJSON *to_detach = cJSON_GetObjectItem(object, string);\n\n    return cJSON_DetachItemViaPointer(object, to_detach);\n}\n\nCJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string)\n{\n    cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string);\n\n    return cJSON_DetachItemViaPointer(object, to_detach);\n}\n\nCJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string)\n{\n    cJSON_Delete(cJSON_DetachItemFromObject(object, string));\n}\n\nCJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string)\n{\n    cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string));\n}\n\n/* Replace array/object items with new ones. */\nCJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem)\n{\n    cJSON *after_inserted = NULL;\n\n    if (which < 0 || newitem == NULL)\n    {\n        return false;\n    }\n\n    after_inserted = get_array_item(array, (size_t)which);\n    if (after_inserted == NULL)\n    {\n        return add_item_to_array(array, newitem);\n    }\n\n    if (after_inserted != array->child && after_inserted->prev == NULL) {\n        /* return false if after_inserted is a corrupted array item */\n        return false;\n    }\n\n    newitem->next = after_inserted;\n    newitem->prev = after_inserted->prev;\n    after_inserted->prev = newitem;\n    if (after_inserted == array->child)\n    {\n        array->child = newitem;\n    }\n    else\n    {\n        newitem->prev->next = newitem;\n    }\n    return true;\n}\n\nCJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement)\n{\n    if ((parent == NULL) || (parent->child == NULL) || (replacement == NULL) || (item == NULL))\n    {\n        return false;\n    }\n\n    if (replacement == item)\n    {\n        return true;\n    }\n\n    replacement->next = item->next;\n    replacement->prev = item->prev;\n\n    if (replacement->next != NULL)\n    {\n        replacement->next->prev = replacement;\n    }\n    if (parent->child == item)\n    {\n        if (parent->child->prev == parent->child)\n        {\n            replacement->prev = replacement;\n        }\n        parent->child = replacement;\n    }\n    else\n    {   /*\n         * To find the last item in array quickly, we use prev in array.\n         * We can't modify the last item's next pointer where this item was the parent's child\n         */\n        if (replacement->prev != NULL)\n        {\n            replacement->prev->next = replacement;\n        }\n        if (replacement->next == NULL)\n        {\n            parent->child->prev = replacement;\n        }\n    }\n\n    item->next = NULL;\n    item->prev = NULL;\n    cJSON_Delete(item);\n\n    return true;\n}\n\nCJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem)\n{\n    if (which < 0)\n    {\n        return false;\n    }\n\n    return cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem);\n}\n\nstatic cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive)\n{\n    if ((replacement == NULL) || (string == NULL))\n    {\n        return false;\n    }\n\n    /* replace the name in the replacement */\n    if (!(replacement->type & cJSON_StringIsConst) && (replacement->string != NULL))\n    {\n        cJSON_free(replacement->string);\n    }\n    replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks);\n    if (replacement->string == NULL)\n    {\n        return false;\n    }\n\n    replacement->type &= ~cJSON_StringIsConst;\n\n    return cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement);\n}\n\nCJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem)\n{\n    return replace_item_in_object(object, string, newitem, false);\n}\n\nCJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem)\n{\n    return replace_item_in_object(object, string, newitem, true);\n}\n\n/* Create basic types: */\nCJSON_PUBLIC(cJSON *) cJSON_CreateNull(void)\n{\n    cJSON *item = cJSON_New_Item(&global_hooks);\n    if(item)\n    {\n        item->type = cJSON_NULL;\n    }\n\n    return item;\n}\n\nCJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void)\n{\n    cJSON *item = cJSON_New_Item(&global_hooks);\n    if(item)\n    {\n        item->type = cJSON_True;\n    }\n\n    return item;\n}\n\nCJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void)\n{\n    cJSON *item = cJSON_New_Item(&global_hooks);\n    if(item)\n    {\n        item->type = cJSON_False;\n    }\n\n    return item;\n}\n\nCJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean)\n{\n    cJSON *item = cJSON_New_Item(&global_hooks);\n    if(item)\n    {\n        item->type = boolean ? cJSON_True : cJSON_False;\n    }\n\n    return item;\n}\n\nCJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num)\n{\n    cJSON *item = cJSON_New_Item(&global_hooks);\n    if(item)\n    {\n        item->type = cJSON_Number;\n        item->valuedouble = num;\n\n        /* use saturation in case of overflow */\n        if (num >= INT_MAX)\n        {\n            item->valueint = INT_MAX;\n        }\n        else if (num <= (double)INT_MIN)\n        {\n            item->valueint = INT_MIN;\n        }\n        else\n        {\n            item->valueint = (int)num;\n        }\n    }\n\n    return item;\n}\n\nCJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string)\n{\n    cJSON *item = cJSON_New_Item(&global_hooks);\n    if(item)\n    {\n        item->type = cJSON_String;\n        item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks);\n        if(!item->valuestring)\n        {\n            cJSON_Delete(item);\n            return NULL;\n        }\n    }\n\n    return item;\n}\n\nCJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string)\n{\n    cJSON *item = cJSON_New_Item(&global_hooks);\n    if (item != NULL)\n    {\n        item->type = cJSON_String | cJSON_IsReference;\n        item->valuestring = (char*)cast_away_const(string);\n    }\n\n    return item;\n}\n\nCJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child)\n{\n    cJSON *item = cJSON_New_Item(&global_hooks);\n    if (item != NULL) {\n        item->type = cJSON_Object | cJSON_IsReference;\n        item->child = (cJSON*)cast_away_const(child);\n    }\n\n    return item;\n}\n\nCJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child) {\n    cJSON *item = cJSON_New_Item(&global_hooks);\n    if (item != NULL) {\n        item->type = cJSON_Array | cJSON_IsReference;\n        item->child = (cJSON*)cast_away_const(child);\n    }\n\n    return item;\n}\n\nCJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw)\n{\n    cJSON *item = cJSON_New_Item(&global_hooks);\n    if(item)\n    {\n        item->type = cJSON_Raw;\n        item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks);\n        if(!item->valuestring)\n        {\n            cJSON_Delete(item);\n            return NULL;\n        }\n    }\n\n    return item;\n}\n\nCJSON_PUBLIC(cJSON *) cJSON_CreateArray(void)\n{\n    cJSON *item = cJSON_New_Item(&global_hooks);\n    if(item)\n    {\n        item->type=cJSON_Array;\n    }\n\n    return item;\n}\n\nCJSON_PUBLIC(cJSON *) cJSON_CreateObject(void)\n{\n    cJSON *item = cJSON_New_Item(&global_hooks);\n    if (item)\n    {\n        item->type = cJSON_Object;\n    }\n\n    return item;\n}\n\n/* Create Arrays: */\nCJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count)\n{\n    size_t i = 0;\n    cJSON *n = NULL;\n    cJSON *p = NULL;\n    cJSON *a = NULL;\n\n    if ((count < 0) || (numbers == NULL))\n    {\n        return NULL;\n    }\n\n    a = cJSON_CreateArray();\n\n    for(i = 0; a && (i < (size_t)count); i++)\n    {\n        n = cJSON_CreateNumber(numbers[i]);\n        if (!n)\n        {\n            cJSON_Delete(a);\n            return NULL;\n        }\n        if(!i)\n        {\n            a->child = n;\n        }\n        else\n        {\n            suffix_object(p, n);\n        }\n        p = n;\n    }\n\n    if (a && a->child) {\n        a->child->prev = n;\n    }\n\n    return a;\n}\n\nCJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count)\n{\n    size_t i = 0;\n    cJSON *n = NULL;\n    cJSON *p = NULL;\n    cJSON *a = NULL;\n\n    if ((count < 0) || (numbers == NULL))\n    {\n        return NULL;\n    }\n\n    a = cJSON_CreateArray();\n\n    for(i = 0; a && (i < (size_t)count); i++)\n    {\n        n = cJSON_CreateNumber((double)numbers[i]);\n        if(!n)\n        {\n            cJSON_Delete(a);\n            return NULL;\n        }\n        if(!i)\n        {\n            a->child = n;\n        }\n        else\n        {\n            suffix_object(p, n);\n        }\n        p = n;\n    }\n\n    if (a && a->child) {\n        a->child->prev = n;\n    }\n\n    return a;\n}\n\nCJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count)\n{\n    size_t i = 0;\n    cJSON *n = NULL;\n    cJSON *p = NULL;\n    cJSON *a = NULL;\n\n    if ((count < 0) || (numbers == NULL))\n    {\n        return NULL;\n    }\n\n    a = cJSON_CreateArray();\n\n    for(i = 0; a && (i < (size_t)count); i++)\n    {\n        n = cJSON_CreateNumber(numbers[i]);\n        if(!n)\n        {\n            cJSON_Delete(a);\n            return NULL;\n        }\n        if(!i)\n        {\n            a->child = n;\n        }\n        else\n        {\n            suffix_object(p, n);\n        }\n        p = n;\n    }\n\n    if (a && a->child) {\n        a->child->prev = n;\n    }\n\n    return a;\n}\n\nCJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count)\n{\n    size_t i = 0;\n    cJSON *n = NULL;\n    cJSON *p = NULL;\n    cJSON *a = NULL;\n\n    if ((count < 0) || (strings == NULL))\n    {\n        return NULL;\n    }\n\n    a = cJSON_CreateArray();\n\n    for (i = 0; a && (i < (size_t)count); i++)\n    {\n        n = cJSON_CreateString(strings[i]);\n        if(!n)\n        {\n            cJSON_Delete(a);\n            return NULL;\n        }\n        if(!i)\n        {\n            a->child = n;\n        }\n        else\n        {\n            suffix_object(p,n);\n        }\n        p = n;\n    }\n\n    if (a && a->child) {\n        a->child->prev = n;\n    }\n\n    return a;\n}\n\n/* Duplication */\ncJSON * cJSON_Duplicate_rec(const cJSON *item, size_t depth, cJSON_bool recurse);\n\nCJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse)\n{\n    return cJSON_Duplicate_rec(item, 0, recurse );\n}\n\ncJSON * cJSON_Duplicate_rec(const cJSON *item, size_t depth, cJSON_bool recurse)\n{\n    cJSON *newitem = NULL;\n    cJSON *child = NULL;\n    cJSON *next = NULL;\n    cJSON *newchild = NULL;\n\n    /* Bail on bad ptr */\n    if (!item)\n    {\n        goto fail;\n    }\n    /* Create new item */\n    newitem = cJSON_New_Item(&global_hooks);\n    if (!newitem)\n    {\n        goto fail;\n    }\n    /* Copy over all vars */\n    newitem->type = item->type & (~cJSON_IsReference);\n    newitem->valueint = item->valueint;\n    newitem->valuedouble = item->valuedouble;\n    if (item->valuestring)\n    {\n        newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks);\n        if (!newitem->valuestring)\n        {\n            goto fail;\n        }\n    }\n    if (item->string)\n    {\n        newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks);\n        if (!newitem->string)\n        {\n            goto fail;\n        }\n    }\n    /* If non-recursive, then we're done! */\n    if (!recurse)\n    {\n        return newitem;\n    }\n    /* Walk the ->next chain for the child. */\n    child = item->child;\n    while (child != NULL)\n    {\n        if(depth >= CJSON_CIRCULAR_LIMIT) {\n            goto fail;\n        }\n        newchild = cJSON_Duplicate_rec(child, depth + 1, true); /* Duplicate (with recurse) each item in the ->next chain */\n        if (!newchild)\n        {\n            goto fail;\n        }\n        if (next != NULL)\n        {\n            /* If newitem->child already set, then crosswire ->prev and ->next and move on */\n            next->next = newchild;\n            newchild->prev = next;\n            next = newchild;\n        }\n        else\n        {\n            /* Set newitem->child and move to it */\n            newitem->child = newchild;\n            next = newchild;\n        }\n        child = child->next;\n    }\n    if (newitem && newitem->child)\n    {\n        newitem->child->prev = newchild;\n    }\n\n    return newitem;\n\nfail:\n    if (newitem != NULL)\n    {\n        cJSON_Delete(newitem);\n    }\n\n    return NULL;\n}\n\nstatic void skip_oneline_comment(char **input)\n{\n    *input += static_strlen(\"//\");\n\n    for (; (*input)[0] != '\\0'; ++(*input))\n    {\n        if ((*input)[0] == '\\n') {\n            *input += static_strlen(\"\\n\");\n            return;\n        }\n    }\n}\n\nstatic void skip_multiline_comment(char **input)\n{\n    *input += static_strlen(\"/*\");\n\n    for (; (*input)[0] != '\\0'; ++(*input))\n    {\n        if (((*input)[0] == '*') && ((*input)[1] == '/'))\n        {\n            *input += static_strlen(\"*/\");\n            return;\n        }\n    }\n}\n\nstatic void minify_string(char **input, char **output) {\n    (*output)[0] = (*input)[0];\n    *input += static_strlen(\"\\\"\");\n    *output += static_strlen(\"\\\"\");\n\n\n    for (; (*input)[0] != '\\0'; (void)++(*input), ++(*output)) {\n        (*output)[0] = (*input)[0];\n\n        if ((*input)[0] == '\\\"') {\n            (*output)[0] = '\\\"';\n            *input += static_strlen(\"\\\"\");\n            *output += static_strlen(\"\\\"\");\n            return;\n        } else if (((*input)[0] == '\\\\') && ((*input)[1] == '\\\"')) {\n            (*output)[1] = (*input)[1];\n            *input += static_strlen(\"\\\"\");\n            *output += static_strlen(\"\\\"\");\n        }\n    }\n}\n\nCJSON_PUBLIC(void) cJSON_Minify(char *json)\n{\n    char *into = json;\n\n    if (json == NULL)\n    {\n        return;\n    }\n\n    while (json[0] != '\\0')\n    {\n        switch (json[0])\n        {\n            case ' ':\n            case '\\t':\n            case '\\r':\n            case '\\n':\n                json++;\n                break;\n\n            case '/':\n                if (json[1] == '/')\n                {\n                    skip_oneline_comment(&json);\n                }\n                else if (json[1] == '*')\n                {\n                    skip_multiline_comment(&json);\n                } else {\n                    json++;\n                }\n                break;\n\n            case '\\\"':\n                minify_string(&json, (char**)&into);\n                break;\n\n            default:\n                into[0] = json[0];\n                json++;\n                into++;\n        }\n    }\n\n    /* and null-terminate. */\n    *into = '\\0';\n}\n\nCJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item)\n{\n    if (item == NULL)\n    {\n        return false;\n    }\n\n    return (item->type & 0xFF) == cJSON_Invalid;\n}\n\nCJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item)\n{\n    if (item == NULL)\n    {\n        return false;\n    }\n\n    return (item->type & 0xFF) == cJSON_False;\n}\n\nCJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item)\n{\n    if (item == NULL)\n    {\n        return false;\n    }\n\n    return (item->type & 0xff) == cJSON_True;\n}\n\n\nCJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item)\n{\n    if (item == NULL)\n    {\n        return false;\n    }\n\n    return (item->type & (cJSON_True | cJSON_False)) != 0;\n}\nCJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item)\n{\n    if (item == NULL)\n    {\n        return false;\n    }\n\n    return (item->type & 0xFF) == cJSON_NULL;\n}\n\nCJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item)\n{\n    if (item == NULL)\n    {\n        return false;\n    }\n\n    return (item->type & 0xFF) == cJSON_Number;\n}\n\nCJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item)\n{\n    if (item == NULL)\n    {\n        return false;\n    }\n\n    return (item->type & 0xFF) == cJSON_String;\n}\n\nCJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item)\n{\n    if (item == NULL)\n    {\n        return false;\n    }\n\n    return (item->type & 0xFF) == cJSON_Array;\n}\n\nCJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item)\n{\n    if (item == NULL)\n    {\n        return false;\n    }\n\n    return (item->type & 0xFF) == cJSON_Object;\n}\n\nCJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item)\n{\n    if (item == NULL)\n    {\n        return false;\n    }\n\n    return (item->type & 0xFF) == cJSON_Raw;\n}\n\nCJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive)\n{\n    if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF)))\n    {\n        return false;\n    }\n\n    /* check if type is valid */\n    switch (a->type & 0xFF)\n    {\n        case cJSON_False:\n        case cJSON_True:\n        case cJSON_NULL:\n        case cJSON_Number:\n        case cJSON_String:\n        case cJSON_Raw:\n        case cJSON_Array:\n        case cJSON_Object:\n            break;\n\n        default:\n            return false;\n    }\n\n    /* identical objects are equal */\n    if (a == b)\n    {\n        return true;\n    }\n\n    switch (a->type & 0xFF)\n    {\n        /* in these cases and equal type is enough */\n        case cJSON_False:\n        case cJSON_True:\n        case cJSON_NULL:\n            return true;\n\n        case cJSON_Number:\n            if (compare_double(a->valuedouble, b->valuedouble))\n            {\n                return true;\n            }\n            return false;\n\n        case cJSON_String:\n        case cJSON_Raw:\n            if ((a->valuestring == NULL) || (b->valuestring == NULL))\n            {\n                return false;\n            }\n            if (strcmp(a->valuestring, b->valuestring) == 0)\n            {\n                return true;\n            }\n\n            return false;\n\n        case cJSON_Array:\n        {\n            cJSON *a_element = a->child;\n            cJSON *b_element = b->child;\n\n            for (; (a_element != NULL) && (b_element != NULL);)\n            {\n                if (!cJSON_Compare(a_element, b_element, case_sensitive))\n                {\n                    return false;\n                }\n\n                a_element = a_element->next;\n                b_element = b_element->next;\n            }\n\n            /* one of the arrays is longer than the other */\n            if (a_element != b_element) {\n                return false;\n            }\n\n            return true;\n        }\n\n        case cJSON_Object:\n        {\n            cJSON *a_element = NULL;\n            cJSON *b_element = NULL;\n            cJSON_ArrayForEach(a_element, a)\n            {\n                /* TODO This has O(n^2) runtime, which is horrible! */\n                b_element = get_object_item(b, a_element->string, case_sensitive);\n                if (b_element == NULL)\n                {\n                    return false;\n                }\n\n                if (!cJSON_Compare(a_element, b_element, case_sensitive))\n                {\n                    return false;\n                }\n            }\n\n            /* doing this twice, once on a and b to prevent true comparison if a subset of b\n             * TODO: Do this the proper way, this is just a fix for now */\n            cJSON_ArrayForEach(b_element, b)\n            {\n                a_element = get_object_item(a, b_element->string, case_sensitive);\n                if (a_element == NULL)\n                {\n                    return false;\n                }\n\n                if (!cJSON_Compare(b_element, a_element, case_sensitive))\n                {\n                    return false;\n                }\n            }\n\n            return true;\n        }\n\n        default:\n            return false;\n    }\n}\n\nCJSON_PUBLIC(void *) cJSON_malloc(size_t size)\n{\n    return global_hooks.allocate(size);\n}\n\nCJSON_PUBLIC(void) cJSON_free(void *object)\n{\n    global_hooks.deallocate(object);\n    object = NULL;\n}"
  },
  {
    "path": "src/json/cJSON.h",
    "content": "/*\n  Copyright (c) 2009-2017 Dave Gamble and cJSON contributors\n\n  Permission is hereby granted, free of charge, to any person obtaining a copy\n  of this software and associated documentation files (the \"Software\"), to deal\n  in the Software without restriction, including without limitation the rights\n  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n  copies of the Software, and to permit persons to whom the Software is\n  furnished to do so, subject to the following conditions:\n\n  The above copyright notice and this permission notice shall be included in\n  all copies or substantial portions of the Software.\n\n  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n  THE SOFTWARE.\n*/\n\n#ifndef cJSON__h\n#define cJSON__h\n\n#ifdef __cplusplus\nextern \"C\"\n{\n#endif\n\n#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32))\n#define __WINDOWS__\n#endif\n\n#ifdef __WINDOWS__\n\n/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention.  For windows you have 3 define options:\n\nCJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols\nCJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default)\nCJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol\n\nFor *nix builds that support visibility attribute, you can define similar behavior by\n\nsetting default visibility to hidden by adding\n-fvisibility=hidden (for gcc)\nor\n-xldscope=hidden (for sun cc)\nto CFLAGS\n\nthen using the CJSON_API_VISIBILITY flag to \"export\" the same symbols the way CJSON_EXPORT_SYMBOLS does\n\n*/\n\n#define CJSON_CDECL __cdecl\n#define CJSON_STDCALL __stdcall\n\n/* export symbols by default, this is necessary for copy pasting the C and header file */\n#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS)\n#define CJSON_EXPORT_SYMBOLS\n#endif\n\n#if defined(CJSON_HIDE_SYMBOLS)\n#define CJSON_PUBLIC(type)   type CJSON_STDCALL\n#elif defined(CJSON_EXPORT_SYMBOLS)\n#define CJSON_PUBLIC(type)   __declspec(dllexport) type CJSON_STDCALL\n#elif defined(CJSON_IMPORT_SYMBOLS)\n#define CJSON_PUBLIC(type)   __declspec(dllimport) type CJSON_STDCALL\n#endif\n#else /* !__WINDOWS__ */\n#define CJSON_CDECL\n#define CJSON_STDCALL\n\n#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY)\n#define CJSON_PUBLIC(type)   __attribute__((visibility(\"default\"))) type\n#else\n#define CJSON_PUBLIC(type) type\n#endif\n#endif\n\n/* project version */\n#define CJSON_VERSION_MAJOR 1\n#define CJSON_VERSION_MINOR 7\n#define CJSON_VERSION_PATCH 19\n\n#include <stddef.h>\n\n/* cJSON Types: */\n#define cJSON_Invalid (0)\n#define cJSON_False  (1 << 0)\n#define cJSON_True   (1 << 1)\n#define cJSON_NULL   (1 << 2)\n#define cJSON_Number (1 << 3)\n#define cJSON_String (1 << 4)\n#define cJSON_Array  (1 << 5)\n#define cJSON_Object (1 << 6)\n#define cJSON_Raw    (1 << 7) /* raw json */\n\n#define cJSON_IsReference 256\n#define cJSON_StringIsConst 512\n\n/* The cJSON structure: */\ntypedef struct cJSON\n{\n    /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */\n    struct cJSON *next;\n    struct cJSON *prev;\n    /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */\n    struct cJSON *child;\n\n    /* The type of the item, as above. */\n    int type;\n\n    /* The item's string, if type==cJSON_String  and type == cJSON_Raw */\n    char *valuestring;\n    /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */\n    int valueint;\n    /* The item's number, if type==cJSON_Number */\n    double valuedouble;\n\n    /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */\n    char *string;\n} cJSON;\n\ntypedef struct cJSON_Hooks\n{\n      /* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */\n      void *(CJSON_CDECL *malloc_fn)(size_t sz);\n      void (CJSON_CDECL *free_fn)(void *ptr);\n} cJSON_Hooks;\n\ntypedef int cJSON_bool;\n\n/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them.\n * This is to prevent stack overflows. */\n#ifndef CJSON_NESTING_LIMIT\n#define CJSON_NESTING_LIMIT 1000\n#endif\n\n/* Limits the length of circular references can be before cJSON rejects to parse them.\n * This is to prevent stack overflows. */\n#ifndef CJSON_CIRCULAR_LIMIT\n#define CJSON_CIRCULAR_LIMIT 10000\n#endif\n\n/* returns the version of cJSON as a string */\nCJSON_PUBLIC(const char*) cJSON_Version(void);\n\n/* Supply malloc, realloc and free functions to cJSON */\nCJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks);\n\n/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */\n/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */\nCJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value);\nCJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length);\n/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */\n/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */\nCJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated);\nCJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated);\n\n/* Render a cJSON entity to text for transfer/storage. */\nCJSON_PUBLIC(char *) cJSON_Print(const cJSON *item);\n/* Render a cJSON entity to text for transfer/storage without any formatting. */\nCJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item);\n/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */\nCJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt);\n/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */\n/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */\nCJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format);\n/* Delete a cJSON entity and all subentities. */\nCJSON_PUBLIC(void) cJSON_Delete(cJSON *item);\n\n/* Returns the number of items in an array (or object). */\nCJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array);\n/* Retrieve item number \"index\" from array \"array\". Returns NULL if unsuccessful. */\nCJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);\n/* Get item \"string\" from object. Case insensitive. */\nCJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);\nCJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string);\nCJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string);\n/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */\nCJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void);\n\n/* Check item type and return its value */\nCJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item);\nCJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item);\n\n/* These functions check the type of an item */\nCJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item);\nCJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item);\nCJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item);\nCJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item);\nCJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item);\nCJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item);\nCJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item);\nCJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item);\nCJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item);\nCJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item);\n\n/* These calls create a cJSON item of the appropriate type. */\nCJSON_PUBLIC(cJSON *) cJSON_CreateNull(void);\nCJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void);\nCJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void);\nCJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean);\nCJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num);\nCJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string);\n/* raw json */\nCJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw);\nCJSON_PUBLIC(cJSON *) cJSON_CreateArray(void);\nCJSON_PUBLIC(cJSON *) cJSON_CreateObject(void);\n\n/* Create a string where valuestring references a string so\n * it will not be freed by cJSON_Delete */\nCJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string);\n/* Create an object/array that only references it's elements so\n * they will not be freed by cJSON_Delete */\nCJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child);\nCJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child);\n\n/* These utilities create an Array of count items.\n * The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/\nCJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count);\nCJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count);\nCJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count);\nCJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count);\n\n/* Append item to the specified array/object. */\nCJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item);\nCJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);\n/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object.\n * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before\n * writing to `item->string` */\nCJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item);\n/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */\nCJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);\nCJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item);\n\n/* Remove/Detach items from Arrays/Objects. */\nCJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item);\nCJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which);\nCJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which);\nCJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string);\nCJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string);\nCJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string);\nCJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string);\n\n/* Update array items. */\nCJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */\nCJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement);\nCJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem);\nCJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem);\nCJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem);\n\n/* Duplicate a cJSON item */\nCJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse);\n/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will\n * need to be released. With recurse!=0, it will duplicate any children connected to the item.\n * The item->next and ->prev pointers are always zero on return from Duplicate. */\n/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal.\n * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */\nCJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive);\n\n/* Minify a strings, remove blank characters(such as ' ', '\\t', '\\r', '\\n') from strings.\n * The input pointer json cannot point to a read-only address area, such as a string constant, \n * but should point to a readable and writable address area. */\nCJSON_PUBLIC(void) cJSON_Minify(char *json);\n\n/* Helper functions for creating and adding items to an object at the same time.\n * They return the added item or NULL on failure. */\nCJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name);\nCJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name);\nCJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name);\nCJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);\nCJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);\nCJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);\nCJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);\nCJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name);\nCJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name);\n\n/* When assigning an integer value, it needs to be propagated to valuedouble too. */\n#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number))\n/* helper for the cJSON_SetNumberValue macro */\nCJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number);\n#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number))\n/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */\nCJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring);\n\n/* If the object is not a boolean type this does nothing and returns cJSON_Invalid else it returns the new type*/\n#define cJSON_SetBoolValue(object, boolValue) ( \\\n    (object != NULL && ((object)->type & (cJSON_False|cJSON_True))) ? \\\n    (object)->type=((object)->type &(~(cJSON_False|cJSON_True)))|((boolValue)?cJSON_True:cJSON_False) : \\\n    cJSON_Invalid\\\n)\n\n/* Macro for iterating over an array or object */\n#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next)\n\n/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */\nCJSON_PUBLIC(void *) cJSON_malloc(size_t size);\nCJSON_PUBLIC(void) cJSON_free(void *object);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif"
  },
  {
    "path": "src/mimetable/mimetable.cpp",
    "content": "#include \"mimetable.h\"\n#include <pgmspace.h>\n\nnamespace mimetype {\n\n// MIME type lookup table stored in PROGMEM\nstatic const struct {\n  const char ext[16];\n  const char type[32];\n} mimeTable[] PROGMEM = {\n  {\".html\", \"text/html\"},\n  {\".htm\", \"text/html\"},\n  {\".css\", \"text/css\"},\n  {\".txt\", \"text/plain\"},\n  {\".js\", \"application/javascript\"},\n  {\".mjs\", \"text/javascript\"},\n  {\".json\", \"application/json\"},\n  {\".png\", \"image/png\"},\n  {\".gif\", \"image/gif\"},\n  {\".jpg\", \"image/jpeg\"},\n  {\".ico\", \"image/x-icon\"},\n  {\".svg\", \"image/svg+xml\"},\n  {\".svg.gz\", \"image/svg+xml\"},\n  {\".ttf\", \"application/x-font-ttf\"},\n  {\".otf\", \"application/x-font-opentype\"},\n  {\".woff\", \"application/font-woff\"},\n  {\".woff2\", \"application/font-woff2\"},\n  {\".eot\", \"application/vnd.ms-fontobject\"},\n  {\".sfnt\", \"application/font-sfnt\"},\n  {\".xml\", \"text/xml\"},\n  {\".pdf\", \"application/pdf\"},\n  {\".zip\", \"application/zip\"},\n  {\".gz\", \"application/x-gzip\"},\n  {\".appcache\", \"text/cache-manifest\"},\n};\n\nString getContentType(const String &path) {\n  char buff[32];\n  \n  // Check all entries for extension match\n  for (size_t i = 0; i < sizeof(mimeTable) / sizeof(mimeTable[0]); i++) {\n    strcpy_P(buff, mimeTable[i].ext);\n    if (path.endsWith(buff)) {\n      strcpy_P(buff, mimeTable[i].type);\n      return String(buff);\n    }\n  }\n  \n  // Default to octet-stream if no match\n  return String(F(\"application/octet-stream\"));\n}\n\n}  // namespace mimetype\n"
  },
  {
    "path": "src/mimetable/mimetable.h",
    "content": "#ifndef __MIMETABLE_H__\n#define __MIMETABLE_H__\n#include <Arduino.h>\n\nnamespace mimetype {\n\n// Minimal MIME type helper - delegates to ESP32 WebServer core when possible\n// Falls back to local implementation if WebServer::getContentType is unavailable\nString getContentType(const String &path);\n\n}  // namespace mimetype\n\n#endif\n"
  },
  {
    "path": "src/websocket/SocketIOclient.cpp",
    "content": "/*\n * SocketIOclient.cpp\n *\n *  Created on: May 12, 2018\n *      Author: links\n */\n\n#include \"WebSockets.h\"\n#include \"WebSocketsClient.h\"\n#include \"SocketIOclient.h\"\n\nSocketIOclient::SocketIOclient() {\n}\n\nSocketIOclient::~SocketIOclient() {\n}\n\nvoid SocketIOclient::begin(const char * host, uint16_t port, const char * url, const char * protocol, uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount) {\n    WebSocketsClient::beginSocketIO(host, port, url, protocol);\n    WebSocketsClient::enableHeartbeat(pingInterval, pongTimeout, disconnectTimeoutCount);\n    initClient();\n}\n\nvoid SocketIOclient::begin(String host, uint16_t port, String url, String protocol, uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount) {\n    WebSocketsClient::beginSocketIO(host, port, url, protocol);\n    WebSocketsClient::enableHeartbeat(pingInterval, pongTimeout, disconnectTimeoutCount);\n    initClient();\n}\n#if defined(HAS_SSL)\nvoid SocketIOclient::beginSSL(const char * host, uint16_t port, const char * url, const char * protocol, uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount) {\n    WebSocketsClient::beginSocketIOSSL(host, port, url, protocol);\n    WebSocketsClient::enableHeartbeat(pingInterval, pongTimeout, disconnectTimeoutCount);\n    initClient();\n}\n\nvoid SocketIOclient::beginSSL(String host, uint16_t port, String url, String protocol, uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount) {\n    WebSocketsClient::beginSocketIOSSL(host, port, url, protocol);\n    WebSocketsClient::enableHeartbeat(pingInterval, pongTimeout, disconnectTimeoutCount);\n    initClient();\n}\n#if defined(SSL_BARESSL)\nvoid SocketIOclient::beginSSLWithCA(const char * host, uint16_t port, const char * url, const char * CA_cert, const char * protocol, uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount) {\n    WebSocketsClient::beginSocketIOSSLWithCA(host, port, url, CA_cert, protocol);\n    WebSocketsClient::enableHeartbeat(pingInterval, pongTimeout, disconnectTimeoutCount);\n    initClient();\n}\n\nvoid SocketIOclient::beginSSLWithCA(const char * host, uint16_t port, const char * url, BearSSL::X509List * CA_cert, const char * protocol, uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount) {\n    WebSocketsClient::beginSocketIOSSLWithCA(host, port, url, CA_cert, protocol);\n    WebSocketsClient::enableHeartbeat(pingInterval, pongTimeout, disconnectTimeoutCount);\n    initClient();\n}\n\nvoid SocketIOclient::setSSLClientCertKey(const char * clientCert, const char * clientPrivateKey) {\n    WebSocketsClient::setSSLClientCertKey(clientCert, clientPrivateKey);\n}\n\nvoid SocketIOclient::setSSLClientCertKey(BearSSL::X509List * clientCert, BearSSL::PrivateKey * clientPrivateKey) {\n    WebSocketsClient::setSSLClientCertKey(clientCert, clientPrivateKey);\n}\n\n#endif\n#endif\n\nvoid SocketIOclient::configureEIOping(bool disableHeartbeat) {\n    _disableHeartbeat = disableHeartbeat;\n}\n\nvoid SocketIOclient::initClient(void) {\n    if(_client.cUrl.indexOf(\"EIO=4\") != -1) {\n        log_debug(\"[wsIOc] found EIO=4 disable EIO ping on client\\n\");\n        configureEIOping(true);\n    }\n}\n\n/**\n * set callback function\n * @param cbEvent SocketIOclientEvent\n */\nvoid SocketIOclient::onEvent(SocketIOclientEvent cbEvent) {\n    _cbEvent = cbEvent;\n}\n\nbool SocketIOclient::isConnected(void) {\n    return WebSocketsClient::isConnected();\n}\n\nvoid SocketIOclient::setExtraHeaders(const char * extraHeaders) {\n    return WebSocketsClient::setExtraHeaders(extraHeaders);\n}\n\nvoid SocketIOclient::setReconnectInterval(unsigned long time) {\n    return WebSocketsClient::setReconnectInterval(time);\n}\n\nvoid SocketIOclient::disconnect(void) {\n    WebSocketsClient::disconnect();\n}\n\n/**\n * send text data to client\n * @param num uint8_t client id\n * @param type socketIOmessageType_t\n * @param payload uint8_t *\n * @param length size_t\n * @param headerToPayload bool (see sendFrame for more details)\n * @return true if ok\n */\nbool SocketIOclient::send(socketIOmessageType_t type, uint8_t * payload, size_t length, bool headerToPayload) {\n    bool ret = false;\n    if(length == 0) {\n        length = strlen((const char *)payload);\n    }\n    if(clientIsConnected(&_client) && _client.status == WSC_CONNECTED) {\n        if(!headerToPayload) {\n            // webSocket Header\n            ret = WebSocketsClient::sendFrameHeader(&_client, WSop_text, length + 2, true);\n            // Engine.IO / Socket.IO Header\n            if(ret) {\n                uint8_t buf[3] = { eIOtype_MESSAGE, type, 0x00 };\n                ret            = WebSocketsClient::write(&_client, buf, 2);\n            }\n            if(ret && payload && length > 0) {\n                ret = WebSocketsClient::write(&_client, payload, length);\n            }\n            return ret;\n        } else {\n            // TODO implement\n        }\n    }\n    return false;\n}\n\nbool SocketIOclient::send(socketIOmessageType_t type, const uint8_t * payload, size_t length) {\n    return send(type, (uint8_t *)payload, length);\n}\n\nbool SocketIOclient::send(socketIOmessageType_t type, char * payload, size_t length, bool headerToPayload) {\n    return send(type, (uint8_t *)payload, length, headerToPayload);\n}\n\nbool SocketIOclient::send(socketIOmessageType_t type, const char * payload, size_t length) {\n    return send(type, (uint8_t *)payload, length);\n}\n\nbool SocketIOclient::send(socketIOmessageType_t type, String & payload) {\n    return send(type, (uint8_t *)payload.c_str(), payload.length());\n}\n\n/**\n * send text data to client\n * @param num uint8_t client id\n * @param payload uint8_t *\n * @param length size_t\n * @param headerToPayload bool  (see sendFrame for more details)\n * @return true if ok\n */\nbool SocketIOclient::sendEVENT(uint8_t * payload, size_t length, bool headerToPayload) {\n    return send(sIOtype_EVENT, payload, length, headerToPayload);\n}\n\nbool SocketIOclient::sendEVENT(const uint8_t * payload, size_t length) {\n    return sendEVENT((uint8_t *)payload, length);\n}\n\nbool SocketIOclient::sendEVENT(char * payload, size_t length, bool headerToPayload) {\n    return sendEVENT((uint8_t *)payload, length, headerToPayload);\n}\n\nbool SocketIOclient::sendEVENT(const char * payload, size_t length) {\n    return sendEVENT((uint8_t *)payload, length);\n}\n\nbool SocketIOclient::sendEVENT(String & payload) {\n    return sendEVENT((uint8_t *)payload.c_str(), payload.length());\n}\n\nvoid SocketIOclient::loop(void) {\n    WebSocketsClient::loop();\n    unsigned long t = millis();\n    if(!_disableHeartbeat && (t - _lastHeartbeat) > EIO_HEARTBEAT_INTERVAL) {\n        _lastHeartbeat = t;\n        log_debug(\"[wsIOc] send ping\\n\");\n        WebSocketsClient::sendTXT(eIOtype_PING);\n    }\n}\n\nvoid SocketIOclient::handleCbEvent(WStype_t type, uint8_t * payload, size_t length) {\n    switch(type) {\n        case WStype_DISCONNECTED:\n            runIOCbEvent(sIOtype_DISCONNECT, NULL, 0);\n            log_debug(\"[wsIOc] Disconnected!\\n\");\n            break;\n        case WStype_CONNECTED: {\n            log_debug(\"[wsIOc] Connected to url: %s\\n\", payload);\n            // send message to server when Connected\n            // Engine.io upgrade confirmation message (required)\n            WebSocketsClient::sendTXT(\"2probe\");\n            WebSocketsClient::sendTXT(eIOtype_UPGRADE);\n            runIOCbEvent(sIOtype_CONNECT, payload, length);\n        } break;\n        case WStype_TEXT: {\n            if(length < 1) {\n                break;\n            }\n\n            engineIOmessageType_t eType = (engineIOmessageType_t)payload[0];\n            switch(eType) {\n                case eIOtype_PING:\n                    payload[0] = eIOtype_PONG;\n                    log_debug(\"[wsIOc] get ping send pong (%s)\\n\", payload);\n                    WebSocketsClient::sendTXT(payload, length, false);\n                    break;\n                case eIOtype_PONG:\n                    log_debug(\"[wsIOc] get pong\\n\");\n                    break;\n                case eIOtype_MESSAGE: {\n                    if(length < 2) {\n                        break;\n                    }\n                    socketIOmessageType_t ioType = (socketIOmessageType_t)payload[1];\n                    uint8_t * data               = &payload[2];\n                    size_t lData                 = length - 2;\n                    switch(ioType) {\n                        case sIOtype_EVENT:\n                            log_debug(\"[wsIOc] get event (%d): %s\\n\", lData, data);\n                            break;\n                        case sIOtype_CONNECT:\n                            log_debug(\"[wsIOc] connected (%d): %s\\n\", lData, data);\n                            return;\n                        case sIOtype_DISCONNECT:\n                        case sIOtype_ACK:\n                        case sIOtype_ERROR:\n                        case sIOtype_BINARY_EVENT:\n                        case sIOtype_BINARY_ACK:\n                        default:\n                            log_debug(\"[wsIOc] Socket.IO Message Type %c (%02X) is not implemented\\n\", ioType, ioType);\n                            log_debug(\"[wsIOc] get text: %s\\n\", payload);\n                            break;\n                    }\n\n                    runIOCbEvent(ioType, data, lData);\n                } break;\n                case eIOtype_OPEN:\n                case eIOtype_CLOSE:\n                case eIOtype_UPGRADE:\n                case eIOtype_NOOP:\n                default:\n                    log_debug(\"[wsIOc] Engine.IO Message Type %c (%02X) is not implemented\\n\", eType, eType);\n                    log_debug(\"[wsIOc] get text: %s\\n\", payload);\n                    break;\n            }\n        } break;\n        case WStype_ERROR:\n        case WStype_BIN:\n        case WStype_FRAGMENT_TEXT_START:\n        case WStype_FRAGMENT_BIN_START:\n        case WStype_FRAGMENT:\n        case WStype_FRAGMENT_FIN:\n        case WStype_PING:\n        case WStype_PONG:\n            break;\n    }\n}\n"
  },
  {
    "path": "src/websocket/SocketIOclient.h",
    "content": "/**\n * SocketIOclient.h\n *\n *  Created on: May 12, 2018\n *      Author: links\n */\n\n#ifndef ESP_SOCKETIOCLIENT_H_\n#define ESP_SOCKETIOCLIENT_H_\n\n#include \"WebSockets.h\"\n#include \"WebSocketsClient.h\"\n\n#define EIO_HEARTBEAT_INTERVAL 20000\n\n#define EIO_MAX_HEADER_SIZE (WEBSOCKETS_MAX_HEADER_SIZE + 1)\n#define SIO_MAX_HEADER_SIZE (EIO_MAX_HEADER_SIZE + 1)\n\ntypedef enum {\n    eIOtype_OPEN    = '0',    ///< Sent from the server when a new transport is opened (recheck)\n    eIOtype_CLOSE   = '1',    ///< Request the close of this transport but does not shutdown the connection itself.\n    eIOtype_PING    = '2',    ///< Sent by the client. Server should answer with a pong packet containing the same data\n    eIOtype_PONG    = '3',    ///< Sent by the server to respond to ping packets.\n    eIOtype_MESSAGE = '4',    ///< actual message, client and server should call their callbacks with the data\n    eIOtype_UPGRADE = '5',    ///< Before engine.io switches a transport, it tests, if server and client can communicate over this transport. If this test succeed, the client sends an upgrade packets which requests the server to flush its cache on the old transport and switch to the new transport.\n    eIOtype_NOOP    = '6',    ///< A noop packet. Used primarily to force a poll cycle when an incoming websocket connection is received.\n} engineIOmessageType_t;\n\ntypedef enum {\n    sIOtype_CONNECT      = '0',\n    sIOtype_DISCONNECT   = '1',\n    sIOtype_EVENT        = '2',\n    sIOtype_ACK          = '3',\n    sIOtype_ERROR        = '4',\n    sIOtype_BINARY_EVENT = '5',\n    sIOtype_BINARY_ACK   = '6',\n} socketIOmessageType_t;\n\nclass SocketIOclient : protected WebSocketsClient {\n  public:\n#ifdef __AVR__\n    typedef void (*SocketIOclientEvent)(socketIOmessageType_t type, uint8_t * payload, size_t length);\n#else\n    typedef std::function<void(socketIOmessageType_t type, uint8_t * payload, size_t length)> SocketIOclientEvent;\n#endif\n\n    SocketIOclient(void);\n    virtual ~SocketIOclient(void);\n\n    void begin(const char * host, uint16_t port, const char * url = \"/socket.io/?EIO=3\", const char * protocol = \"arduino\", uint32_t pingInterval = 60 * 1000, uint32_t pongTimeout = 90 * 1000, uint8_t disconnectTimeoutCount = 5);\n    void begin(String host, uint16_t port, String url = \"/socket.io/?EIO=3\", String protocol = \"arduino\", uint32_t pingInterval = 60 * 1000, uint32_t pongTimeout = 90 * 1000, uint8_t disconnectTimeoutCount = 5);\n\n#ifdef HAS_SSL\n    void beginSSL(const char * host, uint16_t port, const char * url = \"/socket.io/?EIO=3\", const char * protocol = \"arduino\", uint32_t pingInterval = 60 * 1000, uint32_t pongTimeout = 90 * 1000, uint8_t disconnectTimeoutCount = 5);\n    void beginSSL(String host, uint16_t port, String url = \"/socket.io/?EIO=3\", String protocol = \"arduino\", uint32_t pingInterval = 60 * 1000, uint32_t pongTimeout = 90 * 1000, uint8_t disconnectTimeoutCount = 5);\n#ifndef SSL_AXTLS\n    void beginSSLWithCA(const char * host, uint16_t port, const char * url = \"/socket.io/?EIO=3\", const char * CA_cert = NULL, const char * protocol = \"arduino\", uint32_t pingInterval = 60 * 1000, uint32_t pongTimeout = 90 * 1000, uint8_t disconnectTimeoutCount = 5);\n    void beginSSLWithCA(const char * host, uint16_t port, const char * url = \"/socket.io/?EIO=3\", BearSSL::X509List * CA_cert = NULL, const char * protocol = \"arduino\", uint32_t pingInterval = 60 * 1000, uint32_t pongTimeout = 90 * 1000, uint8_t disconnectTimeoutCount = 5);\n    void setSSLClientCertKey(const char * clientCert = NULL, const char * clientPrivateKey = NULL);\n    void setSSLClientCertKey(BearSSL::X509List * clientCert = NULL, BearSSL::PrivateKey * clientPrivateKey = NULL);\n#endif\n#endif\n    bool isConnected(void);\n\n    void onEvent(SocketIOclientEvent cbEvent);\n    void disconnect(void);\n\n    bool sendEVENT(uint8_t * payload, size_t length = 0, bool headerToPayload = false);\n    bool sendEVENT(const uint8_t * payload, size_t length = 0);\n    bool sendEVENT(char * payload, size_t length = 0, bool headerToPayload = false);\n    bool sendEVENT(const char * payload, size_t length = 0);\n    bool sendEVENT(String & payload);\n\n    bool send(socketIOmessageType_t type, uint8_t * payload, size_t length = 0, bool headerToPayload = false);\n    bool send(socketIOmessageType_t type, const uint8_t * payload, size_t length = 0);\n    bool send(socketIOmessageType_t type, char * payload, size_t length = 0, bool headerToPayload = false);\n    bool send(socketIOmessageType_t type, const char * payload, size_t length = 0);\n    bool send(socketIOmessageType_t type, String & payload);\n\n    void setExtraHeaders(const char * extraHeaders = NULL);\n    void setReconnectInterval(unsigned long time);\n\n    void loop(void);\n\n    void configureEIOping(bool disableHeartbeat = false);\n\n  protected:\n    bool _disableHeartbeat  = false;\n    uint64_t _lastHeartbeat = 0;\n    SocketIOclientEvent _cbEvent;\n    virtual void runIOCbEvent(socketIOmessageType_t type, uint8_t * payload, size_t length) {\n        if(_cbEvent) {\n            _cbEvent(type, payload, length);\n        }\n    }\n\n    void initClient(void);\n\n    // Handeling events from websocket layer\n    virtual void runCbEvent(WStype_t type, uint8_t * payload, size_t length) {\n        handleCbEvent(type, payload, length);\n    }\n    void handleCbEvent(WStype_t type, uint8_t * payload, size_t length);\n};\n\n#endif /* SOCKETIOCLIENT_H_ */\n"
  },
  {
    "path": "src/websocket/WebSockets.cpp",
    "content": "/**\n * @file WebSockets.cpp\n * @date 20.05.2015\n * @author Markus Sattler\n *\n * Copyright (c) 2015 Markus Sattler. All rights reserved.\n * This file is part of the WebSockets for Arduino.\n *\n * This library is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 2.1 of the License, or (at your option) any later version.\n *\n * This library is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public\n * License along with this library; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n\n#include \"WebSockets.h\"\n\n#ifdef ESP8266\n#include <core_esp8266_features.h>\n#endif\n\nextern \"C\" {\n#ifdef CORE_HAS_LIBB64\n#include <libb64/cencode.h>\n#else\n#include \"libb64/cencode_inc.h\"\n#endif\n}\n\n#ifdef ESP8266\n#include <Hash.h>\n#elif defined(ESP32)\n#include <esp_system.h>\n\n#if ESP_IDF_VERSION_MAJOR >= 4\n#if(ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(1, 0, 6))\n#include \"sha/sha_parallel_engine.h\"\n#else\n#include <esp32/sha.h>\n#endif\n#else\n#include <hwcrypto/sha.h>\n#endif\n\n#else\n\nextern \"C\" {\n#include \"libsha1/libsha1.h\"\n}\n\n#endif\n\n/**\n *\n * @param client WSclient_t *  ptr to the client struct\n * @param code uint16_t see RFC\n * @param reason ptr to the disconnect reason message\n * @param reasonLen length of the disconnect reason message\n */\nvoid WebSockets::clientDisconnect(WSclient_t * client, uint16_t code, char * reason, size_t reasonLen) {\n    log_debug(\"[WS][%d][handleWebsocket] clientDisconnect code: %u\", client->num, code);\n    if(client->status == WSC_CONNECTED && code) {\n        if(reason) {\n            sendFrame(client, WSop_close, (uint8_t *)reason, reasonLen);\n        } else {\n            uint8_t buffer[2];\n            buffer[0] = ((code >> 8) & 0xFF);\n            buffer[1] = (code & 0xFF);\n            sendFrame(client, WSop_close, &buffer[0], 2);\n        }\n    }\n    clientDisconnect(client);\n}\n\n/**\n *\n * @param buf uint8_t *         ptr to the buffer for writing\n * @param opcode WSopcode_t\n * @param length size_t         length of the payload\n * @param mask bool             add dummy mask to the frame (needed for web browser)\n * @param maskkey uint8_t[4]    key used for payload\n * @param fin bool              can be used to send data in more then one frame (set fin on the last frame)\n */\nuint8_t WebSockets::createHeader(uint8_t * headerPtr, WSopcode_t opcode, size_t length, bool mask, uint8_t maskKey[4], bool fin) {\n    uint8_t headerSize;\n    // calculate header Size\n    if(length < 126) {\n        headerSize = 2;\n    } else if(length < 0xFFFF) {\n        headerSize = 4;\n    } else {\n        headerSize = 10;\n    }\n\n    if(mask) {\n        headerSize += 4;\n    }\n\n    // create header\n\n    // byte 0\n    *headerPtr = 0x00;\n    if(fin) {\n        *headerPtr |= bit(7);    ///< set Fin\n    }\n    *headerPtr |= opcode;    ///< set opcode\n    headerPtr++;\n\n    // byte 1\n    *headerPtr = 0x00;\n    if(mask) {\n        *headerPtr |= bit(7);    ///< set mask\n    }\n\n    if(length < 126) {\n        *headerPtr |= length;\n        headerPtr++;\n    } else if(length < 0xFFFF) {\n        *headerPtr |= 126;\n        headerPtr++;\n        *headerPtr = ((length >> 8) & 0xFF);\n        headerPtr++;\n        *headerPtr = (length & 0xFF);\n        headerPtr++;\n    } else {\n        // Normally we never get here (to less memory)\n        *headerPtr |= 127;\n        headerPtr++;\n        *headerPtr = 0x00;\n        headerPtr++;\n        *headerPtr = 0x00;\n        headerPtr++;\n        *headerPtr = 0x00;\n        headerPtr++;\n        *headerPtr = 0x00;\n        headerPtr++;\n        *headerPtr = ((length >> 24) & 0xFF);\n        headerPtr++;\n        *headerPtr = ((length >> 16) & 0xFF);\n        headerPtr++;\n        *headerPtr = ((length >> 8) & 0xFF);\n        headerPtr++;\n        *headerPtr = (length & 0xFF);\n        headerPtr++;\n    }\n\n    if(mask) {\n        *headerPtr = maskKey[0];\n        headerPtr++;\n        *headerPtr = maskKey[1];\n        headerPtr++;\n        *headerPtr = maskKey[2];\n        headerPtr++;\n        *headerPtr = maskKey[3];\n        headerPtr++;\n    }\n    return headerSize;\n}\n\n/**\n *\n * @param client WSclient_t *   ptr to the client struct\n * @param opcode WSopcode_t\n * @param length size_t         length of the payload\n * @param fin bool              can be used to send data in more then one frame (set fin on the last frame)\n * @return true if ok\n */\nbool WebSockets::sendFrameHeader(WSclient_t * client, WSopcode_t opcode, size_t length, bool fin) {\n    uint8_t maskKey[4]                         = { 0x00, 0x00, 0x00, 0x00 };\n    uint8_t buffer[WEBSOCKETS_MAX_HEADER_SIZE] = { 0 };\n\n    uint8_t headerSize = createHeader(&buffer[0], opcode, length, client->cIsClient, maskKey, fin);\n\n    if(write(client, &buffer[0], headerSize) != headerSize) {\n        return false;\n    }\n\n    return true;\n}\n\n/**\n *\n * @param client WSclient_t *   ptr to the client struct\n * @param opcode WSopcode_t\n * @param payload uint8_t *     ptr to the payload\n * @param length size_t         length of the payload\n * @param fin bool              can be used to send data in more then one frame (set fin on the last frame)\n * @param headerToPayload bool  set true if the payload has reserved 14 Byte at the beginning to dynamically add the Header (payload neet to be in RAM!)\n * @return true if ok\n */\nbool WebSockets::sendFrame(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin, bool headerToPayload) {\n    if(client->tcp && !client->tcp->connected()) {\n        log_error(\"[WS][%d][sendFrame] not Connected!?\", client->num);\n        return false;\n    }\n\n    if(client->status != WSC_CONNECTED) {\n        log_error(\"[WS][%d][sendFrame] not in WSC_CONNECTED state!?\", client->num);\n        return false;\n    }\n\n    log_debug(\"[WS][%d][sendFrame] ------- send message frame -------\", client->num);\n    log_debug(\"[WS][%d][sendFrame] fin: %u opCode: %u mask: %u length: %u headerToPayload: %u\", client->num, fin, opcode, client->cIsClient, length, headerToPayload);\n\n    if(opcode == WSop_text) {\n        log_debug(\"[WS][%d][sendFrame] text: %s\", client->num, (payload + (headerToPayload ? 14 : 0)));\n    }\n\n    uint8_t maskKey[4]                         = { 0x00, 0x00, 0x00, 0x00 };\n    uint8_t buffer[WEBSOCKETS_MAX_HEADER_SIZE] = { 0 };\n\n    uint8_t headerSize;\n    uint8_t * headerPtr;\n    uint8_t * payloadPtr = payload;\n    bool useInternBuffer = false;\n    bool ret             = true;\n\n    // calculate header Size\n    if(length < 126) {\n        headerSize = 2;\n    } else if(length < 0xFFFF) {\n        headerSize = 4;\n    } else {\n        headerSize = 10;\n    }\n\n    if(client->cIsClient) {\n        headerSize += 4;\n    }\n\n#ifdef WEBSOCKETS_USE_BIG_MEM\n    // only for ESP since AVR has less HEAP\n    // try to send data in one TCP package (only if some free Heap is there)\n    if(!headerToPayload && ((length > 0) && (length < 1400)) && (GET_FREE_HEAP > 6000)) {\n        log_debug(\"[WS][%d][sendFrame] pack to one TCP package...\", client->num);\n        uint8_t * dataPtr = (uint8_t *)malloc(length + WEBSOCKETS_MAX_HEADER_SIZE);\n        if(dataPtr) {\n            memcpy((dataPtr + WEBSOCKETS_MAX_HEADER_SIZE), payload, length);\n            headerToPayload = true;\n            useInternBuffer = true;\n            payloadPtr      = dataPtr;\n        }\n    }\n#endif\n\n    // set Header Pointer\n    if(headerToPayload) {\n        // calculate offset in payload\n        headerPtr = (payloadPtr + (WEBSOCKETS_MAX_HEADER_SIZE - headerSize));\n    } else {\n        headerPtr = &buffer[0];\n    }\n\n    if(client->cIsClient && useInternBuffer) {\n        // if we use a Intern Buffer we can modify the data\n        // by this fact its possible the do the masking\n        for(uint8_t x = 0; x < sizeof(maskKey); x++) {\n            maskKey[x] = random(0xFF);\n        }\n    }\n\n    createHeader(headerPtr, opcode, length, client->cIsClient, maskKey, fin);\n\n    if(client->cIsClient && useInternBuffer) {\n        uint8_t * dataMaskPtr;\n\n        if(headerToPayload) {\n            dataMaskPtr = (payloadPtr + WEBSOCKETS_MAX_HEADER_SIZE);\n        } else {\n            dataMaskPtr = payloadPtr;\n        }\n\n        for(size_t x = 0; x < length; x++) {\n            dataMaskPtr[x] = (dataMaskPtr[x] ^ maskKey[x % 4]);\n        }\n    }\n\n\n    if(headerToPayload) {\n        // header has be added to payload\n        // payload is forced to reserved 14 Byte but we may not need all based on the length and mask settings\n        // offset in payload is calculatetd 14 - headerSize\n        if(write(client, &payloadPtr[(WEBSOCKETS_MAX_HEADER_SIZE - headerSize)], (length + headerSize)) != (length + headerSize)) {\n            ret = false;\n        }\n    } else {\n        // send header\n        if(write(client, &buffer[0], headerSize) != headerSize) {\n            ret = false;\n        }\n\n        if(payloadPtr && length > 0) {\n            // send payload\n            if(write(client, &payloadPtr[0], length) != length) {\n                ret = false;\n            }\n        }\n    }\n\n    log_debug(\"[WS][%d][sendFrame] sending Frame Done.\", client->num);\n\n#ifdef WEBSOCKETS_USE_BIG_MEM\n    if(useInternBuffer && payloadPtr) {\n        free(payloadPtr);\n    }\n#endif\n\n    return ret;\n}\n\n/**\n * callen when HTTP header is done\n * @param client WSclient_t *  ptr to the client struct\n */\nvoid WebSockets::headerDone(WSclient_t * client) {\n    client->status    = WSC_CONNECTED;\n    client->cWsRXsize = 0;\n    log_debug(\"[WS][%d][headerDone] Header Handling Done.\", client->num);\n}\n\n/**\n * handle the WebSocket stream\n * @param client WSclient_t *  ptr to the client struct\n */\nvoid WebSockets::handleWebsocket(WSclient_t * client) {\n    if(client->cWsRXsize == 0) {\n        handleWebsocketCb(client);\n    }\n}\n\n/**\n * wait for\n * @param client\n * @param size\n */\nbool WebSockets::handleWebsocketWaitFor(WSclient_t * client, size_t size) {\n    if(!client->tcp || !client->tcp->connected()) {\n        return false;\n    }\n\n    if(size > WEBSOCKETS_MAX_HEADER_SIZE) {\n        log_error(\"[WS][%d][handleWebsocketWaitFor] size: %d too big!\", client->num, size);\n        return false;\n    }\n\n    if(client->cWsRXsize >= size) {\n        return true;\n    }\n\n    log_debug(\"[WS][%d][handleWebsocketWaitFor] size: %d cWsRXsize: %d\", client->num, size, client->cWsRXsize);\n    readCb(client, &client->cWsHeader[client->cWsRXsize], (size - client->cWsRXsize), std::bind([](WebSockets * server, size_t size, WSclient_t * client, bool ok) {\n        log_debug(\"[WS][%d][handleWebsocketWaitFor][readCb] size: %d ok: %d\", client->num, size, ok);\n        if(ok) {\n            client->cWsRXsize = size;\n            server->handleWebsocketCb(client);\n        } else {\n            log_error(\"[WS][%d][readCb] failed.\", client->num);\n            client->cWsRXsize = 0;\n            // timeout or error\n            server->clientDisconnect(client, 1002);\n        }\n    },\n                                                                                          this, size, std::placeholders::_1, std::placeholders::_2));\n    return false;\n}\n\nvoid WebSockets::handleWebsocketCb(WSclient_t * client) {\n    if(!client->tcp || !client->tcp->connected()) {\n        return;\n    }\n\n    uint8_t * buffer = client->cWsHeader;\n\n    WSMessageHeader_t * header = &client->cWsHeaderDecode;\n    uint8_t * payload          = NULL;\n\n    uint8_t headerLen = 2;\n\n    if(!handleWebsocketWaitFor(client, headerLen)) {\n        return;\n    }\n\n    // split first 2 bytes in the data\n    header->fin    = ((*buffer >> 7) & 0x01);\n    header->rsv1   = ((*buffer >> 6) & 0x01);\n    header->rsv2   = ((*buffer >> 5) & 0x01);\n    header->rsv3   = ((*buffer >> 4) & 0x01);\n    header->opCode = (WSopcode_t)(*buffer & 0x0F);\n    buffer++;\n\n    header->mask       = ((*buffer >> 7) & 0x01);\n    header->payloadLen = (WSopcode_t)(*buffer & 0x7F);\n    buffer++;\n\n    if(header->payloadLen == 126) {\n        headerLen += 2;\n        if(!handleWebsocketWaitFor(client, headerLen)) {\n            return;\n        }\n        header->payloadLen = buffer[0] << 8 | buffer[1];\n        buffer += 2;\n    } else if(header->payloadLen == 127) {\n        headerLen += 8;\n        // read 64bit integer as length\n        if(!handleWebsocketWaitFor(client, headerLen)) {\n            return;\n        }\n\n        if(buffer[0] != 0 || buffer[1] != 0 || buffer[2] != 0 || buffer[3] != 0) {\n            // really too big!\n            header->payloadLen = 0xFFFFFFFF;\n        } else {\n            header->payloadLen = buffer[4] << 24 | buffer[5] << 16 | buffer[6] << 8 | buffer[7];\n        }\n        buffer += 8;\n    }\n\n    log_debug(\"[WS][%d][handleWebsocket] ------- read massage frame -------\", client->num);\n    log_debug(\"[WS][%d][handleWebsocket] fin: %u rsv1: %u rsv2: %u rsv3 %u  opCode: %u\", client->num, header->fin, header->rsv1, header->rsv2, header->rsv3, header->opCode);\n    log_debug(\"[WS][%d][handleWebsocket] mask: %u payloadLen: %u\", client->num, header->mask, header->payloadLen);\n\n    if(header->payloadLen > WEBSOCKETS_MAX_DATA_SIZE) {\n        log_error(\"[WS][%d][handleWebsocket] payload too big! (%u)\", client->num, header->payloadLen);\n        clientDisconnect(client, 1009);\n        return;\n    }\n\n    if(header->mask) {\n        headerLen += 4;\n        if(!handleWebsocketWaitFor(client, headerLen)) {\n            return;\n        }\n        header->maskKey = buffer;\n        buffer += 4;\n    }\n\n    if(header->payloadLen > 0) {\n        // if text data we need one more\n        payload = (uint8_t *)malloc(header->payloadLen + 1);\n\n        if(!payload) {\n            log_error(\"[WS][%d][handleWebsocket] to less memory to handle payload %d!\", client->num, header->payloadLen);\n            clientDisconnect(client, 1011);\n            return;\n        }\n        readCb(client, payload, header->payloadLen, std::bind(&WebSockets::handleWebsocketPayloadCb, this, std::placeholders::_1, std::placeholders::_2, payload));\n    } else {\n        handleWebsocketPayloadCb(client, true, NULL);\n    }\n}\n\nvoid WebSockets::handleWebsocketPayloadCb(WSclient_t * client, bool ok, uint8_t * payload) {\n    WSMessageHeader_t * header = &client->cWsHeaderDecode;\n    if(ok) {\n        if(header->payloadLen > 0) {\n            payload[header->payloadLen] = 0x00;\n\n            if(header->mask) {\n                // decode XOR\n                for(size_t i = 0; i < header->payloadLen; i++) {\n                    payload[i] = (payload[i] ^ header->maskKey[i % 4]);\n                }\n            }\n        }\n\n        switch(header->opCode) {\n            case WSop_text:\n                log_debug(\"[WS][%d][handleWebsocket] text: %s\", client->num, payload);\n                // fallthrough\n            case WSop_binary:\n            case WSop_continuation:\n                messageReceived(client, header->opCode, payload, header->payloadLen, header->fin);\n                break;\n            case WSop_ping:\n                // send pong back\n                log_debug(\"[WS][%d][handleWebsocket] ping received (%s)\", client->num, payload ? (const char *)payload : \"\");\n                sendFrame(client, WSop_pong, payload, header->payloadLen);\n                messageReceived(client, header->opCode, payload, header->payloadLen, header->fin);\n                break;\n            case WSop_pong:\n                log_debug(\"[WS][%d][handleWebsocket] get pong (%s)\", client->num, payload ? (const char *)payload : \"\");\n                client->pongReceived = true;\n                messageReceived(client, header->opCode, payload, header->payloadLen, header->fin);\n                break;\n            case WSop_close: {\n#if LOG_LEVEL > 2\n                uint16_t reasonCode = 0;\n                if(header->payloadLen >= 2 && payload) {\n                    reasonCode = (payload[0] << 8) | payload[1];\n                }\n                log_debug(\"[WS][%d][handleWebsocket] get ask for close. Code: %d\", client->num, reasonCode);\n                if(header->payloadLen > 2 && payload) {\n                    log_debug(\" (%s)\\n\", (payload + 2));\n                } else {\n                    log_debug(\"\\n\");\n                }\n#endif\n                clientDisconnect(client, 1000);\n            } break;\n            default:\n                log_error(\"[WS][%d][handleWebsocket] got unknown opcode: %d\", client->num, header->opCode);\n                clientDisconnect(client, 1002);\n                break;\n        }\n\n        if(payload) {\n            free(payload);\n        }\n\n        // reset input\n        client->cWsRXsize = 0;\n\n    } else {\n        log_error(\"[WS][%d][handleWebsocket] missing data!\", client->num);\n        free(payload);\n        clientDisconnect(client, 1002);\n    }\n}\n\n/**\n * generate the key for Sec-WebSocket-Accept\n * @param clientKey String\n * @return String Accept Key\n */\nString WebSockets::acceptKey(String & clientKey) {\n    uint8_t sha1HashBin[20] = { 0 };\n#ifdef ESP8266\n    sha1(clientKey + \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\", &sha1HashBin[0]);\n#elif defined(ESP32)\n    String data = clientKey + \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\";\n    esp_sha(SHA1, (unsigned char *)data.c_str(), data.length(), &sha1HashBin[0]);\n#else\n    clientKey += \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\";\n    SHA1_CTX ctx;\n    SHA1Init(&ctx);\n    SHA1Update(&ctx, (const unsigned char *)clientKey.c_str(), clientKey.length());\n    SHA1Final(&sha1HashBin[0], &ctx);\n#endif\n\n    String key = base64_encode(sha1HashBin, 20);\n    key.trim();\n\n    return key;\n}\n\n/**\n * base64_encode\n * @param data uint8_t *\n * @param length size_t\n * @return base64 encoded String\n */\nString WebSockets::base64_encode(uint8_t * data, size_t length) {\n    size_t size   = ((length * 1.6f) + 1);\n    size          = std::max(size, (size_t)5);    // minimum buffer size\n    char * buffer = (char *)malloc(size);\n    if(buffer) {\n        base64_encodestate _state;\n        base64_init_encodestate(&_state);\n        int len = base64_encode_block((const char *)&data[0], length, &buffer[0], &_state);\n        len     = base64_encode_blockend((buffer + len), &_state);\n\n        String base64 = String(buffer);\n        free(buffer);\n        return base64;\n    }\n    return String(\"-FAIL-\");\n}\n\n/**\n * read x byte from tcp or get timeout\n * @param client WSclient_t *\n * @param out  uint8_t * data buffer\n * @param n size_t byte count\n * @return true if ok\n */\nbool WebSockets::readCb(WSclient_t * client, uint8_t * out, size_t n, WSreadWaitCb cb) {\n\n    unsigned long t = millis();\n    ssize_t len;\n    log_debug(\"[readCb] n: %zu t: %lu\\n\", n, t);\n    while(n > 0) {\n        if(client->tcp == NULL) {\n            log_error(\"[readCb] tcp is null!\");\n            if(cb) {\n                cb(client, false);\n            }\n            return false;\n        }\n\n        if(!client->tcp->connected()) {\n            log_error(\"[readCb] not connected!\");\n            if(cb) {\n                cb(client, false);\n            }\n            return false;\n        }\n\n        if((millis() - t) > WEBSOCKETS_TCP_TIMEOUT) {\n            log_error(\"[readCb] receive TIMEOUT! %lu\", (millis() - t));\n            if(cb) {\n                cb(client, false);\n            }\n            return false;\n        }\n\n        if(!client->tcp->available()) {\n            WEBSOCKETS_YIELD_MORE();\n            continue;\n        }\n\n        len = client->tcp->read((uint8_t *)out, n);\n        if(len > 0) {\n            t = millis();\n            out += len;\n            n -= len;\n        } \n        if(n > 0) {\n            WEBSOCKETS_YIELD();\n        }\n    }\n    if(cb) {\n        cb(client, true);\n    }\n    WEBSOCKETS_YIELD();\n    return true;\n}\n\n/**\n * write x byte to tcp or get timeout\n * @param client WSclient_t *\n * @param out  uint8_t * data buffer\n * @param n size_t byte count\n * @return bytes send\n */\nsize_t WebSockets::write(WSclient_t * client, uint8_t * out, size_t n) {\n    if(out == NULL)\n        return 0;\n    if(client == NULL)\n        return 0;\n    unsigned long t = millis();\n    size_t len      = 0;\n    size_t total    = 0;\n    log_debug(\"[write] n: %zu t: %lu\\n\", n, t);\n    while(n > 0) {\n        if(client->tcp == NULL) {\n            log_error(\"[write] tcp is null!\");\n            break;\n        }\n\n        if(!client->tcp->connected()) {\n            log_error(\"[write] not connected!\");\n            break;\n        }\n\n        if((millis() - t) > WEBSOCKETS_TCP_TIMEOUT) {\n            log_error(\"[write] write TIMEOUT! %lu\", (millis() - t));\n            break;\n        }\n\n        len = client->tcp->write((const uint8_t *)out, n);\n        if(len) {\n            t = millis();\n            out += len;\n            n -= len;\n            total += len;\n        } else {\n            log_error(\"WS write %d failed left %d!\", len, n);\n        }\n        if(n > 0) {\n            WEBSOCKETS_YIELD();\n        }\n    }\n    WEBSOCKETS_YIELD();\n    return total;\n}\n\nsize_t WebSockets::write(WSclient_t * client, const char * out) {\n    if(client == NULL)\n        return 0;\n    if(out == NULL)\n        return 0;\n    return write(client, (uint8_t *)out, strlen(out));\n}\n\n/**\n * enable ping/pong heartbeat process\n * @param client WSclient_t *\n * @param pingInterval uint32_t how often ping will be sent\n * @param pongTimeout uint32_t millis after which pong should timout if not received\n * @param disconnectTimeoutCount uint8_t how many timeouts before disconnect, 0=> do not disconnect\n */\nvoid WebSockets::enableHeartbeat(WSclient_t * client, uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount) {\n    if(client == NULL)\n        return;\n    client->pingInterval           = pingInterval;\n    client->pongTimeout            = pongTimeout;\n    client->disconnectTimeoutCount = disconnectTimeoutCount;\n    client->pongReceived           = false;\n}\n\n/**\n * handle ping/pong heartbeat timeout process\n * @param client WSclient_t *\n */\nvoid WebSockets::handleHBTimeout(WSclient_t * client) {\n    if(client->pingInterval) {    // if heartbeat is enabled\n        uint32_t pi = millis() - client->lastPing;\n\n        if(client->pongReceived) {\n            client->pongTimeoutCount = 0;\n        } else {\n            if(pi > client->pongTimeout) {    // pong not received in time\n                client->pongTimeoutCount++;\n                client->lastPing = millis() - client->pingInterval - 500;    // force ping on the next run\n\n                log_debug(\"[HBtimeout] pong TIMEOUT! lp=%d millis=%lu pi=%d count=%d\", client->lastPing, millis(), pi, client->pongTimeoutCount);\n\n                if(client->disconnectTimeoutCount && client->pongTimeoutCount >= client->disconnectTimeoutCount) {\n                    clientDisconnect(client);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/websocket/WebSockets.h",
    "content": "/**\n * @file WebSockets.h\n * @date 20.05.2015\n * @author Markus Sattler\n *\n * Copyright (c) 2015 Markus Sattler. All rights reserved.\n * This file is part of the WebSockets for Arduino.\n *\n * This library is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 2.1 of the License, or (at your option) any later version.\n *\n * This library is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public\n * License along with this library; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n\n#ifndef ESP_WEBSOCKETS_H_\n#define ESP_WEBSOCKETS_H_\n\n#include <Arduino.h>\n#include <IPAddress.h>\n#include <functional>\n#include \"../SerialLog.h\"\n\n\n#define WEBSOCKETS_MAX_DATA_SIZE (15 * 1024)\n#define WEBSOCKETS_USE_BIG_MEM\n#define GET_FREE_HEAP ESP.getFreeHeap()\n\n// ws timeout\n#define WEBSOCKETS_TCP_TIMEOUT (5000)\n// max size of the WS Message Header\n#define WEBSOCKETS_MAX_HEADER_SIZE (14)\n\n// moves all Header strings to Flash (~300 Byte)\n// #define WEBSOCKETS_SAVE_RAM\n#ifdef WEBSOCKETS_SAVE_RAM\n#define WEBSOCKETS_STRING(var) F(var)\n#else\n#define WEBSOCKETS_STRING(var) var\n#endif\n\n#if defined(ESP8266)\n    #define WEBSOCKETS_NETWORK_CLASS WiFiClient\n    #define WEBSOCKETS_NETWORK_SSL_CLASS WiFiClientSecure\n    #define WEBSOCKETS_NETWORK_SERVER_CLASS WiFiServer\n    #define HAS_SSL\n    #define WEBSOCKETS_YIELD() delay(0)\n    #define WEBSOCKETS_YIELD_MORE() delay(1)\n\n    #include <ESP8266WiFi.h>\n    #if defined(wificlientbearssl_h) && !defined(USING_AXTLS) && !defined(wificlientsecure_h)\n    #define SSL_BARESSL\n    #else\n    #define SSL_AXTLS\n    #endif\n\n#elif defined(ESP32)\n    #include <WiFi.h>\n    #include <WiFiClientSecure.h>\n    #define SSL_AXTLS\n    #define WEBSOCKETS_YIELD() yield()\n    #define WEBSOCKETS_YIELD_MORE() delay(1)\n\n    #define WEBSOCKETS_NETWORK_CLASS WiFiClient\n    #define WEBSOCKETS_NETWORK_SSL_CLASS WiFiClientSecure\n    #define WEBSOCKETS_NETWORK_SERVER_CLASS WiFiServer\n    #define HAS_SSL\n#endif\n\n\n\ntypedef enum {\n    WSC_NOT_CONNECTED,\n    WSC_HEADER,\n    WSC_BODY,\n    WSC_CONNECTED\n} WSclientsStatus_t;\n\ntypedef enum {\n    WStype_ERROR,\n    WStype_DISCONNECTED,\n    WStype_CONNECTED,\n    WStype_TEXT,\n    WStype_BIN,\n    WStype_FRAGMENT_TEXT_START,\n    WStype_FRAGMENT_BIN_START,\n    WStype_FRAGMENT,\n    WStype_FRAGMENT_FIN,\n    WStype_PING,\n    WStype_PONG,\n} WStype_t;\n\ntypedef enum {\n    WSop_continuation = 0x00,    ///< %x0 denotes a continuation frame\n    WSop_text         = 0x01,    ///< %x1 denotes a text frame\n    WSop_binary       = 0x02,    ///< %x2 denotes a binary frame\n                                 ///< %x3-7 are reserved for further non-control frames\n    WSop_close = 0x08,           ///< %x8 denotes a connection close\n    WSop_ping  = 0x09,           ///< %x9 denotes a ping\n    WSop_pong  = 0x0A            ///< %xA denotes a pong\n                                 ///< %xB-F are reserved for further control frames\n} WSopcode_t;\n\ntypedef struct {\n    bool fin;\n    bool rsv1;\n    bool rsv2;\n    bool rsv3;\n\n    WSopcode_t opCode;\n    bool mask;\n\n    size_t payloadLen;\n\n    uint8_t * maskKey;\n} WSMessageHeader_t;\n\ntypedef struct {\n    void init(uint8_t num,\n        uint32_t pingInterval,\n        uint32_t pongTimeout,\n        uint8_t disconnectTimeoutCount) {\n        this->num                    = num;\n        this->pingInterval           = pingInterval;\n        this->pongTimeout            = pongTimeout;\n        this->disconnectTimeoutCount = disconnectTimeoutCount;\n    }\n\n    uint8_t num = 0;    ///< connection number\n\n    WSclientsStatus_t status = WSC_NOT_CONNECTED;\n\n    WEBSOCKETS_NETWORK_CLASS * tcp = nullptr;\n\n    bool isSocketIO = false;    ///< client for socket.io server\n\n#if defined(HAS_SSL)\n    bool isSSL = false;    ///< run in ssl mode\n    WEBSOCKETS_NETWORK_SSL_CLASS * ssl;\n#endif\n\n    String cUrl;           ///< http url\n    uint16_t cCode = 0;    ///< http code\n\n    bool cIsClient    = false;    ///< will be used for masking\n    bool cIsUpgrade   = false;    ///< Connection == Upgrade\n    bool cIsWebsocket = false;    ///< Upgrade == websocket\n\n    String cSessionId;        ///< client Set-Cookie (session id)\n    String cKey;              ///< client Sec-WebSocket-Key\n    String cAccept;           ///< client Sec-WebSocket-Accept\n    String cProtocol;         ///< client Sec-WebSocket-Protocol\n    String cExtensions;       ///< client Sec-WebSocket-Extensions\n    uint16_t cVersion = 0;    ///< client Sec-WebSocket-Version\n\n    uint8_t cWsRXsize = 0;                            ///< State of the RX\n    uint8_t cWsHeader[WEBSOCKETS_MAX_HEADER_SIZE];    ///< RX WS Message buffer\n    WSMessageHeader_t cWsHeaderDecode;\n\n    String base64Authorization;    ///< Base64 encoded Auth request\n    String plainAuthorization;     ///< Base64 encoded Auth request\n\n    String extraHeaders;\n\n    bool cHttpHeadersValid = false;    ///< non-websocket http header validity indicator\n    size_t cMandatoryHeadersCount;     ///< non-websocket mandatory http headers present count\n\n    bool pongReceived              = false;\n    uint32_t pingInterval          = 0;    // how often ping will be sent, 0 means \"heartbeat is not active\"\n    uint32_t lastPing              = 0;    // millis when last pong has been received\n    uint32_t pongTimeout           = 0;    // interval in millis after which pong is considered to timeout\n    uint8_t disconnectTimeoutCount = 0;    // after how many subsequent pong timeouts discconnect will happen, 0 means \"do not disconnect\"\n    uint8_t pongTimeoutCount       = 0;    // current pong timeout count\n\n} WSclient_t;\n\n\nclass WebSockets {\n  protected:\n\n    typedef std::function<void(WSclient_t * client, bool ok)> WSreadWaitCb;\n\n    virtual void clientDisconnect(WSclient_t * client)  = 0;\n    virtual bool clientIsConnected(WSclient_t * client) = 0;\n\n    void clientDisconnect(WSclient_t * client, uint16_t code, char * reason = NULL, size_t reasonLen = 0);\n\n    virtual void messageReceived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin) = 0;\n\n    uint8_t createHeader(uint8_t * buf, WSopcode_t opcode, size_t length, bool mask, uint8_t maskKey[4], bool fin);\n    bool sendFrameHeader(WSclient_t * client, WSopcode_t opcode, size_t length = 0, bool fin = true);\n    bool sendFrame(WSclient_t * client, WSopcode_t opcode, uint8_t * payload = NULL, size_t length = 0, bool fin = true, bool headerToPayload = false);\n\n    void headerDone(WSclient_t * client);\n\n    void handleWebsocket(WSclient_t * client);\n\n    bool handleWebsocketWaitFor(WSclient_t * client, size_t size);\n    void handleWebsocketCb(WSclient_t * client);\n    void handleWebsocketPayloadCb(WSclient_t * client, bool ok, uint8_t * payload);\n\n    String acceptKey(String & clientKey);\n    String base64_encode(uint8_t * data, size_t length);\n\n    bool readCb(WSclient_t * client, uint8_t * out, size_t n, WSreadWaitCb cb);\n    virtual size_t write(WSclient_t * client, uint8_t * out, size_t n);\n    size_t write(WSclient_t * client, const char * out);\n\n    void enableHeartbeat(WSclient_t * client, uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount);\n    void handleHBTimeout(WSclient_t * client);\n};\n\n#ifndef UNUSED\n#define UNUSED(var) (void)(var)\n#endif\n\n\n\n#endif /* WEBSOCKETS_H_ */\n"
  },
  {
    "path": "src/websocket/WebSocketsClient.cpp",
    "content": "/**\n * @file WebSocketsClient.cpp\n * @date 20.05.2015\n * @author Markus Sattler\n *\n * Copyright (c) 2015 Markus Sattler. All rights reserved.\n * This file is part of the WebSockets for Arduino.\n *\n * This library is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 2.1 of the License, or (at your option) any later version.\n *\n * This library is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public\n * License along with this library; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n\n#include \"WebSockets.h\"\n#include \"WebSocketsClient.h\"\n\nWebSocketsClient::WebSocketsClient() {\n    _cbEvent             = NULL;\n    _client.num          = 0;\n    _client.cIsClient    = true;\n    _client.extraHeaders = WEBSOCKETS_STRING(\"Origin: file://\");\n    _reconnectInterval   = 500;\n    _port                = 0;\n    _host                = \"\";\n}\n\nWebSocketsClient::~WebSocketsClient() {\n    disconnect();\n}\n\n/**\n * calles to init the Websockets server\n */\nvoid WebSocketsClient::begin(const char * host, uint16_t port, const char * url, const char * protocol) {\n    _host = host;\n    _port = port;\n#if defined(HAS_SSL)\n    _fingerprint = SSL_FINGERPRINT_NULL;\n    _CA_cert     = NULL;\n#endif\n\n    _client.num    = 0;\n    _client.status = WSC_NOT_CONNECTED;\n    _client.tcp    = NULL;\n#if defined(HAS_SSL)\n    _client.isSSL = false;\n    _client.ssl   = NULL;\n#endif\n    _client.cUrl                = url;\n    _client.cCode               = 0;\n    _client.cIsUpgrade          = false;\n    _client.cIsWebsocket        = true;\n    _client.cKey                = \"\";\n    _client.cAccept             = \"\";\n    _client.cProtocol           = protocol;\n    _client.cExtensions         = \"\";\n    _client.cVersion            = 0;\n    _client.base64Authorization = \"\";\n    _client.plainAuthorization  = \"\";\n    _client.isSocketIO          = false;\n\n    _client.lastPing         = 0;\n    _client.pongReceived     = false;\n    _client.pongTimeoutCount = 0;\n\n#ifdef ESP8266\n    randomSeed(RANDOM_REG32);\n#else\n    // todo find better seed\n    randomSeed(millis());\n#endif\n\n    _lastConnectionFail = 0;\n    _lastHeaderSent     = 0;\n\n    log_debug(\"[WS-Client] Websocket started\\n\");\n}\n\nvoid WebSocketsClient::begin(String host, uint16_t port, String url, String protocol) {\n    begin(host.c_str(), port, url.c_str(), protocol.c_str());\n}\n\nvoid WebSocketsClient::begin(IPAddress host, uint16_t port, const char * url, const char * protocol) {\n    return begin(host.toString().c_str(), port, url, protocol);\n}\n\n#if defined(HAS_SSL)\n#if defined(SSL_AXTLS)\nvoid WebSocketsClient::beginSSL(const char * host, uint16_t port, const char * url, const char * fingerprint, const char * protocol) {\n    begin(host, port, url, protocol);\n    _client.isSSL = true;\n    _fingerprint  = fingerprint;\n    _CA_cert      = NULL;\n}\n\nvoid WebSocketsClient::beginSSL(String host, uint16_t port, String url, String fingerprint, String protocol) {\n    beginSSL(host.c_str(), port, url.c_str(), fingerprint.c_str(), protocol.c_str());\n}\n\nvoid WebSocketsClient::beginSslWithCA(const char * host, uint16_t port, const char * url, const char * CA_cert, const char * protocol) {\n    begin(host, port, url, protocol);\n    _client.isSSL = true;\n    _fingerprint  = SSL_FINGERPRINT_NULL;\n    _CA_cert      = CA_cert;\n}\n#else\nvoid WebSocketsClient::beginSSL(const char * host, uint16_t port, const char * url, const uint8_t * fingerprint, const char * protocol) {\n    begin(host, port, url, protocol);\n    _client.isSSL = true;\n    _fingerprint  = fingerprint;\n    _CA_cert      = NULL;\n}\n\nvoid WebSocketsClient::beginSslWithCA(const char * host, uint16_t port, const char * url, BearSSL::X509List * CA_cert, const char * protocol) {\n    begin(host, port, url, protocol);\n    _client.isSSL = true;\n    _fingerprint  = SSL_FINGERPRINT_NULL;\n    _CA_cert      = CA_cert;\n}\n\nvoid WebSocketsClient::beginSslWithCA(const char * host, uint16_t port, const char * url, const char * CA_cert, const char * protocol) {\n    beginSslWithCA(host, port, url, new BearSSL::X509List(CA_cert), protocol);\n}\n\nvoid WebSocketsClient::setSSLClientCertKey(BearSSL::X509List * clientCert, BearSSL::PrivateKey * clientPrivateKey) {\n    _client_cert = clientCert;\n    _client_key  = clientPrivateKey;\n}\n\nvoid WebSocketsClient::setSSLClientCertKey(const char * clientCert, const char * clientPrivateKey) {\n    setSSLClientCertKey(new BearSSL::X509List(clientCert), new BearSSL::PrivateKey(clientPrivateKey));\n}\n\n#endif    // SSL_AXTLS\n#endif    // HAS_SSL\n\nvoid WebSocketsClient::beginSocketIO(const char * host, uint16_t port, const char * url, const char * protocol) {\n    begin(host, port, url, protocol);\n    _client.isSocketIO = true;\n}\n\nvoid WebSocketsClient::beginSocketIO(String host, uint16_t port, String url, String protocol) {\n    beginSocketIO(host.c_str(), port, url.c_str(), protocol.c_str());\n}\n\n#if defined(HAS_SSL)\nvoid WebSocketsClient::beginSocketIOSSL(const char * host, uint16_t port, const char * url, const char * protocol) {\n    begin(host, port, url, protocol);\n    _client.isSocketIO = true;\n    _client.isSSL      = true;\n    _fingerprint       = SSL_FINGERPRINT_NULL;\n}\n\nvoid WebSocketsClient::beginSocketIOSSL(String host, uint16_t port, String url, String protocol) {\n    beginSocketIOSSL(host.c_str(), port, url.c_str(), protocol.c_str());\n}\n\n#if defined(SSL_BARESSL)\nvoid WebSocketsClient::beginSocketIOSSLWithCA(const char * host, uint16_t port, const char * url, BearSSL::X509List * CA_cert, const char * protocol) {\n    begin(host, port, url, protocol);\n    _client.isSocketIO = true;\n    _client.isSSL      = true;\n    _fingerprint       = SSL_FINGERPRINT_NULL;\n    _CA_cert           = CA_cert;\n}\n#endif\n\nvoid WebSocketsClient::beginSocketIOSSLWithCA(const char * host, uint16_t port, const char * url, const char * CA_cert, const char * protocol) {\n    begin(host, port, url, protocol);\n    _client.isSocketIO = true;\n    _client.isSSL      = true;\n    _fingerprint       = SSL_FINGERPRINT_NULL;\n#if defined(SSL_BARESSL)\n    _CA_cert = new BearSSL::X509List(CA_cert);\n#else\n    _CA_cert = CA_cert;\n#endif\n}\n\n#endif\n\n\n/**\n * called in arduino loop\n */\nvoid WebSocketsClient::loop(void) {\n    if(_port == 0) {\n        return;\n    }\n    WEBSOCKETS_YIELD();\n    if(!clientIsConnected(&_client)) {\n        // do not flood the server\n        if((millis() - _lastConnectionFail) < _reconnectInterval) {\n            return;\n        }\n\n#if defined(HAS_SSL)\n        if(_client.isSSL) {\n            log_debug(\"[WS-Client] connect wss...\\n\");\n            if(_client.ssl) {\n                delete _client.ssl;\n                _client.ssl = NULL;\n                _client.tcp = NULL;\n            }\n            _client.ssl = new WEBSOCKETS_NETWORK_SSL_CLASS();\n            _client.tcp = _client.ssl;\n            if(_CA_cert) {\n                log_debug(\"[WS-Client] setting CA certificate\");\n#if defined(ESP32)\n                _client.ssl->setCACert(_CA_cert);\n#elif defined(ESP8266) && defined(SSL_AXTLS)\n                _client.ssl->setCACert((const uint8_t *)_CA_cert, strlen(_CA_cert) + 1);\n#elif(defined(ESP8266) || defined(ARDUINO_ARCH_RP2040)) && defined(SSL_BARESSL)\n                _client.ssl->setTrustAnchors(_CA_cert);\n#else\n#error setCACert not implemented\n#endif\n#if defined(ESP32)\n            } else if(!SSL_FINGERPRINT_IS_SET) {\n                _client.ssl->setInsecure();\n#elif defined(SSL_BARESSL)\n            } else if(SSL_FINGERPRINT_IS_SET) {\n                _client.ssl->setFingerprint(_fingerprint);\n            } else {\n                _client.ssl->setInsecure();\n            }\n            if(_client_cert && _client_key) {\n                _client.ssl->setClientRSACert(_client_cert, _client_key);\n                log_debug(\"[WS-Client] setting client certificate and key\");\n#endif\n            }\n        } else {\n            log_debug(\"[WS-Client] connect ws...\\n\");\n            if(_client.tcp) {\n                delete _client.tcp;\n                _client.tcp = NULL;\n            }\n            _client.tcp = new WEBSOCKETS_NETWORK_CLASS();\n        }\n#else\n        _client.tcp = new WEBSOCKETS_NETWORK_CLASS();\n#endif\n\n        if(!_client.tcp) {\n            log_error(\"[WS-Client] creating Network class failed!\");\n            return;\n        }\n        WEBSOCKETS_YIELD();\n#if defined(ESP32)\n        if(_client.tcp->connect(_host.c_str(), _port, WEBSOCKETS_TCP_TIMEOUT)) {\n#else\n        if(_client.tcp->connect(_host.c_str(), _port)) {\n#endif\n            connectedCb();\n            _lastConnectionFail = 0;\n        } else {\n            connectFailedCb();\n            _lastConnectionFail = millis();\n        }\n    } else {\n        handleClientData();\n        WEBSOCKETS_YIELD();\n        if(_client.status == WSC_CONNECTED) {\n            handleHBPing();\n            handleHBTimeout(&_client);\n        }\n    }\n}\n\n\n/**\n * set callback function\n * @param cbEvent WebSocketServerEvent\n */\nvoid WebSocketsClient::onEvent(WebSocketClientEvent cbEvent) {\n    _cbEvent = cbEvent;\n}\n\n/**\n * send text data to client\n * @param num uint8_t client id\n * @param payload uint8_t *\n * @param length size_t\n * @param headerToPayload bool  (see sendFrame for more details)\n * @return true if ok\n */\nbool WebSocketsClient::sendTXT(uint8_t * payload, size_t length, bool headerToPayload) {\n    if(length == 0) {\n        length = strlen((const char *)payload);\n    }\n    if(clientIsConnected(&_client)) {\n        return sendFrame(&_client, WSop_text, payload, length, true, headerToPayload);\n    }\n    return false;\n}\n\nbool WebSocketsClient::sendTXT(const uint8_t * payload, size_t length) {\n    return sendTXT((uint8_t *)payload, length);\n}\n\nbool WebSocketsClient::sendTXT(char * payload, size_t length, bool headerToPayload) {\n    return sendTXT((uint8_t *)payload, length, headerToPayload);\n}\n\nbool WebSocketsClient::sendTXT(const char * payload, size_t length) {\n    return sendTXT((uint8_t *)payload, length);\n}\n\nbool WebSocketsClient::sendTXT(String & payload) {\n    return sendTXT((uint8_t *)payload.c_str(), payload.length());\n}\n\nbool WebSocketsClient::sendTXT(char payload) {\n    uint8_t buf[WEBSOCKETS_MAX_HEADER_SIZE + 2] = { 0x00 };\n    buf[WEBSOCKETS_MAX_HEADER_SIZE]             = payload;\n    return sendTXT(buf, 1, true);\n}\n\n/**\n * send binary data to client\n * @param num uint8_t client id\n * @param payload uint8_t *\n * @param length size_t\n * @param headerToPayload bool  (see sendFrame for more details)\n * @return true if ok\n */\nbool WebSocketsClient::sendBIN(uint8_t * payload, size_t length, bool headerToPayload) {\n    if(clientIsConnected(&_client)) {\n        return sendFrame(&_client, WSop_binary, payload, length, true, headerToPayload);\n    }\n    return false;\n}\n\nbool WebSocketsClient::sendBIN(const uint8_t * payload, size_t length) {\n    return sendBIN((uint8_t *)payload, length);\n}\n\n/**\n * sends a WS ping to Server\n * @param payload uint8_t *\n * @param length size_t\n * @return true if ping is send out\n */\nbool WebSocketsClient::sendPing(uint8_t * payload, size_t length) {\n    if(clientIsConnected(&_client)) {\n        bool sent = sendFrame(&_client, WSop_ping, payload, length);\n        if(sent)\n            _client.lastPing = millis();\n        return sent;\n    }\n    return false;\n}\n\nbool WebSocketsClient::sendPing(String & payload) {\n    return sendPing((uint8_t *)payload.c_str(), payload.length());\n}\n\n/**\n * disconnect one client\n * @param num uint8_t client id\n */\nvoid WebSocketsClient::disconnect(void) {\n    if(clientIsConnected(&_client)) {\n        WebSockets::clientDisconnect(&_client, 1000);\n    }\n}\n\n/**\n * set the Authorizatio for the http request\n * @param user const char *\n * @param password const char *\n */\nvoid WebSocketsClient::setAuthorization(const char * user, const char * password) {\n    if(user && password) {\n        String auth = user;\n        auth += \":\";\n        auth += password;\n        _client.base64Authorization = base64_encode((uint8_t *)auth.c_str(), auth.length());\n    }\n}\n\n/**\n * set the Authorizatio for the http request\n * @param auth const char * base64\n */\nvoid WebSocketsClient::setAuthorization(const char * auth) {\n    if(auth) {\n        //_client.base64Authorization = auth;\n        _client.plainAuthorization = auth;\n    }\n}\n\n/**\n * set extra headers for the http request;\n * separate headers by \"\\r\\n\"\n * @param extraHeaders const char * extraHeaders\n */\nvoid WebSocketsClient::setExtraHeaders(const char * extraHeaders) {\n    _client.extraHeaders = extraHeaders;\n}\n\n/**\n * set the reconnect Interval\n * how long to wait after a connection initiate failed\n * @param time in ms\n */\nvoid WebSocketsClient::setReconnectInterval(unsigned long time) {\n    _reconnectInterval = time;\n}\n\nbool WebSocketsClient::isConnected(void) {\n    return (_client.status == WSC_CONNECTED);\n}\n\n// #################################################################################\n// #################################################################################\n// #################################################################################\n\n/**\n *\n * @param client WSclient_t *  ptr to the client struct\n * @param opcode WSopcode_t\n * @param payload  uint8_t *\n * @param length size_t\n */\nvoid WebSocketsClient::messageReceived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin) {\n    WStype_t type = WStype_ERROR;\n\n    UNUSED(client);\n\n    switch(opcode) {\n        case WSop_text:\n            type = fin ? WStype_TEXT : WStype_FRAGMENT_TEXT_START;\n            break;\n        case WSop_binary:\n            type = fin ? WStype_BIN : WStype_FRAGMENT_BIN_START;\n            break;\n        case WSop_continuation:\n            type = fin ? WStype_FRAGMENT_FIN : WStype_FRAGMENT;\n            break;\n        case WSop_ping:\n            type = WStype_PING;\n            break;\n        case WSop_pong:\n            type = WStype_PONG;\n            break;\n        case WSop_close:\n        default:\n            break;\n    }\n\n    runCbEvent(type, payload, length);\n}\n\n/**\n * Disconnect an client\n * @param client WSclient_t *  ptr to the client struct\n */\nvoid WebSocketsClient::clientDisconnect(WSclient_t * client) {\n    bool event = false;\n\n    if(client->isSSL && client->ssl) {\n        if(client->ssl->connected()) {\n            client->ssl->flush();\n            client->ssl->stop();\n        }\n        event = true;\n        delete client->ssl;\n        client->ssl = NULL;\n        client->tcp = NULL;\n    }\n\n\n    if(client->tcp) {\n        if(client->tcp->connected()) {\n            // client->tcp->clear();\n            client->tcp->stop();\n        }\n        event = true;\n        client->status = WSC_NOT_CONNECTED;\n        client->tcp = NULL;\n    }\n\n    client->cCode        = 0;\n    client->cKey         = \"\";\n    client->cAccept      = \"\";\n    client->cVersion     = 0;\n    client->cIsUpgrade   = false;\n    client->cIsWebsocket = false;\n    client->cSessionId   = \"\";\n\n    client->status      = WSC_NOT_CONNECTED;\n    _lastConnectionFail = millis();\n\n    log_debug(\"[WS-Client] client disconnected.\\n\");\n    if(event) {\n        runCbEvent(WStype_DISCONNECTED, NULL, 0);\n    }\n}\n\n/**\n * get client state\n * @param client WSclient_t *  ptr to the client struct\n * @return true = conneted\n */\nbool WebSocketsClient::clientIsConnected(WSclient_t * client) {\n    if(!client->tcp) {\n        return false;\n    }\n\n    if(client->tcp->connected()) {\n        if(client->status != WSC_NOT_CONNECTED) {\n            return true;\n        }\n    } else {\n        // client lost\n        if(client->status != WSC_NOT_CONNECTED) {\n            log_debug(\"[WS-Client] connection lost.\\n\");\n            // do cleanup\n            clientDisconnect(client);\n        }\n    }\n\n    if(client->tcp) {\n        // do cleanup\n        clientDisconnect(client);\n    }\n\n    return false;\n}\n\n/**\n * Handel incomming data from Client\n */\nvoid WebSocketsClient::handleClientData(void) {\n    if((_client.status == WSC_HEADER || _client.status == WSC_BODY) && _lastHeaderSent + WEBSOCKETS_TCP_TIMEOUT < millis()) {\n        log_debug(\"[WS-Client][handleClientData] header response timeout.. disconnecting!\\n\");\n        clientDisconnect(&_client);\n        WEBSOCKETS_YIELD();\n        return;\n    }\n\n    int len = _client.tcp->available();\n    if(len > 0) {\n        switch(_client.status) {\n            case WSC_HEADER: {\n                String headerLine = _client.tcp->readStringUntil('\\n');\n                handleHeader(&_client, &headerLine);\n            } break;\n            case WSC_BODY: {\n                char buf[256] = { 0 };\n                _client.tcp->readBytes(&buf[0], std::min((size_t)len, sizeof(buf)));\n                String bodyLine = buf;\n                handleHeader(&_client, &bodyLine);\n            } break;\n            case WSC_CONNECTED:\n                WebSockets::handleWebsocket(&_client);\n                break;\n            default:\n                WebSockets::clientDisconnect(&_client, 1002);\n                break;\n        }\n    }\n    WEBSOCKETS_YIELD();\n}\n\n\n/**\n * send the WebSocket header to Server\n * @param client WSclient_t *  ptr to the client struct\n */\nvoid WebSocketsClient::sendHeader(WSclient_t * client) {\n    static const char * NEW_LINE = \"\\r\\n\";\n\n    log_debug(\"[WS-Client][sendHeader] sending header...\\n\");\n\n    uint8_t randomKey[16] = { 0 };\n\n    for(uint8_t i = 0; i < sizeof(randomKey); i++) {\n        randomKey[i] = random(0xFF);\n    }\n\n    client->cKey = base64_encode(&randomKey[0], 16);\n\n    String handshake;\n    bool ws_header = true;\n    String url     = client->cUrl;\n\n    if(client->isSocketIO) {\n        if(client->cSessionId.length() == 0) {\n            url += WEBSOCKETS_STRING(\"&transport=polling\");\n            ws_header = false;\n        } else {\n            url += WEBSOCKETS_STRING(\"&transport=websocket&sid=\");\n            url += client->cSessionId;\n        }\n    }\n\n    handshake = WEBSOCKETS_STRING(\"GET \");\n    handshake += url + WEBSOCKETS_STRING(\n                           \" HTTP/1.1\\r\\n\"\n                           \"Host: \");\n    handshake += _host + \":\" + _port + NEW_LINE;\n\n    if(ws_header) {\n        handshake += WEBSOCKETS_STRING(\n            \"Connection: Upgrade\\r\\n\"\n            \"Upgrade: websocket\\r\\n\"\n            \"Sec-WebSocket-Version: 13\\r\\n\"\n            \"Sec-WebSocket-Key: \");\n        handshake += client->cKey + NEW_LINE;\n\n        if(client->cProtocol.length() > 0) {\n            handshake += WEBSOCKETS_STRING(\"Sec-WebSocket-Protocol: \");\n            handshake += client->cProtocol + NEW_LINE;\n        }\n\n        if(client->cExtensions.length() > 0) {\n            handshake += WEBSOCKETS_STRING(\"Sec-WebSocket-Extensions: \");\n            handshake += client->cExtensions + NEW_LINE;\n        }\n    } else {\n        handshake += WEBSOCKETS_STRING(\"Connection: keep-alive\\r\\n\");\n    }\n\n    // add extra headers; by default this includes \"Origin: file://\"\n    if(client->extraHeaders.length() > 0) {\n        handshake += client->extraHeaders + NEW_LINE;\n    }\n\n    handshake += WEBSOCKETS_STRING(\"User-Agent: arduino-WebSocket-Client\\r\\n\");\n\n    if(client->base64Authorization.length() > 0) {\n        handshake += WEBSOCKETS_STRING(\"Authorization: Basic \");\n        handshake += client->base64Authorization + NEW_LINE;\n    }\n\n    if(client->plainAuthorization.length() > 0) {\n        handshake += WEBSOCKETS_STRING(\"Authorization: \");\n        handshake += client->plainAuthorization + NEW_LINE;\n    }\n\n    handshake += NEW_LINE;\n\n    log_debug(\"[WS-Client][sendHeader] handshake %s\", handshake.c_str());\n    write(client, (uint8_t *)handshake.c_str(), handshake.length());\n\n    log_debug(\"[WS-Client][sendHeader] sending header... Done.\\n\");\n    _lastHeaderSent = millis();\n}\n\n/**\n * handle the WebSocket header reading\n * @param client WSclient_t *  ptr to the client struct\n */\nvoid WebSocketsClient::handleHeader(WSclient_t * client, String * headerLine) {\n    headerLine->trim();    // remove \\r\n\n    // this code handels the http body for Socket.IO V3 requests\n    if(headerLine->length() > 0 && client->isSocketIO && client->status == WSC_BODY && client->cSessionId.length() == 0) {\n        log_debug(\"[WS-Client][handleHeader] socket.io json: %s\\n\", headerLine->c_str());\n        String sid_begin = WEBSOCKETS_STRING(\"\\\"sid\\\":\\\"\");\n        if(headerLine->indexOf(sid_begin) > -1) {\n            int start          = headerLine->indexOf(sid_begin) + sid_begin.length();\n            int end            = headerLine->indexOf('\"', start);\n            client->cSessionId = headerLine->substring(start, end);\n            log_debug(\"[WS-Client][handleHeader]  - cSessionId: %s\\n\", client->cSessionId.c_str());\n\n            // Trigger websocket connection code path\n            *headerLine = \"\";\n        }\n    }\n\n    // headle HTTP header\n    if(headerLine->length() > 0) {\n        log_debug(\"[WS-Client][handleHeader] RX: %s\\n\", headerLine->c_str());\n\n        if(headerLine->startsWith(WEBSOCKETS_STRING(\"HTTP/1.\"))) {\n            // \"HTTP/1.1 101 Switching Protocols\"\n            client->cCode = headerLine->substring(9, headerLine->indexOf(' ', 9)).toInt();\n        } else if(headerLine->indexOf(':') >= 0) {\n            String headerName  = headerLine->substring(0, headerLine->indexOf(':'));\n            String headerValue = headerLine->substring(headerLine->indexOf(':') + 1);\n\n            // remove space in the beginning  (RFC2616)\n            if(headerValue[0] == ' ') {\n                headerValue.remove(0, 1);\n            }\n\n            if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING(\"Connection\"))) {\n                if(headerValue.equalsIgnoreCase(WEBSOCKETS_STRING(\"upgrade\"))) {\n                    client->cIsUpgrade = true;\n                }\n            } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING(\"Upgrade\"))) {\n                if(headerValue.equalsIgnoreCase(WEBSOCKETS_STRING(\"websocket\"))) {\n                    client->cIsWebsocket = true;\n                }\n            } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING(\"Sec-WebSocket-Accept\"))) {\n                client->cAccept = headerValue;\n                client->cAccept.trim();    // see rfc6455\n            } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING(\"Sec-WebSocket-Protocol\"))) {\n                client->cProtocol = headerValue;\n            } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING(\"Sec-WebSocket-Extensions\"))) {\n                client->cExtensions = headerValue;\n            } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING(\"Sec-WebSocket-Version\"))) {\n                client->cVersion = headerValue.toInt();\n            } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING(\"Set-Cookie\")) && headerValue.indexOf(\" io=\") > -1) {\n                if(headerValue.indexOf(';') > -1) {\n                    client->cSessionId = headerValue.substring(headerValue.indexOf('=') + 1, headerValue.indexOf(\";\"));\n                } else {\n                    client->cSessionId = headerValue.substring(headerValue.indexOf('=') + 1);\n                }\n            }\n        } else {\n            log_debug(\"[WS-Client][handleHeader] Header error (%s)\\n\", headerLine->c_str());\n        }\n\n        (*headerLine) = \"\";\n\n    } else {\n        log_debug(\"[WS-Client][handleHeader] Header read fin.\\n\");\n        log_debug(\"[WS-Client][handleHeader] Client settings:\\n\");\n\n        log_debug(\"[WS-Client][handleHeader]  - cURL: %s\\n\", client->cUrl.c_str());\n        log_debug(\"[WS-Client][handleHeader]  - cKey: %s\\n\", client->cKey.c_str());\n\n        log_debug(\"[WS-Client][handleHeader] Server header:\\n\");\n        log_debug(\"[WS-Client][handleHeader]  - cCode: %d\\n\", client->cCode);\n        log_debug(\"[WS-Client][handleHeader]  - cIsUpgrade: %d\\n\", client->cIsUpgrade);\n        log_debug(\"[WS-Client][handleHeader]  - cIsWebsocket: %d\\n\", client->cIsWebsocket);\n        log_debug(\"[WS-Client][handleHeader]  - cAccept: %s\\n\", client->cAccept.c_str());\n        log_debug(\"[WS-Client][handleHeader]  - cProtocol: %s\\n\", client->cProtocol.c_str());\n        log_debug(\"[WS-Client][handleHeader]  - cExtensions: %s\\n\", client->cExtensions.c_str());\n        log_debug(\"[WS-Client][handleHeader]  - cVersion: %d\\n\", client->cVersion);\n        log_debug(\"[WS-Client][handleHeader]  - cSessionId: %s\\n\", client->cSessionId.c_str());\n\n        if(client->isSocketIO && client->cSessionId.length() == 0 && clientIsConnected(client)) {\n            log_debug(\"[WS-Client][handleHeader] still missing cSessionId try socket.io V3\\n\");\n            client->status = WSC_BODY;\n            return;\n        } else {\n            client->status = WSC_HEADER;\n        }\n\n        bool ok = (client->cIsUpgrade && client->cIsWebsocket);\n\n        if(ok) {\n            switch(client->cCode) {\n                case 101:    ///< Switching Protocols\n\n                    break;\n                case 200:\n                    if(client->isSocketIO) {\n                        break;\n                    }\n                    // falls through\n                case 403:    ///< Forbidden\n                             // todo handle login\n                             // falls through\n                default:     ///< Server dont unterstand requrst\n                    ok = false;\n                    log_debug(\"[WS-Client][handleHeader] serverCode is not 101 (%d)\\n\", client->cCode);\n                    clientDisconnect(client);\n                    _lastConnectionFail = millis();\n                    break;\n            }\n        }\n\n        if(ok) {\n            if(client->cAccept.length() == 0) {\n                ok = false;\n            } else {\n                // generate Sec-WebSocket-Accept key for check\n                String sKey = acceptKey(client->cKey);\n                if(sKey != client->cAccept) {\n                    log_debug(\"[WS-Client][handleHeader] Sec-WebSocket-Accept is wrong\\n\");\n                    ok = false;\n                }\n            }\n        }\n\n        if(ok) {\n            log_debug(\"[WS-Client][handleHeader] Websocket connection init done.\\n\");\n            headerDone(client);\n            runCbEvent(WStype_CONNECTED, (uint8_t *)client->cUrl.c_str(), client->cUrl.length());\n\n        } else if(client->isSocketIO) {\n            if(client->cSessionId.length() > 0) {\n                log_debug(\"[WS-Client][handleHeader] found cSessionId\\n\");\n                if(clientIsConnected(client) && _client.tcp->available()) {\n                    // read not needed data\n                    log_debug(\"[WS-Client][handleHeader] still data in buffer (%d), clean up.\\n\", _client.tcp->available());\n                    while(_client.tcp->available() > 0) {\n                        _client.tcp->read();\n                    }\n                }\n                sendHeader(client);\n            }\n\n        } else {\n            log_debug(\"[WS-Client][handleHeader] no Websocket connection close.\\n\");\n            _lastConnectionFail = millis();\n            if(clientIsConnected(client)) {\n                write(client, \"This is a webSocket client!\");\n            }\n            clientDisconnect(client);\n        }\n    }\n}\n\nvoid WebSocketsClient::connectedCb() {\n    log_debug(\"[WS-Client] connected to %s:%u.\\n\", _host.c_str(), _port);\n    _client.status = WSC_HEADER;\n    // set Timeout for readBytesUntil and readStringUntil\n    _client.tcp->setTimeout(WEBSOCKETS_TCP_TIMEOUT);\n    _client.tcp->setNoDelay(true);\n\n\n#if defined(HAS_SSL)\n#if defined(SSL_AXTLS) || defined(ESP32)\n    if(_client.isSSL && SSL_FINGERPRINT_IS_SET) {\n        if(!_client.ssl->verify(_fingerprint.c_str(), _host.c_str())) {\n            log_error(\"[WS-Client] certificate mismatch\\n\");\n            WebSockets::clientDisconnect(&_client, 1000);\n            return;\n        }\n#else\n    if(_client.isSSL && SSL_FINGERPRINT_IS_SET) {\n#endif\n    } else if(_client.isSSL && !_CA_cert) {\n#if defined(SSL_BARESSL)\n        _client.ssl->setInsecure();\n#endif\n    }\n#endif\n\n    // send Header to Server\n    sendHeader(&_client);\n}\n\nvoid WebSocketsClient::connectFailedCb() {\n    log_error(\"[WS-Client] connection to %s:%u Failed\\n\", _host.c_str(), _port);\n}\n\n\n/**\n * send heartbeat ping to server in set intervals\n */\nvoid WebSocketsClient::handleHBPing() {\n    if(_client.pingInterval == 0)\n        return;\n    uint32_t pi = millis() - _client.lastPing;\n    if(pi > _client.pingInterval) {\n        log_error(\"[WS-Client] sending HB ping\\n\");\n        if(sendPing()) {\n            _client.lastPing     = millis();\n            _client.pongReceived = false;\n        } else {\n            log_error(\"[WS-Client] sending HB ping failed\\n\");\n            WebSockets::clientDisconnect(&_client, 1000);\n        }\n    }\n}\n\n/**\n * enable ping/pong heartbeat process\n * @param pingInterval uint32_t how often ping will be sent\n * @param pongTimeout uint32_t millis after which pong should timout if not received\n * @param disconnectTimeoutCount uint8_t how many timeouts before disconnect, 0=> do not disconnect\n */\nvoid WebSocketsClient::enableHeartbeat(uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount) {\n    WebSockets::enableHeartbeat(&_client, pingInterval, pongTimeout, disconnectTimeoutCount);\n}\n\n/**\n * disable ping/pong heartbeat process\n */\nvoid WebSocketsClient::disableHeartbeat() {\n    _client.pingInterval = 0;\n}\n"
  },
  {
    "path": "src/websocket/WebSocketsClient.h",
    "content": "/**\n * @file WebSocketsClient.h\n * @date 20.05.2015\n * @author Markus Sattler\n *\n * Copyright (c) 2015 Markus Sattler. All rights reserved.\n * This file is part of the WebSockets for Arduino.\n *\n * This library is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 2.1 of the License, or (at your option) any later version.\n *\n * This library is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public\n * License along with this library; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n\n#ifndef ESP_WEBSOCKETSCLIENT_H_\n#define ESP_WEBSOCKETSCLIENT_H_\n\n#include \"WebSockets.h\"\n\nclass WebSocketsClient : protected WebSockets {\n  public:\n    typedef std::function<void(WStype_t type, uint8_t * payload, size_t length)> WebSocketClientEvent;\n\n    WebSocketsClient(void);\n    virtual ~WebSocketsClient(void);\n\n    void begin(const char * host, uint16_t port, const char * url = \"/\", const char * protocol = \"arduino\");\n    void begin(String host, uint16_t port, String url = \"/\", String protocol = \"arduino\");\n    void begin(IPAddress host, uint16_t port, const char * url = \"/\", const char * protocol = \"arduino\");\n\n#if defined(HAS_SSL)\n#ifdef SSL_AXTLS\n    void beginSSL(const char * host, uint16_t port, const char * url = \"/\", const char * fingerprint = \"\", const char * protocol = \"arduino\");\n    void beginSSL(String host, uint16_t port, String url = \"/\", String fingerprint = \"\", String protocol = \"arduino\");\n#else\n    void beginSSL(const char * host, uint16_t port, const char * url = \"/\", const uint8_t * fingerprint = NULL, const char * protocol = \"arduino\");\n    void beginSslWithCA(const char * host, uint16_t port, const char * url = \"/\", BearSSL::X509List * CA_cert = NULL, const char * protocol = \"arduino\");\n    void setSSLClientCertKey(BearSSL::X509List * clientCert = NULL, BearSSL::PrivateKey * clientPrivateKey = NULL);\n    void setSSLClientCertKey(const char * clientCert = NULL, const char * clientPrivateKey = NULL);\n#endif\n    void beginSslWithCA(const char * host, uint16_t port, const char * url = \"/\", const char * CA_cert = NULL, const char * protocol = \"arduino\");\n#endif\n\n    void beginSocketIO(const char * host, uint16_t port, const char * url = \"/socket.io/?EIO=3\", const char * protocol = \"arduino\");\n    void beginSocketIO(String host, uint16_t port, String url = \"/socket.io/?EIO=3\", String protocol = \"arduino\");\n\n#if defined(HAS_SSL)\n    void beginSocketIOSSL(const char * host, uint16_t port, const char * url = \"/socket.io/?EIO=3\", const char * protocol = \"arduino\");\n    void beginSocketIOSSL(String host, uint16_t port, String url = \"/socket.io/?EIO=3\", String protocol = \"arduino\");\n\n    void beginSocketIOSSLWithCA(const char * host, uint16_t port, const char * url = \"/socket.io/?EIO=3\", const char * CA_cert = NULL, const char * protocol = \"arduino\");\n#if defined(SSL_BARESSL)\n    void beginSocketIOSSLWithCA(const char * host, uint16_t port, const char * url = \"/socket.io/?EIO=3\", BearSSL::X509List * CA_cert = NULL, const char * protocol = \"arduino\");\n#endif\n#endif\n\n    void onEvent(WebSocketClientEvent cbEvent);\n\n    bool sendTXT(uint8_t * payload, size_t length = 0, bool headerToPayload = false);\n    bool sendTXT(const uint8_t * payload, size_t length = 0);\n    bool sendTXT(char * payload, size_t length = 0, bool headerToPayload = false);\n    bool sendTXT(const char * payload, size_t length = 0);\n    bool sendTXT(String & payload);\n    bool sendTXT(char payload);\n\n    bool sendBIN(uint8_t * payload, size_t length, bool headerToPayload = false);\n    bool sendBIN(const uint8_t * payload, size_t length);\n\n    bool sendPing(uint8_t * payload = NULL, size_t length = 0);\n    bool sendPing(String & payload);\n\n    void disconnect(void);\n\n    void setAuthorization(const char * user, const char * password);\n    void setAuthorization(const char * auth);\n\n    void setExtraHeaders(const char * extraHeaders = NULL);\n    void setReconnectInterval(unsigned long time);\n\n    void enableHeartbeat(uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount);\n    void disableHeartbeat();\n\n    bool isConnected(void);\n\n  protected:\n    String _host;\n    uint16_t _port;\n\n#if defined(HAS_SSL)\n#ifdef SSL_AXTLS\n    String _fingerprint;\n    const char * _CA_cert;\n#define SSL_FINGERPRINT_IS_SET (_fingerprint.length())\n#define SSL_FINGERPRINT_NULL \"\"\n#else\n    const uint8_t * _fingerprint;\n    BearSSL::X509List * _CA_cert;\n    BearSSL::X509List * _client_cert;\n    BearSSL::PrivateKey * _client_key;\n#define SSL_FINGERPRINT_IS_SET (_fingerprint != NULL)\n#define SSL_FINGERPRINT_NULL NULL\n#endif\n\n#endif\n    WSclient_t _client;\n\n    WebSocketClientEvent _cbEvent;\n\n    unsigned long _lastConnectionFail;\n    unsigned long _reconnectInterval;\n    unsigned long _lastHeaderSent;\n\n    void messageReceived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin);\n    void clientDisconnect(WSclient_t * client);\n    bool clientIsConnected(WSclient_t * client);\n    void handleClientData(void);\n    void sendHeader(WSclient_t * client);\n    void handleHeader(WSclient_t * client, String * headerLine);\n    void connectFailedCb();\n    void handleHBPing();    // send ping in specified intervals\n\n    void loop(void);\n    void connectedCb();\n\n    /**\n     * called for sending a Event to the app\n     * @param type WStype_t\n     * @param payload uint8_t *\n     * @param length size_t\n     */\n    virtual void runCbEvent(WStype_t type, uint8_t * payload, size_t length) {\n        if(_cbEvent) {\n            _cbEvent(type, payload, length);\n        }\n    }\n};\n\n#endif /* WEBSOCKETSCLIENT_H_ */\n"
  },
  {
    "path": "src/websocket/WebSocketsServer.cpp",
    "content": "/**\n * @file WebSocketsServer.cpp\n * @date 20.05.2015\n * @author Markus Sattler\n *\n * Copyright (c) 2015 Markus Sattler. All rights reserved.\n * This file is part of the WebSockets for Arduino.\n *\n * This library is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 2.1 of the License, or (at your option) any later version.\n *\n * This library is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public\n * License along with this library; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n\n#include \"WebSockets.h\"\n#include \"WebSocketsServer.h\"\n\nWebSocketsServerCore::WebSocketsServerCore(const String & origin, const String & protocol) {\n    _origin                 = origin;\n    _protocol               = protocol;\n    _runnning               = false;\n    _pingInterval           = 0;\n    _pongTimeout            = 0;\n    _disconnectTimeoutCount = 0;\n\n    _cbEvent = NULL;\n\n    _httpHeaderValidationFunc = NULL;\n    _mandatoryHttpHeaders     = NULL;\n    _mandatoryHttpHeaderCount = 0;\n}\n\nWebSocketsServer::WebSocketsServer(uint16_t port, const String & origin, const String & protocol)\n    : WebSocketsServerCore(origin, protocol) {\n    _port = port;\n    _server = new WEBSOCKETS_NETWORK_SERVER_CLASS(port);\n}\n\nWebSocketsServerCore::~WebSocketsServerCore() {\n    // disconnect all clients\n    close();\n\n    if(_mandatoryHttpHeaders)\n        delete[] _mandatoryHttpHeaders;\n\n    _mandatoryHttpHeaderCount = 0;\n}\n\nWebSocketsServer::~WebSocketsServer() {\n    delete _server;\n}\n\n/**\n * called to initialize the Websocket server\n */\nvoid WebSocketsServerCore::begin(void) {\n    // adjust clients storage:\n    // _clients[i]'s constructor are already called,\n    // all its members are initialized to their default value,\n    // except the ones explicitly detailed in WSclient_t() constructor.\n    // Then we need to initialize some members to non-trivial values:\n    for(int i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) {\n        _clients[i].init(i, _pingInterval, _pongTimeout, _disconnectTimeoutCount);\n    }\n\n#ifdef ESP8266\n    randomSeed(RANDOM_REG32);\n#elif defined(ESP32)\n    randomSeed(esp_random());\n#elif defined(ARDUINO_ARCH_RP2040)\n    randomSeed(rp2040.hwrand32());\n#else\n    // TODO find better seed\n    randomSeed(millis());\n#endif\n\n    _runnning = true;\n\n    log_debug(\"[WS-Server] Websocket server started\\n\");\n}\n\nvoid WebSocketsServerCore::close(void) {\n    _runnning = false;\n    disconnect();\n\n    // restore _clients[] to their initial state\n    // before next call to ::begin()\n    for(int i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) {\n        _clients[i] = WSclient_t();\n    }\n}\n\n/**\n * set callback function\n * @param cbEvent WebSocketServerEvent\n */\nvoid WebSocketsServerCore::onEvent(WebSocketServerEvent cbEvent) {\n    _cbEvent = cbEvent;\n}\n\n/*\n * Sets the custom http header validator function\n * @param httpHeaderValidationFunc WebSocketServerHttpHeaderValFunc ///< pointer to the custom http header validation function\n * @param mandatoryHttpHeaders[] const char* ///< the array of named http headers considered to be mandatory / must be present in order for websocket upgrade to succeed\n * @param mandatoryHttpHeaderCount size_t ///< the number of items in the mandatoryHttpHeaders array\n */\nvoid WebSocketsServerCore::onValidateHttpHeader(\n    WebSocketServerHttpHeaderValFunc validationFunc,\n    const char * mandatoryHttpHeaders[],\n    size_t mandatoryHttpHeaderCount) {\n    _httpHeaderValidationFunc = validationFunc;\n\n    if(_mandatoryHttpHeaders)\n        delete[] _mandatoryHttpHeaders;\n\n    _mandatoryHttpHeaderCount = mandatoryHttpHeaderCount;\n    _mandatoryHttpHeaders     = new String[_mandatoryHttpHeaderCount];\n\n    for(size_t i = 0; i < _mandatoryHttpHeaderCount; i++) {\n        _mandatoryHttpHeaders[i] = mandatoryHttpHeaders[i];\n    }\n}\n\n/*\n * send text data to client\n * @param num uint8_t client id\n * @param payload uint8_t *\n * @param length size_t\n * @param headerToPayload bool  (see sendFrame for more details)\n * @return true if ok\n */\nbool WebSocketsServerCore::sendTXT(uint8_t num, uint8_t * payload, size_t length, bool headerToPayload) {\n    if(num >= WEBSOCKETS_SERVER_CLIENT_MAX) {\n        return false;\n    }\n    if(length == 0) {\n        length = strlen((const char *)payload);\n    }\n    WSclient_t * client = &_clients[num];\n    if(clientIsConnected(client)) {\n        return sendFrame(client, WSop_text, payload, length, true, headerToPayload);\n    }\n    return false;\n}\n\nbool WebSocketsServerCore::sendTXT(uint8_t num, const uint8_t * payload, size_t length) {\n    return sendTXT(num, (uint8_t *)payload, length);\n}\n\nbool WebSocketsServerCore::sendTXT(uint8_t num, char * payload, size_t length, bool headerToPayload) {\n    return sendTXT(num, (uint8_t *)payload, length, headerToPayload);\n}\n\nbool WebSocketsServerCore::sendTXT(uint8_t num, const char * payload, size_t length) {\n    return sendTXT(num, (uint8_t *)payload, length);\n}\n\nbool WebSocketsServerCore::sendTXT(uint8_t num, String & payload) {\n    return sendTXT(num, (uint8_t *)payload.c_str(), payload.length());\n}\n\n/**\n * send text data to client all\n * @param payload uint8_t *\n * @param length size_t\n * @param headerToPayload bool  (see sendFrame for more details)\n * @return true if ok\n */\nbool WebSocketsServerCore::broadcastTXT(uint8_t * payload, size_t length, bool headerToPayload) {\n    WSclient_t * client;\n    bool ret = true;\n    if(length == 0) {\n        length = strlen((const char *)payload);\n    }\n\n    for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) {\n        client = &_clients[i];\n        if(clientIsConnected(client)) {\n            if(!sendFrame(client, WSop_text, payload, length, true, headerToPayload)) {\n                ret = false;\n            }\n        }\n        WEBSOCKETS_YIELD();\n    }\n    return ret;\n}\n\nbool WebSocketsServerCore::broadcastTXT(const uint8_t * payload, size_t length) {\n    return broadcastTXT((uint8_t *)payload, length);\n}\n\nbool WebSocketsServerCore::broadcastTXT(char * payload, size_t length, bool headerToPayload) {\n    return broadcastTXT((uint8_t *)payload, length, headerToPayload);\n}\n\nbool WebSocketsServerCore::broadcastTXT(const char * payload, size_t length) {\n    return broadcastTXT((uint8_t *)payload, length);\n}\n\nbool WebSocketsServerCore::broadcastTXT(String & payload) {\n    return broadcastTXT((uint8_t *)payload.c_str(), payload.length());\n}\n\n/**\n * send binary data to client\n * @param num uint8_t client id\n * @param payload uint8_t *\n * @param length size_t\n * @param headerToPayload bool  (see sendFrame for more details)\n * @return true if ok\n */\nbool WebSocketsServerCore::sendBIN(uint8_t num, uint8_t * payload, size_t length, bool headerToPayload) {\n    if(num >= WEBSOCKETS_SERVER_CLIENT_MAX) {\n        return false;\n    }\n    WSclient_t * client = &_clients[num];\n    if(clientIsConnected(client)) {\n        return sendFrame(client, WSop_binary, payload, length, true, headerToPayload);\n    }\n    return false;\n}\n\nbool WebSocketsServerCore::sendBIN(uint8_t num, const uint8_t * payload, size_t length) {\n    return sendBIN(num, (uint8_t *)payload, length);\n}\n\n/**\n * send binary data to client all\n * @param payload uint8_t *\n * @param length size_t\n * @param headerToPayload bool  (see sendFrame for more details)\n * @return true if ok\n */\nbool WebSocketsServerCore::broadcastBIN(uint8_t * payload, size_t length, bool headerToPayload) {\n    WSclient_t * client;\n    bool ret = true;\n    for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) {\n        client = &_clients[i];\n        if(clientIsConnected(client)) {\n            if(!sendFrame(client, WSop_binary, payload, length, true, headerToPayload)) {\n                ret = false;\n            }\n        }\n        WEBSOCKETS_YIELD();\n    }\n    return ret;\n}\n\nbool WebSocketsServerCore::broadcastBIN(const uint8_t * payload, size_t length) {\n    return broadcastBIN((uint8_t *)payload, length);\n}\n\n/**\n * sends a WS ping to Client\n * @param num uint8_t client id\n * @param payload uint8_t *\n * @param length size_t\n * @return true if ping is send out\n */\nbool WebSocketsServerCore::sendPing(uint8_t num, uint8_t * payload, size_t length) {\n    if(num >= WEBSOCKETS_SERVER_CLIENT_MAX) {\n        return false;\n    }\n    WSclient_t * client = &_clients[num];\n    if(clientIsConnected(client)) {\n        return sendFrame(client, WSop_ping, payload, length);\n    }\n    return false;\n}\n\nbool WebSocketsServerCore::sendPing(uint8_t num, String & payload) {\n    return sendPing(num, (uint8_t *)payload.c_str(), payload.length());\n}\n\n/**\n *  sends a WS ping to all Client\n * @param payload uint8_t *\n * @param length size_t\n * @return true if ping is send out\n */\nbool WebSocketsServerCore::broadcastPing(uint8_t * payload, size_t length) {\n    WSclient_t * client;\n    bool ret = true;\n    for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) {\n        client = &_clients[i];\n        if(clientIsConnected(client)) {\n            if(!sendFrame(client, WSop_ping, payload, length)) {\n                ret = false;\n            }\n        }\n        WEBSOCKETS_YIELD();\n    }\n    return ret;\n}\n\nbool WebSocketsServerCore::broadcastPing(String & payload) {\n    return broadcastPing((uint8_t *)payload.c_str(), payload.length());\n}\n\n/**\n * disconnect all clients\n */\nvoid WebSocketsServerCore::disconnect(void) {\n    WSclient_t * client;\n    for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) {\n        client = &_clients[i];\n        if(clientIsConnected(client)) {\n            WebSockets::clientDisconnect(client, 1000);\n        }\n    }\n}\n\n/**\n * disconnect one client\n * @param num uint8_t client id\n */\nvoid WebSocketsServerCore::disconnect(uint8_t num) {\n    if(num >= WEBSOCKETS_SERVER_CLIENT_MAX) {\n        return;\n    }\n    WSclient_t * client = &_clients[num];\n    if(clientIsConnected(client)) {\n        WebSockets::clientDisconnect(client, 1000);\n    }\n}\n\n/*\n * set the Authorization for the http request\n * @param user const char *\n * @param password const char *\n */\nvoid WebSocketsServerCore::setAuthorization(const char * user, const char * password) {\n    if(user && password) {\n        String auth = user;\n        auth += \":\";\n        auth += password;\n        _base64Authorization = base64_encode((uint8_t *)auth.c_str(), auth.length());\n    }\n}\n\n/**\n * set the Authorizatio for the http request\n * @param auth const char * base64\n */\nvoid WebSocketsServerCore::setAuthorization(const char * auth) {\n    if(auth) {\n        _base64Authorization = auth;\n    }\n}\n\n/**\n * count the connected clients (optional ping them)\n * @param ping bool ping the connected clients\n */\nint WebSocketsServerCore::connectedClients(bool ping) {\n    WSclient_t * client;\n    int count = 0;\n    for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) {\n        client = &_clients[i];\n        if(client->status == WSC_CONNECTED) {\n            if(ping != true || sendPing(i)) {\n                count++;\n            }\n        }\n    }\n    return count;\n}\n\n/**\n * see if one client is connected\n * @param num uint8_t client id\n */\nbool WebSocketsServerCore::clientIsConnected(uint8_t num) {\n    if(num >= WEBSOCKETS_SERVER_CLIENT_MAX) {\n        return false;\n    }\n    WSclient_t * client = &_clients[num];\n    return clientIsConnected(client);\n}\n\n/**\n * get an IP for a client\n * @param num uint8_t client id\n * @return IPAddress\n */\nIPAddress WebSocketsServerCore::remoteIP(uint8_t num) {\n    if(num < WEBSOCKETS_SERVER_CLIENT_MAX) {\n        WSclient_t * client = &_clients[num];\n        if(clientIsConnected(client)) {\n            return client->tcp->remoteIP();\n        }\n    }\n\n    return IPAddress();\n}\n\n\n// #################################################################################\n// #################################################################################\n// #################################################################################\n\n/**\n * handle new client connection\n * @param client\n */\nWSclient_t * WebSocketsServerCore::newClient(WEBSOCKETS_NETWORK_CLASS * TCPclient) {\n    WSclient_t * client;\n    // search free list entry for client\n    for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) {\n        client = &_clients[i];\n\n        // look for match to existing socket before creating a new one\n        if(clientIsConnected(client)) {\n\n        } else {\n            // state is not connected or tcp connection is lost\n            client->tcp = TCPclient;\n\n            client->isSSL = false;\n            client->tcp->setNoDelay(true);\n            client->status = WSC_HEADER;\n            #if  (LOG_LEVEL > 2)\n            IPAddress ip = client->tcp->remoteIP();\n            log_debug(\"[WS-Server][%d] new client from %d.%d.%d.%d\\n\", client->num, ip[0], ip[1], ip[2], ip[3]);\n            #endif\n\n            client->pingInterval           = _pingInterval;\n            client->pongTimeout            = _pongTimeout;\n            client->disconnectTimeoutCount = _disconnectTimeoutCount;\n            client->lastPing               = millis();\n            client->pongReceived           = false;\n\n            return client;\n        }\n    }\n    return nullptr;\n}\n\n/**\n *\n * @param client WSclient_t *  ptr to the client struct\n * @param opcode WSopcode_t\n * @param payload  uint8_t *\n * @param length size_t\n */\nvoid WebSocketsServerCore::messageReceived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin) {\n    WStype_t type = WStype_ERROR;\n\n    switch(opcode) {\n        case WSop_text:\n            type = fin ? WStype_TEXT : WStype_FRAGMENT_TEXT_START;\n            break;\n        case WSop_binary:\n            type = fin ? WStype_BIN : WStype_FRAGMENT_BIN_START;\n            break;\n        case WSop_continuation:\n            type = fin ? WStype_FRAGMENT_FIN : WStype_FRAGMENT;\n            break;\n        case WSop_ping:\n            type = WStype_PING;\n            break;\n        case WSop_pong:\n            type = WStype_PONG;\n            break;\n        case WSop_close:\n        default:\n            break;\n    }\n\n    runCbEvent(client->num, type, payload, length);\n}\n\n/**\n * Discard a native client\n * @param client WSclient_t *  ptr to the client struct contaning the native client \"->tcp\"\n */\nvoid WebSocketsServerCore::dropNativeClient(WSclient_t * client) {\n    if(!client) {\n        return;\n    }\n    if(client->tcp) {\n        if(client->tcp->connected()) {\n            client->tcp->stop();\n        }\n        delete client->tcp;\n        client->tcp = NULL;\n    }\n}\n\n/**\n * Disconnect an client\n * @param client WSclient_t *  ptr to the client struct\n */\nvoid WebSocketsServerCore::clientDisconnect(WSclient_t * client) {\n\n    if(client->isSSL && client->ssl) {\n        if(client->ssl->connected()) {\n            client->ssl->flush();\n            client->ssl->stop();\n        }\n        delete client->ssl;\n        client->ssl = NULL;\n        client->tcp = NULL;\n    }\n\n    dropNativeClient(client);\n\n    client->cUrl         = \"\";\n    client->cKey         = \"\";\n    client->cProtocol    = \"\";\n    client->cVersion     = 0;\n    client->cIsUpgrade   = false;\n    client->cIsWebsocket = false;\n    client->cWsRXsize = 0;\n    client->status = WSC_NOT_CONNECTED;\n\n    log_debug(\"[WS-Server][%d] client disconnected.\\n\", client->num);\n    runCbEvent(client->num, WStype_DISCONNECTED, NULL, 0);\n}\n\n/**\n * get client state\n * @param client WSclient_t *  ptr to the client struct\n * @return true = connected\n */\nbool WebSocketsServerCore::clientIsConnected(WSclient_t * client) {\n    if(!client->tcp) {\n        return false;\n    }\n\n    if(client->tcp->connected()) {\n        if(client->status != WSC_NOT_CONNECTED) {\n            return true;\n        }\n    } else {\n        // client lost\n        if(client->status != WSC_NOT_CONNECTED) {\n            log_debug(\"[WS-Server][%d] client connection lost.\\n\", client->num);\n            // do cleanup\n            clientDisconnect(client);\n        }\n    }\n\n    if(client->tcp) {\n        // do cleanup\n        log_debug(\"[WS-Server][%d] client list cleanup.\\n\", client->num);\n        clientDisconnect(client);\n    }\n\n    return false;\n}\n\n/**\n * Handle incoming Connection Request\n */\nWSclient_t * WebSocketsServerCore::handleNewClient(WEBSOCKETS_NETWORK_CLASS * tcpClient) {\n    WSclient_t * client = newClient(tcpClient);\n\n    if(!client) {\n        // no free space to handle client\n        log_error(\"[WS-Server] no free space new client\\n\");\n\n        // no client! => create dummy!\n        WSclient_t dummy = WSclient_t();\n        client           = &dummy;\n        client->tcp      = tcpClient;\n        dropNativeClient(client);\n        return nullptr;\n    }\n\n    WEBSOCKETS_YIELD();\n\n    return client;\n}\n\n/**\n * Handle incoming Connection Request\n */\nvoid WebSocketsServer::handleNewClients(void) {\n    while(_server->hasClient()) {\n        // store new connection\n        WEBSOCKETS_NETWORK_CLASS * tcpClient = new WEBSOCKETS_NETWORK_CLASS(_server->accept());\n        if(!tcpClient) {\n            log_error(\"[WS-Client] creating Network class failed!\");\n            return;\n        }\n        handleNewClient(tcpClient);\n\n   }\n}\n\n/**\n * Handel incomming data from Client\n */\nvoid WebSocketsServerCore::handleClientData(void) {\n    WSclient_t * client;\n    for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) {\n        client = &_clients[i];\n        if(clientIsConnected(client)) {\n            int len = client->tcp->available();\n            if(len > 0) {\n\n                switch(client->status) {\n                    case WSC_HEADER: {\n                        String headerLine = client->tcp->readStringUntil('\\n');\n                        handleHeader(client, &headerLine);\n                    } break;\n                    case WSC_CONNECTED:\n                        WebSockets::handleWebsocket(client);\n                        break;\n                    default:\n                        log_error(\"[WS-Server][%d][handleClientData] unknown client status %d\\n\", client->num, client->status);\n                        WebSockets::clientDisconnect(client, 1002);\n                        break;\n                }\n            }\n\n            handleHBPing(client);\n            handleHBTimeout(client);\n        }\n        WEBSOCKETS_YIELD();\n    }\n}\n\n\n/*\n * returns an indicator whether the given named header exists in the configured _mandatoryHttpHeaders collection\n * @param headerName String ///< the name of the header being checked\n */\nbool WebSocketsServerCore::hasMandatoryHeader(String headerName) {\n    for(size_t i = 0; i < _mandatoryHttpHeaderCount; i++) {\n        if(_mandatoryHttpHeaders[i].equalsIgnoreCase(headerName))\n            return true;\n    }\n    return false;\n}\n\n/**\n * handles http header reading for WebSocket upgrade\n * @param client WSclient_t * ///< pointer to the client struct\n * @param headerLine String ///< the header being read / processed\n */\nvoid WebSocketsServerCore::handleHeader(WSclient_t * client, String * headerLine) {\n    static const char * NEW_LINE = \"\\r\\n\";\n\n    headerLine->trim();    // remove \\r\n\n    if(headerLine->length() > 0) {\n        log_debug(\"[WS-Server][%d][handleHeader] RX: %s\\n\", client->num, headerLine->c_str());\n\n        // websocket requests always start with GET see rfc6455\n        if(headerLine->startsWith(\"GET \")) {\n            // cut URL out\n            client->cUrl = headerLine->substring(4, headerLine->indexOf(' ', 4));\n\n            // reset non-websocket http header validation state for this client\n            client->cHttpHeadersValid      = true;\n            client->cMandatoryHeadersCount = 0;\n\n        } else if(headerLine->indexOf(':') >= 0) {\n            String headerName  = headerLine->substring(0, headerLine->indexOf(':'));\n            String headerValue = headerLine->substring(headerLine->indexOf(':') + 1);\n\n            // remove space in the beginning (RFC2616)\n            if(headerValue[0] == ' ') {\n                headerValue.remove(0, 1);\n            }\n\n            if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING(\"Connection\"))) {\n                headerValue.toLowerCase();\n                if(headerValue.indexOf(WEBSOCKETS_STRING(\"upgrade\")) >= 0) {\n                    client->cIsUpgrade = true;\n                }\n            } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING(\"Upgrade\"))) {\n                if(headerValue.equalsIgnoreCase(WEBSOCKETS_STRING(\"websocket\"))) {\n                    client->cIsWebsocket = true;\n                }\n            } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING(\"Sec-WebSocket-Version\"))) {\n                client->cVersion = headerValue.toInt();\n            } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING(\"Sec-WebSocket-Key\"))) {\n                client->cKey = headerValue;\n                client->cKey.trim();    // see rfc6455\n            } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING(\"Sec-WebSocket-Protocol\"))) {\n                client->cProtocol = headerValue;\n            } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING(\"Sec-WebSocket-Extensions\"))) {\n                client->cExtensions = headerValue;\n            } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING(\"Authorization\"))) {\n                client->base64Authorization = headerValue;\n            } else {\n                client->cHttpHeadersValid &= execHttpHeaderValidation(headerName, headerValue);\n                if(_mandatoryHttpHeaderCount > 0 && hasMandatoryHeader(headerName)) {\n                    client->cMandatoryHeadersCount++;\n                }\n            }\n\n        } else {\n            log_error(\"[WS-Client][handleHeader] Header error (%s)\\n\", headerLine->c_str());\n        }\n\n        (*headerLine) = \"\";\n    } else {\n        log_debug(\"[WS-Server][%d][handleHeader] Header read fin.\\n\", client->num);\n        log_debug(\"[WS-Server][%d][handleHeader]  - cURL: %s\\n\", client->num, client->cUrl.c_str());\n        log_debug(\"[WS-Server][%d][handleHeader]  - cIsUpgrade: %d\\n\", client->num, client->cIsUpgrade);\n        log_debug(\"[WS-Server][%d][handleHeader]  - cIsWebsocket: %d\\n\", client->num, client->cIsWebsocket);\n        log_debug(\"[WS-Server][%d][handleHeader]  - cKey: %s\\n\", client->num, client->cKey.c_str());\n        log_debug(\"[WS-Server][%d][handleHeader]  - cProtocol: %s\\n\", client->num, client->cProtocol.c_str());\n        log_debug(\"[WS-Server][%d][handleHeader]  - cExtensions: %s\\n\", client->num, client->cExtensions.c_str());\n        log_debug(\"[WS-Server][%d][handleHeader]  - cVersion: %d\\n\", client->num, client->cVersion);\n        log_debug(\"[WS-Server][%d][handleHeader]  - base64Authorization: %s\\n\", client->num, client->base64Authorization.c_str());\n        log_debug(\"[WS-Server][%d][handleHeader]  - cHttpHeadersValid: %d\\n\", client->num, client->cHttpHeadersValid);\n        log_debug(\"[WS-Server][%d][handleHeader]  - cMandatoryHeadersCount: %d\\n\", client->num, client->cMandatoryHeadersCount);\n\n        bool ok = (client->cIsUpgrade && client->cIsWebsocket);\n\n        if(ok) {\n            if(client->cUrl.length() == 0) {\n                ok = false;\n            }\n            if(client->cKey.length() == 0) {\n                ok = false;\n            }\n            if(client->cVersion != 13) {\n                ok = false;\n            }\n            if(!client->cHttpHeadersValid) {\n                ok = false;\n            }\n            if(client->cMandatoryHeadersCount != _mandatoryHttpHeaderCount) {\n                ok = false;\n            }\n        }\n\n        if(_base64Authorization.length() > 0) {\n            String auth = WEBSOCKETS_STRING(\"Basic \");\n            auth += _base64Authorization;\n            if(auth != client->base64Authorization) {\n                log_error(\"[WS-Server][%d][handleHeader] HTTP Authorization failed!\\n\", client->num);\n                handleAuthorizationFailed(client);\n                return;\n            }\n        }\n\n        if(ok) {\n            log_debug(\"[WS-Server][%d][handleHeader] Websocket connection incoming.\\n\", client->num);\n\n            // generate Sec-WebSocket-Accept key\n            String sKey = acceptKey(client->cKey);\n\n            log_debug(\"[WS-Server][%d][handleHeader]  - sKey: %s\\n\", client->num, sKey.c_str());\n\n            client->status = WSC_CONNECTED;\n\n            String handshake = WEBSOCKETS_STRING(\n                \"HTTP/1.1 101 Switching Protocols\\r\\n\"\n                \"Server: arduino-WebSocketsServer\\r\\n\"\n                \"Upgrade: websocket\\r\\n\"\n                \"Connection: Upgrade\\r\\n\"\n                \"Sec-WebSocket-Version: 13\\r\\n\"\n                \"Sec-WebSocket-Accept: \");\n            handshake += sKey + NEW_LINE;\n\n            if(_origin.length() > 0) {\n                handshake += WEBSOCKETS_STRING(\"Access-Control-Allow-Origin: \");\n                handshake += _origin + NEW_LINE;\n            }\n\n            if(client->cProtocol.length() > 0) {\n                handshake += WEBSOCKETS_STRING(\"Sec-WebSocket-Protocol: \");\n                handshake += _protocol + NEW_LINE;\n            }\n\n            // header end\n            handshake += NEW_LINE;\n\n            log_debug(\"[WS-Server][%d][handleHeader] handshake %s\", client->num, (uint8_t *)handshake.c_str());\n\n            write(client, (uint8_t *)handshake.c_str(), handshake.length());\n\n            headerDone(client);\n\n            // send ping\n            WebSockets::sendFrame(client, WSop_ping);\n\n            runCbEvent(client->num, WStype_CONNECTED, (uint8_t *)client->cUrl.c_str(), client->cUrl.length());\n\n        } else {\n            handleNonWebsocketConnection(client);\n        }\n    }\n}\n\n/**\n * send heartbeat ping to server in set intervals\n */\nvoid WebSocketsServerCore::handleHBPing(WSclient_t * client) {\n    if(client->pingInterval == 0)\n        return;\n    uint32_t pi = millis() - client->lastPing;\n    if(pi > client->pingInterval) {\n        log_debug(\"[WS-Server][%d] sending HB ping\\n\", client->num);\n        if(sendPing(client->num)) {\n            client->lastPing     = millis();\n            client->pongReceived = false;\n        }\n    }\n}\n\n/**\n * enable ping/pong heartbeat process\n * @param pingInterval uint32_t how often ping will be sent\n * @param pongTimeout uint32_t millis after which pong should timout if not received\n * @param disconnectTimeoutCount uint8_t how many timeouts before disconnect, 0=> do not disconnect\n */\nvoid WebSocketsServerCore::enableHeartbeat(uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount) {\n    _pingInterval           = pingInterval;\n    _pongTimeout            = pongTimeout;\n    _disconnectTimeoutCount = disconnectTimeoutCount;\n\n    WSclient_t * client;\n    for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) {\n        client = &_clients[i];\n        WebSockets::enableHeartbeat(client, pingInterval, pongTimeout, disconnectTimeoutCount);\n    }\n}\n\n/**\n * disable ping/pong heartbeat process\n */\nvoid WebSocketsServerCore::disableHeartbeat() {\n    _pingInterval = 0;\n\n    WSclient_t * client;\n    for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) {\n        client               = &_clients[i];\n        client->pingInterval = 0;\n    }\n}\n\n////////////////////\n// WebSocketServer\n\n/**\n * called to initialize the Websocket server\n */\nvoid WebSocketsServer::begin(void) {\n    WebSocketsServerCore::begin();\n    _server->begin();\n\n    log_debug(\"[WS-Server] Server Started.\\n\");\n}\n\nvoid WebSocketsServer::close(void) {\n    WebSocketsServerCore::close();\n#if defined(ESP8266)\n    _server->close();\n#elif defined(ESP32)\n     _server->end();\n#endif\n\n}\n\n\n/**\n * called in arduino loop\n */\nvoid WebSocketsServerCore::loop(void) {\n    if(_runnning) {\n        WEBSOCKETS_YIELD();\n        handleClientData();\n    }\n}\n\n/**\n * called in arduino loop\n */\nvoid WebSocketsServer::loop(void) {\n    if(_runnning) {\n        WEBSOCKETS_YIELD();\n        handleNewClients();\n        WebSocketsServerCore::loop();\n    }\n}\n\n"
  },
  {
    "path": "src/websocket/WebSocketsServer.h",
    "content": "/**\n * @file WebSocketsServer.h\n * @date 20.05.2015\n * @author Markus Sattler\n *\n * Copyright (c) 2015 Markus Sattler. All rights reserved.\n * This file is part of the WebSockets for Arduino.\n *\n * This library is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Lesser General Public\n * License as published by the Free Software Foundation; either\n * version 2.1 of the License, or (at your option) any later version.\n *\n * This library is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Lesser General Public License for more details.\n *\n * You should have received a copy of the GNU Lesser General Public\n * License along with this library; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n *\n */\n\n#ifndef ESP_WEBSOCKETSSERVER_H_\n#define ESP_WEBSOCKETSSERVER_H_\n\n#include \"WebSockets.h\"\n\n#ifndef WEBSOCKETS_SERVER_CLIENT_MAX\n#define WEBSOCKETS_SERVER_CLIENT_MAX (5)\n#endif\n\nclass WebSocketsServerCore : protected WebSockets {\n  public:\n    WebSocketsServerCore(const String & origin = \"\", const String & protocol = \"arduino\");\n    virtual ~WebSocketsServerCore(void);\n    inline WebSocketsServerCore* getWebSocketServer() {\n        return this;\n    }\n\n    void begin(void);\n    void close(void);\n\n    typedef std::function<void(uint8_t num, WStype_t type, uint8_t * payload, size_t length)> WebSocketServerEvent;\n    typedef std::function<bool(String headerName, String headerValue)> WebSocketServerHttpHeaderValFunc;\n\n\n    void onEvent(WebSocketServerEvent cbEvent);\n    void onValidateHttpHeader(\n        WebSocketServerHttpHeaderValFunc validationFunc,\n        const char * mandatoryHttpHeaders[],\n        size_t mandatoryHttpHeaderCount);\n\n    bool sendTXT(uint8_t num, uint8_t * payload, size_t length = 0, bool headerToPayload = false);\n    bool sendTXT(uint8_t num, const uint8_t * payload, size_t length = 0);\n    bool sendTXT(uint8_t num, char * payload, size_t length = 0, bool headerToPayload = false);\n    bool sendTXT(uint8_t num, const char * payload, size_t length = 0);\n    bool sendTXT(uint8_t num, String & payload);\n\n    bool broadcastTXT(uint8_t * payload, size_t length = 0, bool headerToPayload = false);\n    bool broadcastTXT(const uint8_t * payload, size_t length = 0);\n    bool broadcastTXT(char * payload, size_t length = 0, bool headerToPayload = false);\n    bool broadcastTXT(const char * payload, size_t length = 0);\n    bool broadcastTXT(String & payload);\n\n    bool sendBIN(uint8_t num, uint8_t * payload, size_t length, bool headerToPayload = false);\n    bool sendBIN(uint8_t num, const uint8_t * payload, size_t length);\n\n    bool broadcastBIN(uint8_t * payload, size_t length, bool headerToPayload = false);\n    bool broadcastBIN(const uint8_t * payload, size_t length);\n\n    bool sendPing(uint8_t num, uint8_t * payload = NULL, size_t length = 0);\n    bool sendPing(uint8_t num, String & payload);\n\n    bool broadcastPing(uint8_t * payload = NULL, size_t length = 0);\n    bool broadcastPing(String & payload);\n\n    void disconnect(void);\n    void disconnect(uint8_t num);\n\n    void setAuthorization(const char * user, const char * password);\n    void setAuthorization(const char * auth);\n\n    int connectedClients(bool ping = false);\n\n    bool clientIsConnected(uint8_t num);\n\n    void enableHeartbeat(uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount);\n    void disableHeartbeat();\n    IPAddress remoteIP(uint8_t num);\n    void loop(void);    // handle client data only\n\n    WSclient_t * newClient(WEBSOCKETS_NETWORK_CLASS * TCPclient);\n\n  protected:\n    String _origin;\n    String _protocol;\n    String _base64Authorization;    ///< Base64 encoded Auth request\n    String * _mandatoryHttpHeaders;\n    size_t _mandatoryHttpHeaderCount;\n\n    WSclient_t _clients[WEBSOCKETS_SERVER_CLIENT_MAX];\n\n    WebSocketServerEvent _cbEvent;\n    WebSocketServerHttpHeaderValFunc _httpHeaderValidationFunc;\n\n    bool _runnning;\n\n    uint32_t _pingInterval;\n    uint32_t _pongTimeout;\n    uint8_t _disconnectTimeoutCount;\n\n    void messageReceived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin);\n\n    void clientDisconnect(WSclient_t * client);\n    bool clientIsConnected(WSclient_t * client);\n\n    void handleClientData(void);\n\n    void handleHeader(WSclient_t * client, String * headerLine);\n\n    void handleHBPing(WSclient_t * client);    // send ping in specified intervals\n\n    /**\n     * called if a non Websocket connection is coming in.\n     * Note: can be override\n     * @param client WSclient_t *  ptr to the client struct\n     */\n    virtual void handleNonWebsocketConnection(WSclient_t * client) {\n        log_debug(\"[WS-Server][%d][handleHeader] no Websocket connection close.\\n\", client->num);\n        client->tcp->write(\n            \"HTTP/1.1 400 Bad Request\\r\\n\"\n            \"Server: arduino-WebSocket-Server\\r\\n\"\n            \"Content-Type: text/plain\\r\\n\"\n            \"Content-Length: 32\\r\\n\"\n            \"Connection: close\\r\\n\"\n            \"Sec-WebSocket-Version: 13\\r\\n\"\n            \"\\r\\n\"\n            \"This is a Websocket server only!\");\n        clientDisconnect(client);\n    }\n\n    /**\n     * called if a non Authorization connection is coming in.\n     * Note: can be override\n     * @param client WSclient_t *  ptr to the client struct\n     */\n    virtual void handleAuthorizationFailed(WSclient_t * client) {\n        client->tcp->write(\n            \"HTTP/1.1 401 Unauthorized\\r\\n\"\n            \"Server: arduino-WebSocket-Server\\r\\n\"\n            \"Content-Type: text/plain\\r\\n\"\n            \"Content-Length: 45\\r\\n\"\n            \"Connection: close\\r\\n\"\n            \"Sec-WebSocket-Version: 13\\r\\n\"\n            \"WWW-Authenticate: Basic realm=\\\"WebSocket Server\\\"\"\n            \"\\r\\n\"\n            \"This Websocket server requires Authorization!\");\n        clientDisconnect(client);\n    }\n\n    /**\n     * called for sending a Event to the app\n     * @param num uint8_t\n     * @param type WStype_t\n     * @param payload uint8_t *\n     * @param length size_t\n     */\n    virtual void runCbEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {\n        if(_cbEvent) {\n            _cbEvent(num, type, payload, length);\n        }\n    }\n\n    /*\n     * Called at client socket connect handshake negotiation time for each http header that is not\n     * a websocket specific http header (not Connection, Upgrade, Sec-WebSocket-*)\n     * If the custom httpHeaderValidationFunc returns false for any headerName / headerValue passed, the\n     * socket negotiation is considered invalid and the upgrade to websockets request is denied / rejected\n     * This mechanism can be used to enable custom authentication schemes e.g. test the value\n     * of a session cookie to determine if a user is logged on / authenticated\n     */\n    virtual bool execHttpHeaderValidation(String headerName, String headerValue) {\n        if(_httpHeaderValidationFunc) {\n            // return the value of the custom http header validation function\n            return _httpHeaderValidationFunc(headerName, headerValue);\n        }\n        // no custom http header validation so just assume all is good\n        return true;\n    }\n\n    WSclient_t * handleNewClient(WEBSOCKETS_NETWORK_CLASS * tcpClient);\n\n\n    /**\n     * drop native tcp connection (client->tcp)\n     */\n    void dropNativeClient(WSclient_t * client);\n\n  private:\n    /*\n     * returns an indicator whether the given named header exists in the configured _mandatoryHttpHeaders collection\n     * @param headerName String ///< the name of the header being checked\n     */\n    bool hasMandatoryHeader(String headerName);\n};\n\nclass WebSocketsServer : public WebSocketsServerCore {\n  public:\n    WebSocketsServer(uint16_t port, const String & origin = \"\", const String & protocol = \"arduino\");\n    virtual ~WebSocketsServer(void);\n\n    void begin(void);\n    void close(void);\n    void loop(void);    // handle incoming client and client data\n  protected:\n\n    void handleNewClients(void);\n\n    uint16_t _port;\n    WEBSOCKETS_NETWORK_SERVER_CLASS * _server;\n};\n\n#endif /* WEBSOCKETSSERVER_H_ */\n"
  },
  {
    "path": "src/websocket/libb64/AUTHORS",
    "content": "libb64: Base64 Encoding/Decoding Routines\n======================================\n\nAuthors:\n-------\n\nChris Venter\tchris.venter@gmail.com\thttp://rocketpod.blogspot.com\n"
  },
  {
    "path": "src/websocket/libb64/LICENSE",
    "content": "Copyright-Only Dedication (based on United States law) \nor Public Domain Certification\n\nThe person or persons who have associated work with this document (the\n\"Dedicator\" or \"Certifier\") hereby either (a) certifies that, to the best of\nhis knowledge, the work of authorship identified is in the public domain of the\ncountry from which the work is published, or (b) hereby dedicates whatever\ncopyright the dedicators holds in the work of authorship identified below (the\n\"Work\") to the public domain. A certifier, moreover, dedicates any copyright\ninterest he may have in the associated work, and for these purposes, is\ndescribed as a \"dedicator\" below.\n\nA certifier has taken reasonable steps to verify the copyright status of this\nwork. Certifier recognizes that his good faith efforts may not shield him from\nliability if in fact the work certified is not in the public domain.\n\nDedicator makes this dedication for the benefit of the public at large and to\nthe detriment of the Dedicator's heirs and successors. Dedicator intends this\ndedication to be an overt act of relinquishment in perpetuity of all present\nand future rights under copyright law, whether vested or contingent, in the\nWork. Dedicator understands that such relinquishment of all rights includes\nthe relinquishment of all rights to enforce (by lawsuit or otherwise) those\ncopyrights in the Work.\n\nDedicator recognizes that, once placed in the public domain, the Work may be\nfreely reproduced, distributed, transmitted, used, modified, built upon, or\notherwise exploited by anyone for any purpose, commercial or non-commercial,\nand in any way, including by methods that have not yet been invented or\nconceived."
  },
  {
    "path": "src/websocket/libb64/cdecode.c",
    "content": "/*\ncdecoder.c - c source to a base64 decoding algorithm implementation\n\nThis is part of the libb64 project, and has been placed in the public domain.\nFor details, see http://sourceforge.net/projects/libb64\n*/\n\n#ifdef ESP8266\n#include <core_esp8266_features.h>\n#endif\n\n#if defined(ESP32) || defined(ARDUINO_ARCH_RP2040)\n#define CORE_HAS_LIBB64\n#endif\n\n#ifndef CORE_HAS_LIBB64\n#include \"cdecode_inc.h\"\n\nint base64_decode_value(char value_in)\n{\n\tstatic const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51};\n\tstatic const char decoding_size = sizeof(decoding);\n\tvalue_in -= 43;\n\tif (value_in < 0 || value_in > decoding_size) return -1;\n\treturn decoding[(int)value_in];\n}\n\nvoid base64_init_decodestate(base64_decodestate* state_in)\n{\n\tstate_in->step = step_a;\n\tstate_in->plainchar = 0;\n}\n\nint base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in)\n{\n\tconst char* codechar = code_in;\n\tchar* plainchar = plaintext_out;\n\tchar fragment;\n\n\t*plainchar = state_in->plainchar;\n\n\tswitch (state_in->step)\n\t{\n\t\twhile (1)\n\t\t{\n\tcase step_a:\n\t\t\tdo {\n\t\t\t\tif (codechar == code_in+length_in)\n\t\t\t\t{\n\t\t\t\t\tstate_in->step = step_a;\n\t\t\t\t\tstate_in->plainchar = *plainchar;\n\t\t\t\t\treturn plainchar - plaintext_out;\n\t\t\t\t}\n\t\t\t\tfragment = (char)base64_decode_value(*codechar++);\n\t\t\t} while (fragment < 0);\n\t\t\t*plainchar    = (fragment & 0x03f) << 2;\n\tcase step_b:\n\t\t\tdo {\n\t\t\t\tif (codechar == code_in+length_in)\n\t\t\t\t{\n\t\t\t\t\tstate_in->step = step_b;\n\t\t\t\t\tstate_in->plainchar = *plainchar;\n\t\t\t\t\treturn plainchar - plaintext_out;\n\t\t\t\t}\n\t\t\t\tfragment = (char)base64_decode_value(*codechar++);\n\t\t\t} while (fragment < 0);\n\t\t\t*plainchar++ |= (fragment & 0x030) >> 4;\n\t\t\t*plainchar    = (fragment & 0x00f) << 4;\n\tcase step_c:\n\t\t\tdo {\n\t\t\t\tif (codechar == code_in+length_in)\n\t\t\t\t{\n\t\t\t\t\tstate_in->step = step_c;\n\t\t\t\t\tstate_in->plainchar = *plainchar;\n\t\t\t\t\treturn plainchar - plaintext_out;\n\t\t\t\t}\n\t\t\t\tfragment = (char)base64_decode_value(*codechar++);\n\t\t\t} while (fragment < 0);\n\t\t\t*plainchar++ |= (fragment & 0x03c) >> 2;\n\t\t\t*plainchar    = (fragment & 0x003) << 6;\n\tcase step_d:\n\t\t\tdo {\n\t\t\t\tif (codechar == code_in+length_in)\n\t\t\t\t{\n\t\t\t\t\tstate_in->step = step_d;\n\t\t\t\t\tstate_in->plainchar = *plainchar;\n\t\t\t\t\treturn plainchar - plaintext_out;\n\t\t\t\t}\n\t\t\t\tfragment = (char)base64_decode_value(*codechar++);\n\t\t\t} while (fragment < 0);\n\t\t\t*plainchar++   |= (fragment & 0x03f);\n\t\t}\n\t}\n\t/* control should not reach here */\n\treturn plainchar - plaintext_out;\n}\n\n#endif\n"
  },
  {
    "path": "src/websocket/libb64/cdecode_inc.h",
    "content": "/*\ncdecode.h - c header for a base64 decoding algorithm\n\nThis is part of the libb64 project, and has been placed in the public domain.\nFor details, see http://sourceforge.net/projects/libb64\n*/\n\n#ifndef BASE64_CDECODE_H\n#define BASE64_CDECODE_H\n\ntypedef enum\n{\n\tstep_a, step_b, step_c, step_d\n} base64_decodestep;\n\ntypedef struct\n{\n\tbase64_decodestep step;\n\tchar plainchar;\n} base64_decodestate;\n\nvoid base64_init_decodestate(base64_decodestate* state_in);\n\nint base64_decode_value(char value_in);\n\nint base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in);\n\n#endif /* BASE64_CDECODE_H */\n"
  },
  {
    "path": "src/websocket/libb64/cencode.c",
    "content": "/*\ncencoder.c - c source to a base64 encoding algorithm implementation\n\nThis is part of the libb64 project, and has been placed in the public domain.\nFor details, see http://sourceforge.net/projects/libb64\n*/\n\n#ifdef ESP8266\n#include <core_esp8266_features.h>\n#endif\n\n#if defined(ESP32) || defined(ARDUINO_ARCH_RP2040)\n#define CORE_HAS_LIBB64\n#endif\n\n#ifndef CORE_HAS_LIBB64\n#include \"cencode_inc.h\"\n\nconst int CHARS_PER_LINE = 72;\n\nvoid base64_init_encodestate(base64_encodestate* state_in)\n{\n\tstate_in->step = step_A;\n\tstate_in->result = 0;\n\tstate_in->stepcount = 0;\n}\n\nchar base64_encode_value(char value_in)\n{\n\tstatic const char* encoding = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n\tif (value_in > 63) return '=';\n\treturn encoding[(int)value_in];\n}\n\nint base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in)\n{\n\tconst char* plainchar = plaintext_in;\n\tconst char* const plaintextend = plaintext_in + length_in;\n\tchar* codechar = code_out;\n\tchar result;\n\tchar fragment;\n\n\tresult = state_in->result;\n\n\tswitch (state_in->step)\n\t{\n\t\twhile (1)\n\t\t{\n\tcase step_A:\n\t\t\tif (plainchar == plaintextend)\n\t\t\t{\n\t\t\t\tstate_in->result = result;\n\t\t\t\tstate_in->step = step_A;\n\t\t\t\treturn codechar - code_out;\n\t\t\t}\n\t\t\tfragment = *plainchar++;\n\t\t\tresult = (fragment & 0x0fc) >> 2;\n\t\t\t*codechar++ = base64_encode_value(result);\n\t\t\tresult = (fragment & 0x003) << 4;\n\tcase step_B:\n\t\t\tif (plainchar == plaintextend)\n\t\t\t{\n\t\t\t\tstate_in->result = result;\n\t\t\t\tstate_in->step = step_B;\n\t\t\t\treturn codechar - code_out;\n\t\t\t}\n\t\t\tfragment = *plainchar++;\n\t\t\tresult |= (fragment & 0x0f0) >> 4;\n\t\t\t*codechar++ = base64_encode_value(result);\n\t\t\tresult = (fragment & 0x00f) << 2;\n\tcase step_C:\n\t\t\tif (plainchar == plaintextend)\n\t\t\t{\n\t\t\t\tstate_in->result = result;\n\t\t\t\tstate_in->step = step_C;\n\t\t\t\treturn codechar - code_out;\n\t\t\t}\n\t\t\tfragment = *plainchar++;\n\t\t\tresult |= (fragment & 0x0c0) >> 6;\n\t\t\t*codechar++ = base64_encode_value(result);\n\t\t\tresult  = (fragment & 0x03f) >> 0;\n\t\t\t*codechar++ = base64_encode_value(result);\n\n\t\t\t++(state_in->stepcount);\n\t\t\tif (state_in->stepcount == CHARS_PER_LINE/4)\n\t\t\t{\n\t\t\t\t*codechar++ = '\\n';\n\t\t\t\tstate_in->stepcount = 0;\n\t\t\t}\n\t\t}\n\t}\n\t/* control should not reach here */\n\treturn codechar - code_out;\n}\n\nint base64_encode_blockend(char* code_out, base64_encodestate* state_in)\n{\n\tchar* codechar = code_out;\n\n\tswitch (state_in->step)\n\t{\n\tcase step_B:\n\t\t*codechar++ = base64_encode_value(state_in->result);\n\t\t*codechar++ = '=';\n\t\t*codechar++ = '=';\n\t\tbreak;\n\tcase step_C:\n\t\t*codechar++ = base64_encode_value(state_in->result);\n\t\t*codechar++ = '=';\n\t\tbreak;\n\tcase step_A:\n\t\tbreak;\n\t}\n\t*codechar++ = 0x00;\n\n\treturn codechar - code_out;\n}\n\n#endif\n"
  },
  {
    "path": "src/websocket/libb64/cencode_inc.h",
    "content": "/*\ncencode.h - c header for a base64 encoding algorithm\n\nThis is part of the libb64 project, and has been placed in the public domain.\nFor details, see http://sourceforge.net/projects/libb64\n*/\n\n#ifndef BASE64_CENCODE_H\n#define BASE64_CENCODE_H\n\ntypedef enum\n{\n\tstep_A, step_B, step_C\n} base64_encodestep;\n\ntypedef struct\n{\n\tbase64_encodestep step;\n\tchar result;\n\tint stepcount;\n} base64_encodestate;\n\nvoid base64_init_encodestate(base64_encodestate* state_in);\n\nchar base64_encode_value(char value_in);\n\nint base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in);\n\nint base64_encode_blockend(char* code_out, base64_encodestate* state_in);\n\n#endif /* BASE64_CENCODE_H */\n"
  },
  {
    "path": "src/websocket/libsha1/libsha1.c",
    "content": "/* from valgrind tests */\n\n/* ================ sha1.c ================ */\n/*\nSHA-1 in C\nBy Steve Reid <steve@edmweb.com>\n100% Public Domain\n\nTest Vectors (from FIPS PUB 180-1)\n\"abc\"\n  A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D\n\"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq\"\n  84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1\nA million repetitions of \"a\"\n  34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F\n*/\n\n/* #define LITTLE_ENDIAN * This should be #define'd already, if true. */\n/* #define SHA1HANDSOFF * Copies data before messing with it. */\n\n#if !defined(ESP8266) && !defined(ESP32)\n\n#define SHA1HANDSOFF\n\n#include <stdio.h>\n#include <string.h>\n#include <stdint.h>\n\n#include \"libsha1.h\"\n\n\n#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))\n\n/* blk0() and blk() perform the initial expand. */\n/* I got the idea of expanding during the round function from SSLeay */\n#if BYTE_ORDER == LITTLE_ENDIAN\n#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \\\n    |(rol(block->l[i],8)&0x00FF00FF))\n#elif BYTE_ORDER == BIG_ENDIAN\n#define blk0(i) block->l[i]\n#else\n#error \"Endianness not defined!\"\n#endif\n#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \\\n    ^block->l[(i+2)&15]^block->l[i&15],1))\n\n/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */\n#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30);\n#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);\n#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);\n#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);\n#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);\n\n\n/* Hash a single 512-bit block. This is the core of the algorithm. */\n\nvoid SHA1Transform(uint32_t state[5], const unsigned char buffer[64])\n{\n    uint32_t a, b, c, d, e;\n    typedef union {\n        unsigned char c[64];\n        uint32_t l[16];\n    } CHAR64LONG16;\n#ifdef SHA1HANDSOFF\n    CHAR64LONG16 block[1];  /* use array to appear as a pointer */\n    memcpy(block, buffer, 64);\n#else\n    /* The following had better never be used because it causes the\n     * pointer-to-const buffer to be cast into a pointer to non-const.\n     * And the result is written through.  I threw a \"const\" in, hoping\n     * this will cause a diagnostic.\n     */\n    CHAR64LONG16* block = (const CHAR64LONG16*)buffer;\n#endif\n    /* Copy context->state[] to working vars */\n    a = state[0];\n    b = state[1];\n    c = state[2];\n    d = state[3];\n    e = state[4];\n    /* 4 rounds of 20 operations each. Loop unrolled. */\n    R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3);\n    R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7);\n    R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11);\n    R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15);\n    R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);\n    R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23);\n    R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27);\n    R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31);\n    R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35);\n    R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39);\n    R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43);\n    R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47);\n    R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51);\n    R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55);\n    R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59);\n    R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63);\n    R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67);\n    R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71);\n    R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75);\n    R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79);\n    /* Add the working vars back into context.state[] */\n    state[0] += a;\n    state[1] += b;\n    state[2] += c;\n    state[3] += d;\n    state[4] += e;\n    /* Wipe variables */\n    a = b = c = d = e = 0;\n#ifdef SHA1HANDSOFF\n    memset(block, '\\0', sizeof(block));\n#endif\n}\n\n\n/* SHA1Init - Initialize new context */\n\nvoid SHA1Init(SHA1_CTX* context)\n{\n    /* SHA1 initialization constants */\n    context->state[0] = 0x67452301;\n    context->state[1] = 0xEFCDAB89;\n    context->state[2] = 0x98BADCFE;\n    context->state[3] = 0x10325476;\n    context->state[4] = 0xC3D2E1F0;\n    context->count[0] = context->count[1] = 0;\n}\n\n\n/* Run your data through this. */\n\nvoid SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len)\n{\n    uint32_t i, j;\n\n    j = context->count[0];\n    if ((context->count[0] += len << 3) < j)\n        context->count[1]++;\n    context->count[1] += (len>>29);\n    j = (j >> 3) & 63;\n    if ((j + len) > 63) {\n        memcpy(&context->buffer[j], data, (i = 64-j));\n        SHA1Transform(context->state, context->buffer);\n        for ( ; i + 63 < len; i += 64) {\n            SHA1Transform(context->state, &data[i]);\n        }\n        j = 0;\n    }\n    else i = 0;\n    memcpy(&context->buffer[j], &data[i], len - i);\n}\n\n\n/* Add padding and return the message digest. */\n\nvoid SHA1Final(unsigned char digest[20], SHA1_CTX* context)\n{\n    unsigned i;\n    unsigned char finalcount[8];\n    unsigned char c;\n\n#if 0\t/* untested \"improvement\" by DHR */\n    /* Convert context->count to a sequence of bytes\n     * in finalcount.  Second element first, but\n     * big-endian order within element.\n     * But we do it all backwards.\n     */\n    unsigned char *fcp = &finalcount[8];\n\n    for (i = 0; i < 2; i++)\n       {\n        uint32_t t = context->count[i];\n        int j;\n\n        for (j = 0; j < 4; t >>= 8, j++)\n\t          *--fcp = (unsigned char) t;\n    }\n#else\n    for (i = 0; i < 8; i++) {\n        finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)]\n         >> ((3-(i & 3)) * 8) ) & 255);  /* Endian independent */\n    }\n#endif\n    c = 0200;\n    SHA1Update(context, &c, 1);\n    while ((context->count[0] & 504) != 448) {\n\tc = 0000;\n        SHA1Update(context, &c, 1);\n    }\n    SHA1Update(context, finalcount, 8);  /* Should cause a SHA1Transform() */\n    for (i = 0; i < 20; i++) {\n        digest[i] = (unsigned char)\n         ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255);\n    }\n    /* Wipe variables */\n    memset(context, '\\0', sizeof(*context));\n    memset(&finalcount, '\\0', sizeof(finalcount));\n}\n/* ================ end of sha1.c ================ */\n\n\n#endif\n"
  },
  {
    "path": "src/websocket/libsha1/libsha1.h",
    "content": "/* ================ sha1.h ================ */\n/*\nSHA-1 in C\nBy Steve Reid <steve@edmweb.com>\n100% Public Domain\n*/\n\n#if !defined(ESP8266) && !defined(ESP32)\n\ntypedef struct {\n    uint32_t state[5];\n    uint32_t count[2];\n    unsigned char buffer[64];\n} SHA1_CTX;\n\nvoid SHA1Transform(uint32_t state[5], const unsigned char buffer[64]);\nvoid SHA1Init(SHA1_CTX* context);\nvoid SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len);\nvoid SHA1Final(unsigned char digest[20], SHA1_CTX* context);\n\n#endif\n"
  }
]