[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [pkerspe]\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**Environment**\nPlease describe which IDE you are using (Arduino or PlatformIO) and which Version of the Library you are using. Also Please provide the Library versions of the depency libraries (ESP-FlexyStepper, AsyncWebserver, ArduinoJSON)\n\n**To Reproduce**\nPLease describe the steps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/scripts/install-arduino-core-esp32.sh",
    "content": "#!/bin/bash\n\nexport ARDUINO_ESP32_PATH=\"$ARDUINO_USR_PATH/hardware/espressif/esp32\"\nif [ ! -d \"$ARDUINO_ESP32_PATH\" ]; then\n\techo \"Installing ESP32 Arduino Core ...\"\n\tscript_init_path=\"$PWD\"\n\tmkdir -p \"$ARDUINO_USR_PATH/hardware/espressif\"\n\tcd \"$ARDUINO_USR_PATH/hardware/espressif\"\n\n\techo \"Installing Python Serial ...\"\n\tpip install pyserial > /dev/null\n\n\tif [ \"$OS_IS_WINDOWS\" == \"1\" ]; then\n\t\techo \"Installing Python Requests ...\"\n\t\tpip install requests > /dev/null\n\tfi\n\n\tif [ \"$GITHUB_REPOSITORY\" == \"espressif/arduino-esp32\" ];  then\n\t\techo \"Linking Core...\"\n\t\tln -s $GITHUB_WORKSPACE esp32\n\telse\n\t\techo \"Cloning Core Repository...\"\n\t\tgit clone https://github.com/espressif/arduino-esp32.git esp32 > /dev/null 2>&1\n\tfi\n\n\techo \"Updating Submodules ...\"\n\tcd esp32\n\tgit submodule update --init --recursive > /dev/null 2>&1\n\n\techo \"Installing Platform Tools ...\"\n\tcd tools && python get.py\n\tcd $script_init_path\n\n\techo \"ESP32 Arduino has been installed in '$ARDUINO_ESP32_PATH'\"\n\techo \"\"\nfi\n"
  },
  {
    "path": ".github/scripts/install-arduino-ide.sh",
    "content": "#!/bin/bash\n\n#OSTYPE: 'linux-gnu', ARCH: 'x86_64' => linux64\n#OSTYPE: 'msys', ARCH: 'x86_64' => win32\n#OSTYPE: 'darwin18', ARCH: 'i386' => macos\n\nOSBITS=$(arch)\nif [[ \"$OSTYPE\" == \"linux\"* ]]; then\n\texport OS_IS_LINUX=\"1\"\n\tARCHIVE_FORMAT=\"tar.xz\"\n\tif [[ \"$OSBITS\" == \"i686\" ]]; then\n\t\tOS_NAME=\"linux32\"\n\telif [[ \"$OSBITS\" == \"x86_64\" ]]; then\n\t\tOS_NAME=\"linux64\"\n\telif [[ \"$OSBITS\" == \"armv7l\" || \"$OSBITS\" == \"aarch64\" ]]; then\n\t\tOS_NAME=\"linuxarm\"\n\telse\n\t\tOS_NAME=\"$OSTYPE-$OSBITS\"\n\t\techo \"Unknown OS '$OS_NAME'\"\n\t\texit 1\n\tfi\nelif [[ \"$OSTYPE\" == \"darwin\"* ]]; then\n\texport OS_IS_MACOS=\"1\"\n\tARCHIVE_FORMAT=\"zip\"\n\tOS_NAME=\"macosx\"\nelif [[ \"$OSTYPE\" == \"cygwin\" ]] || [[ \"$OSTYPE\" == \"msys\" ]] || [[ \"$OSTYPE\" == \"win32\" ]]; then\n\texport OS_IS_WINDOWS=\"1\"\n\tARCHIVE_FORMAT=\"zip\"\n\tOS_NAME=\"windows\"\nelse\n\tOS_NAME=\"$OSTYPE-$OSBITS\"\n\techo \"Unknown OS '$OS_NAME'\"\n\texit 1\nfi\nexport OS_NAME\n\nARDUINO_BUILD_DIR=\"$HOME/.arduino/build.tmp\"\nARDUINO_CACHE_DIR=\"$HOME/.arduino/cache.tmp\"\n\nif [ \"$OS_IS_MACOS\" == \"1\" ]; then\n\texport ARDUINO_IDE_PATH=\"/Applications/Arduino.app/Contents/Java\"\n\texport ARDUINO_USR_PATH=\"$HOME/Documents/Arduino\"\nelif [ \"$OS_IS_WINDOWS\" == \"1\" ]; then\n\texport ARDUINO_IDE_PATH=\"$HOME/arduino_ide\"\n\texport ARDUINO_USR_PATH=\"$HOME/Documents/Arduino\"\nelse\n\texport ARDUINO_IDE_PATH=\"$HOME/arduino_ide\"\n\texport ARDUINO_USR_PATH=\"$HOME/Arduino\"\nfi\n\nif [ ! -d \"$ARDUINO_IDE_PATH\" ]; then\n\techo \"Installing Arduino IDE on $OS_NAME ...\"\n\techo \"Downloading 'arduino-nightly-$OS_NAME.$ARCHIVE_FORMAT' to 'arduino.$ARCHIVE_FORMAT' ...\"\n\tif [ \"$OS_IS_LINUX\" == \"1\" ]; then\n\t\twget -O \"arduino.$ARCHIVE_FORMAT\" \"https://www.arduino.cc/download.php?f=/arduino-nightly-$OS_NAME.$ARCHIVE_FORMAT\" > /dev/null 2>&1\n\t\techo \"Extracting 'arduino.$ARCHIVE_FORMAT' ...\"\n\t\ttar xf \"arduino.$ARCHIVE_FORMAT\" > /dev/null\n\t\tmv arduino-nightly \"$ARDUINO_IDE_PATH\"\n\telse\n\t\tcurl -o \"arduino.$ARCHIVE_FORMAT\" -L \"https://www.arduino.cc/download.php?f=/arduino-nightly-$OS_NAME.$ARCHIVE_FORMAT\" > /dev/null 2>&1\n\t\techo \"Extracting 'arduino.$ARCHIVE_FORMAT' ...\"\n\t\tunzip \"arduino.$ARCHIVE_FORMAT\" > /dev/null\n\t\tif [ \"$OS_IS_MACOS\" == \"1\" ]; then\n\t\t\tmv \"Arduino.app\" \"/Applications/Arduino.app\"\n\t\telse\n\t\t\tmv arduino-nightly \"$ARDUINO_IDE_PATH\"\n\t\tfi\n\tfi\n\trm -rf \"arduino.$ARCHIVE_FORMAT\"\n\n\tmkdir -p \"$ARDUINO_USR_PATH/libraries\"\n\tmkdir -p \"$ARDUINO_USR_PATH/hardware\"\n\n\techo \"Arduino IDE Installed in '$ARDUINO_IDE_PATH'\"\n\techo \"\"\nfi\n\nfunction build_sketch(){ # build_sketch <fqbn> <path-to-ino> <build-flags> [extra-options]\n    if [ \"$#\" -lt 2 ]; then\n\t\techo \"ERROR: Illegal number of parameters\"\n\t\techo \"USAGE: build_sketch <fqbn> <path-to-ino> <build-flags> [extra-options]\"\n\t\treturn 1\n\tfi\n\n\tlocal fqbn=\"$1\"\n\tlocal sketch=\"$2\"\n    local build_flags=\"$3\"\n    local xtra_opts=\"$4\"\n\tlocal win_opts=\"\"\n\tif [ \"$OS_IS_WINDOWS\" == \"1\" ]; then\n\t\tlocal ctags_version=\"ls \\\"$ARDUINO_IDE_PATH/tools-builder/ctags/\\\"\"\n\t\tlocal preprocessor_version=\"ls \\\"$ARDUINO_IDE_PATH/tools-builder/arduino-preprocessor/\\\"\"\n\t\twin_opts=\"-prefs=runtime.tools.ctags.path=$ARDUINO_IDE_PATH/tools-builder/ctags/$ctags_version -prefs=runtime.tools.arduino-preprocessor.path=$ARDUINO_IDE_PATH/tools-builder/arduino-preprocessor/$preprocessor_version\"\n\tfi\n\n\techo \"\"\n\techo \"Compiling '\\\"$(basename \"$sketch\")\\\"' ...\"\n\tmkdir -p \"$ARDUINO_BUILD_DIR\"\n\tmkdir -p \"$ARDUINO_CACHE_DIR\"\n\t\"$ARDUINO_IDE_PATH/arduino-builder -compile -logger=human -core-api-version=10810 \\\n\t\t-fqbn=$fqbn \\\n\t\t-warnings=\\\"all\\\" \\\n\t\t-tools \\\"$ARDUINO_IDE_PATH/tools-builder\\\" \\\n\t\t-tools \\\"$ARDUINO_IDE_PATH/tools\\\" \\\n\t\t-built-in-libraries \\\"$ARDUINO_IDE_PATH/libraries\\\" \\\n\t\t-hardware \\\"$ARDUINO_IDE_PATH/hardware\\\" \\\n\t\t-hardware \\\"$ARDUINO_USR_PATH/hardware\\\" \\\n\t\t-libraries \\\"$ARDUINO_USR_PATH/libraries\\\" \\\n\t\t-build-cache \\\"$ARDUINO_CACHE_DIR\\\" \\\n\t\t-build-path \\\"$ARDUINO_BUILD_DIR\\\" \\\n        -prefs=compiler.cpp.extra_flags=\\\"$build_flags\\\" \\\n\t\t$win_opts $xtra_opts \\\"$sketch\\\"\"\n}\n\nfunction count_sketches() # count_sketches <examples-path>\n{\n\tlocal examples=\"$1\"\n    rm -rf sketches.txt\n\tif [ ! -d \"$examples\" ]; then\n\t\ttouch sketches.txt\n\t\treturn 0\n\tfi\n    local sketches=$(find $examples -name *.ino)\n    local sketchnum=0\n    for sketch in $sketches; do\n\t\techo \"found $sketch\"\n        local sketchdir=$(dirname $sketch)\n        local sketchdirname=$(basename $sketchdir)\n        local sketchname=$(basename $sketch)\n        if [[ \"${sketchdirname}.ino\" != \"$sketchname\" ]]; then\n            continue\n        fi;\n        if [[ -f \"$sketchdir/.test.skip\" ]]; then\n            continue\n        fi\n        echo $sketch >> sketches.txt\n        sketchnum=($sketchnum + 1)\n    done\n    return $sketchnum\n}\n\nfunction build_sketches() # build_sketches <fqbn> <examples-path> <chunk> <total-chunks> [extra-options]\n{\n    local fqbn=$1\n    local examples=$2\n    local chunk_idex=$3\n    local chunks_num=$4\n    local xtra_opts=$5\n\n    if [ \"$#\" -lt 2 ]; then\n\t\techo \"ERROR: Illegal number of parameters\"\n\t\techo \"USAGE: build_sketches <fqbn> <examples-path> [<chunk> <total-chunks>] [extra-options]\"\n\t\treturn 1\n\tfi\n\n    if [ \"$#\" -lt 4 ]; then\n\t\tchunk_idex=\"0\"\n\t\tchunks_num=\"1\"\n\t\txtra_opts=$3\n\tfi\n\n\tif [ \"$chunks_num\" -le 0 ]; then\n\t\techo \"ERROR: Chunks count must be positive number\"\n\t\treturn 1\n\tfi\n\tif [ \"$chunk_idex\" -ge \"$chunks_num\" ]; then\n\t\techo \"ERROR: Chunk index must be less than chunks count\"\n\t\treturn 1\n\tfi\n\n\tset +e\n    count_sketches \"$examples\"\n    local sketchcount=$?\n\tset -e\n    local sketches=$(cat sketches.txt)\n    rm -rf sketches.txt\n\n    local chunk_size=( $sketchcount / $chunks_num )\n    local all_chunks=( $chunks_num * $chunk_size )\n    if [ \"$all_chunks\" -lt \"$sketchcount\" ]; then\n    \tchunk_size=( $chunk_size + 1 )\n    fi\n\n    local start_index=( $chunk_idex * $chunk_size )\n    if [ \"$sketchcount\" -le \"$start_index\" ]; then\n    \techo \"Skipping job\"\n    \treturn 0\n    fi\n\n    local end_index=( $chunk_idex + 1 ) * $chunk_size\n    if [ \"$end_index\" -gt \"$sketchcount\" ]; then\n    \tend_index=$sketchcount\n    fi\n\n    local start_num=( $start_index + 1 )\n    echo \"Found $sketchcount Sketches\";\n    echo \"Chunk Count : $chunks_num\"\n    echo \"Chunk Size  : $chunk_size\"\n    echo \"Start Sketch: $start_num\"\n    echo \"End Sketch  : $end_index\"\n\n    local sketchnum=0\n    for sketch in $sketches; do\n        local sketchdir=$(dirname $sketch)\n        local sketchdirname=$(basename $sketchdir)\n        local sketchname=$(basename $sketch)\n        if [ \"${sketchdirname}.ino\" != \"$sketchname\" ] \\\n        || [ -f \"$sketchdir/.test.skip\" ]; then\n            continue\n        fi\n        sketchnum=$(($sketchnum + 1))\n        if [ \"$sketchnum\" -le \"$start_index\" ] \\\n        || [ \"$sketchnum\" -gt \"$end_index\" ]; then\n        \tcontinue\n        fi\n        local sketchBuildFlags=\"\"\n        if [ -f \"$sketchdir/.test.build_flags\" ]; then\n            while read line; do\n                sketchBuildFlags=\"$sketchBuildFlags $line\"\n            done < \"$sketchdir/.test.build_flags\"\n        fi\n        build_sketch \"$fqbn\" \"$sketch\" \"$sketchBuildFlags\" \"$xtra_opts\"\n        local result=$?\n        if [ $result -ne 0 ]; then\n            return $result\n        fi\n    done\n    return 0\n}\n"
  },
  {
    "path": ".github/scripts/install-platformio.sh",
    "content": "#!/bin/bash\n\necho \"Installing Python Wheel ...\"\npip install wheel > /dev/null 2>&1\n\necho \"Installing PlatformIO ...\"\npip install -U platformio > /dev/null 2>&1\n\necho \"PlatformIO has been installed\"\necho \"\"\n\n\nfunction build_pio_sketch(){ # build_pio_sketch <board> <path-to-ino> <build-flags>\n    if [ \"$#\" -lt 3 ]; then\n        echo \"ERROR: Illegal number of parameters\"\n        echo \"USAGE: build_pio_sketch <board> <path-to-ino> <build-flags>\"\n        return 1\n    fi\n\n\tlocal board=\"$1\"\n\tlocal sketch=\"$2\"\n    local buildFlags=\"$3\"\n\tlocal sketch_dir=$(dirname \"$sketch\")\n\techo \"\"\n\techo \"Compiling '\"$(basename \"$sketch\")\"' ...\"\n\tpython -m platformio ci -l '.' --board \"$board\" \"$sketch_dir\" --project-option=\"board_build.partitions = huge_app.csv\" --project-option=\"build_flags=$buildFlags\"\n}\n\nfunction count_sketches() # count_sketches <examples-path>\n{\n    local examples=\"$1\"\n    rm -rf sketches.txt\n    if [ ! -d \"$examples\" ]; then\n        touch sketches.txt\n        return 0\n    fi\n    local sketches=$(find $examples -name *.ino)\n    local sketchnum=0\n    for sketch in $sketches; do\n        local sketchdir=$(dirname $sketch)\n        local sketchdirname=$(basename $sketchdir)\n        local sketchname=$(basename $sketch)\n        if [[ \"${sketchdirname}.ino\" != \"$sketchname\" ]]; then\n            continue\n        fi;\n        if [[ -f \"$sketchdir/.test.skip\" ]]; then\n            continue\n        fi\n        echo $sketch >> sketches.txt\n        sketchnum=$(($sketchnum + 1))\n    done\n    return $sketchnum\n}\n\nfunction build_pio_sketches() # build_pio_sketches <board> <examples-path> <chunk> <total-chunks>\n{\n    if [ \"$#\" -lt 2 ]; then\n        echo \"ERROR: Illegal number of parameters\"\n        echo \"USAGE: build_pio_sketches <board> <examples-path> [<chunk> <total-chunks>]\"\n        return 1\n    fi\n\n    local board=$1\n    local examples=$2\n    local chunk_idex=$3\n    local chunks_num=$4\n\n    if [ \"$#\" -lt 4 ]; then\n        chunk_idex=\"0\"\n        chunks_num=\"1\"\n    fi\n\n\tif [ \"$chunks_num\" -le 0 ]; then\n\t\techo \"ERROR: Chunks count must be positive number\"\n\t\treturn 1\n\tfi\n\tif [ \"$chunk_idex\" -ge \"$chunks_num\" ]; then\n\t\techo \"ERROR: Chunk index must be less than chunks count\"\n\t\treturn 1\n\tfi\n\n    set +e\n    count_sketches \"$examples\"\n    local sketchcount=$?\n    set -e\n    local sketches=$(cat sketches.txt)\n    rm -rf sketches.txt\n\n    local chunk_size=$(( $sketchcount / $chunks_num ))\n    local all_chunks=$(( $chunks_num * $chunk_size ))\n    if [ \"$all_chunks\" -lt \"$sketchcount\" ]; then\n    \tchunk_size=$(( $chunk_size + 1 ))\n    fi\n\n    local start_index=$(( $chunk_idex * $chunk_size ))\n    if [ \"$sketchcount\" -le \"$start_index\" ]; then\n    \techo \"Skipping job\"\n    \treturn 0\n    fi\n\n    local end_index=$(( $(( $chunk_idex + 1 )) * $chunk_size ))\n    if [ \"$end_index\" -gt \"$sketchcount\" ]; then\n    \tend_index=$sketchcount\n    fi\n\n    local start_num=$(( $start_index + 1 ))\n    echo \"Found $sketchcount Sketches\";\n    echo \"Chunk Count : $chunks_num\"\n    echo \"Chunk Size  : $chunk_size\"\n    echo \"Start Sketch: $start_num\"\n    echo \"End Sketch  : $end_index\"\n\n    local sketchnum=0\n    for sketch in $sketches; do\n        local sketchdir=$(dirname $sketch)\n        local sketchdirname=$(basename $sketchdir)\n        local sketchname=$(basename $sketch)\n        if [ \"${sketchdirname}.ino\" != \"$sketchname\" ] \\\n        || [ -f \"$sketchdir/.test.skip\" ]; then\n            continue\n        fi\n        local sketchBuildFlags=\"\"\n        if [ -f \"$sketchdir/.test.build_flags\" ]; then\n            while read line; do\n                sketchBuildFlags=\"$sketchBuildFlags $line\"\n            done < \"$sketchdir/.test.build_flags\"\n        fi\n        sketchnum=$(($sketchnum + 1))\n        if [ \"$sketchnum\" -le \"$start_index\" ] \\\n        || [ \"$sketchnum\" -gt \"$end_index\" ]; then\n        \tcontinue\n        fi\n        build_pio_sketch \"$board\" \"$sketch\" \"$sketchBuildFlags\"\n        local result=$?\n        if [ $result -ne 0 ]; then\n            return $result\n        fi\n    done\n    return 0\n}\n"
  },
  {
    "path": ".github/scripts/on-push.sh",
    "content": "#!/bin/bash\n\nset -e\n\nif [ ! -z \"$TRAVIS_BUILD_DIR\" ]; then\n\texport GITHUB_WORKSPACE=\"$TRAVIS_BUILD_DIR\"\n\texport GITHUB_REPOSITORY=\"$TRAVIS_REPO_SLUG\"\nelif [ -z \"$GITHUB_WORKSPACE\" ]; then\n\texport GITHUB_WORKSPACE=\"$PWD\"\n\texport GITHUB_REPOSITORY=\"me-no-dev/ESPAsyncWebServer\"\nfi\n\nTARGET_PLATFORM=\"$1\"\nCHUNK_INDEX=$2\nCHUNKS_CNT=$3\nBUILD_PIO=0\nif [ \"$#\" -lt 1 ]; then\n\tTARGET_PLATFORM=\"esp32\"\nfi\nif [ \"$#\" -lt 3 ] || [ \"$CHUNKS_CNT\" -le 0 ]; then\n\tCHUNK_INDEX=0\n\tCHUNKS_CNT=1\nelif [ \"$CHUNK_INDEX\" -gt \"$CHUNKS_CNT\" ]; then\n\tCHUNK_INDEX=$CHUNKS_CNT\nelif [ \"$CHUNK_INDEX\" -eq \"$CHUNKS_CNT\" ]; then\n\tBUILD_PIO=1\nfi\n\nif [ \"$BUILD_PIO\" -eq 0 ]; then\n\t# ArduinoIDE Test\n\tsource ./.github/scripts/install-arduino-ide.sh\n\n\techo \"Installing ESPAsyncWebserver ...\"\n\tgit clone https://github.com/me-no-dev/ESPAsyncWebServer \"$ARDUINO_USR_PATH/libraries/ESPAsyncWebServer\"\n\n\techo \"Installing ESP-StepperMotor-Server ...\"\n\tcp -rf \"$GITHUB_WORKSPACE\" \"$ARDUINO_USR_PATH/libraries/ESPAsyncWebServer\"\n\t\n\techo \"Installing ESP-FlexyStepper ...\"\n\tgit clone https://github.com/pkerspe/ESP-FlexyStepper \"$ARDUINO_USR_PATH/libraries/ESP-FlexyStepper\" > /dev/null 2>&1\n\techo \"Installing ArduinoJson ...\"\n\tgit clone https://github.com/bblanchon/ArduinoJson \"$ARDUINO_USR_PATH/libraries/ArduinoJson\" > /dev/null 2>&1\n\n\tif [[ \"$TARGET_PLATFORM\" == \"esp32\" ]]; then\n\t\techo \"Installing AsyncTCP ...\"\n\t\tgit clone https://github.com/me-no-dev/AsyncTCP \"$ARDUINO_USR_PATH/libraries/AsyncTCP\" > /dev/null 2>&1\n\t\tFQBN=\"espressif:esp32:esp32:PSRAM=enabled,PartitionScheme=huge_app\"\n\t\tsource ./.github/scripts/install-arduino-core-esp32.sh\n\t\techo \"BUILDING ESP32 EXAMPLES\"\n\telse\n\t\techo \"Installing ESPAsyncTCP ...\"\n\t\tgit clone https://github.com/me-no-dev/ESPAsyncTCP \"$ARDUINO_USR_PATH/libraries/ESPAsyncTCP\" > /dev/null 2>&1\n\t\tFQBN=\"esp8266com:esp8266:generic:eesz=4M1M,ip=lm2f\"\n\t\tsource ./.github/scripts/install-arduino-core-esp8266.sh\n\t\techo \"BUILDING ESP8266 EXAMPLES\"\n\tfi\n\tbuild_sketches \"$FQBN\" \"$GITHUB_WORKSPACE/examples\" \"$CHUNK_INDEX\" \"$CHUNKS_CNT\"\nelse\n\t# PlatformIO Test\n\tsource ./.github/scripts/install-platformio.sh\n\n\tpython -m platformio lib --storage-dir \"$GITHUB_WORKSPACE\" install\n\t\n\techo \"Installing ESP-FlexyStepper ...\"\n\tpython -m platformio lib -g install https://github.com/pkerspe/ESP-FlexyStepper.git > /dev/null 2>&1\n\techo \"Installing ESPAsyncWebserver ...\"\n\tpython -m platformio lib -g install https://github.com/me-no-dev/ESPAsyncWebServer.git > /dev/null 2>&1\n\n\techo \"Installing ArduinoJson ...\"\n\tpython -m platformio lib -g install https://github.com/bblanchon/ArduinoJson.git > /dev/null 2>&1\n\tif [[ \"$TARGET_PLATFORM\" == \"esp32\" ]]; then\n\t\tBOARD=\"esp32dev\"\n\t\techo \"Installing AsyncTCP ...\"\n\t\tpython -m platformio lib -g install https://github.com/me-no-dev/AsyncTCP.git > /dev/null 2>&1\n\t\techo \"BUILDING ESP32 EXAMPLES\"\n\telse\n\t\tBOARD=\"esp12e\"\n\t\techo \"Installing ESPAsyncTCP ...\"\n\t\tpython -m platformio lib -g install https://github.com/me-no-dev/ESPAsyncTCP.git > /dev/null 2>&1\n\t\techo \"BUILDING ESP8266 EXAMPLES\"\n\tfi\n\tbuild_pio_sketches \"$BOARD\" \"$GITHUB_WORKSPACE/examples\"\nfi\n"
  },
  {
    "path": ".github/workflows/push.yml",
    "content": "name: ESP Async Web Server CI\n\non:\n  push:\n    branches:\n    - master\n    - release/*\n  pull_request:\n\njobs:\n\n  build-arduino:\n    name: Arduino for ${{ matrix.board }} on ${{ matrix.os }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os: [ubuntu-latest, windows-latest, macOS-latest]\n        board: [esp32]\n    steps:\n    - uses: actions/checkout@v1\n    - name: Build Tests\n      run: bash ./.github/scripts/on-push.sh ${{ matrix.board }} 0 1\n\n  build-pio:\n    name: PlatformIO for ${{ matrix.board }} on ${{ matrix.os }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os: [ubuntu-latest, windows-latest, macOS-latest]\n        board: [esp32]\n    steps:\n    - uses: actions/checkout@v1\n    - name: Build Tests\n      run: bash ./.github/scripts/on-push.sh ${{ matrix.board }} 1 1\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\n.pio\n.pioenvs\n.piolibdeps\n.travis.yml\n.vscode\ndata\n"
  },
  {
    "path": "Documentation.md",
    "content": "# ESP-StepperMotor-Server\n\nThis Library is ment for ESP32 modules for development in the Arduino or PlatfromIO IDE. It allows an easy configuration of an ESP32 based stepper motor control server.\nIt provides multiple interfaces to configure and control 1-n stepper motors, that are connected to the ESP modules IO pins via stepper driver modules.\nBasically every stepper driver that provides an step and direction interface (IO Pins) can be controller with this library.\n\nThe ESP-StepperMotor-Server starts a webserver with a user interface and also a REST API to configure and control stepper motor drivers as well as limit switches (homing and position switches) and software controled emergency shutdown switches.\n\nAlso supports rotary encoders to control the position of connected stepper motors.\n\nFor more details visit the ESP-StepperMotor-Server [github repository](https://github.com/pkerspe/ESP-StepperMotor-Server) and check out the detailed [README.md](https://github.com/pkerspe/ESP-StepperMotor-Server/blob/master/README.md) file.\n\nCopyright (c) 2019 Paul Kerspe  -   Licensed under the [MIT license](https://github.com/pkerspe/ESP-StepperMotor-Server/blob/master/LICENSE.txt)."
  },
  {
    "path": "LICENSE.txt",
    "content": "MIT License\n\nCopyright (c) 2019 Paul Kerspe\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "README.md",
    "content": "# ESP-StepperMotor-Server - A stepper motor control server running on ESP32 modules\n[![Codacy Badge](https://api.codacy.com/project/badge/Grade/d9bf5e50c7334b71a9dfba7367b3b18e)](https://app.codacy.com/manual/pkerspe/ESP-StepperMotor-Server?utm_source=github.com&utm_medium=referral&utm_content=pkerspe/ESP-StepperMotor-Server&utm_campaign=Badge_Grade_Dashboard) \n\nTurn your ESP32 into a standalone stepper motor control server with easy to use webinterface.\nConnect one or more stepper drivers with step and direction input, and optionally some limit-switches to the IO-pins of your ESP module and control the stepper motor via a comfortable web interface, via REST API or via a serial control interface.\n\n## Table of contents\n\n* [Introduction](#introduction)\n  * [What this library is NOT](#what-this-library-is-not)\n  * [Prerequisites and dependencies](#prerequisites-and-dependencies)\n* [Setting up your ESP-StepperMotor-Server](#Setting-up-your-ESP-StepperMotor-Server)\n  * [Firmware installation](#Firmware-installation)\n    * [Using Arduino IDE](#using-arduino-ide)\n    * [Using PlatformIO](#using-platformio)\n  * [Reducing code size](#reducing-code-size)  \n  * [Installation of the web user interface](#installation-of-the-web-ui)\n  * [Connecting the hardware](#connecting-the-hardware)\n  * [Connecting rotary encoders](#connecting-rotary-encoders)\n  * [Configuration via the web user interface](#configuration-via-the-web-user-interface)\n* [Other UI masks](#other-ui-masks)\n  * [The OTA Fimware Update function](#the-ota-fimware-update-function) \n  * [The self-test page](#the-self-test-page)\n* [API Documentation](#api-documentation)\n  * [Library API documentation](#library-api-documentation)\n  * [REST API documentation](#rest-api-documentation)\n  * [Serial command line interface (CLI)](#Serial-command-line-interface)\n* [Further documentation](#further-documentation)\n* [License](#license)\n\n| <img src=\"https://github.com/pkerspe/ESP-StepperMotor-Server/raw/master/doc/images/server_startup_screen.png\" alt=\"ESP StepperMotor Server start screen\" height=\"200\"/> | <img src=\"https://github.com/pkerspe/ESP-StepperMotor-Server/raw/master/doc/images/setup_screen.png\" alt=\"ESP StepperMotor Server start screen\" height=\"200\"/> | <img src=\"https://github.com/pkerspe/ESP-StepperMotor-Server/raw/master/doc/images/motor_control_screen.png\" alt=\"ESP StepperMotor Server start screen\" height=\"200\"/> | <img src=\"https://github.com/pkerspe/ESP-StepperMotor-Server/raw/master/doc/images/rest_api_doc_screen.png\" alt=\"ESP StepperMotor Server REST API documentation screen\" height=\"200\"/> |\n| --- | --- | --- | --- |\n| home screen | configuration screen | control screen | REST API doucmentation |\n\n## Introduction\n\nThis library started as a fork for the [FlexyStepper library](https://github.com/Stan-Reifel/FlexyStepper). While the FlexyStepper Library is a general Arduino compatible library this fork had a focus on the ESP32 modules from Espressif. Soon this library became much more than a modfied version of FlexyStepper but turned into a stand-alone application to turn a regular ESP32 module into a stepper motor control server.\nThe core part that controls the stepper motor drivers is now based on a modified version of FlexyStepper, called [ESP-FlexyStepper](https://github.com/pkerspe/ESP-FlexyStepper).\nSince the ESP-32 modules contain a WIFI module they are perfectly suited for web-controlled stepper server and since they have enough memory and processing power, they are ideal as low cost, low energy consumption standalone server component, that allows configuration and controlling of one to many stepper motor drivers with limit-switches and outputs (e.g. for Relays and LEDs).\n\nOnce the ESP Stepper Motor Server has been uploaded to the ESP module, all further configuration and controlling can be done vie the web UI without the need to code another line in the Arduino or PlatformIO IDE.\n\n### What this library is NOT\n\nThis library is not ideal if you are looking for a solution to control your CNC Router. It does not support Gerber commands (GRBL) in general or fully synchronized multi axis movements (it can move multiple axis at the same time though since it generates all step signals for all connected stepper drivers asynchronous).\nIf you need a solution for you CNC project, you might want to look into the [Grbl_Esp32](https://github.com/bdring/Grbl_Esp32) (for ESP32 specifically) or [grbl](https://github.com/gnea/grbl) (for Arduino in general) Libraries. \nBut if you are looking for an easy way to setup and control one or more stepper motors independently and adding limit switches and rotary encoders to control them, then this project here might be just what you are looking for.\n\n### Prerequisites and dependencies\n\nIn order to compile your project with the ESP-StepperMotor-Server Library, the following 3rd party extensions need to be installed on your system (if you use PlatformIO all needed dependencies will be installed for you when you follow the instructions in the [Using PlatformIO](#using-platformio) section above):\n*   [ESPAsyncWebserver](https://github.com/me-no-dev/ESPAsyncWebServer)\n*   [AsyncTCP](https://github.com/me-no-dev/AsyncTCP)\n*   [ArduinoJSON (NOTE: must version 6.x, version 5 will not work)](https://arduinojson.org)\n*   [ESP-FlexyStepper](https://github.com/pkerspe/ESP-FlexyStepper)\n*   FS file system wrapper: should be installed with the ESP32 libraries already if you setup your IDE for these modules\n*   WIFI: should be installed with the ESP32 libraries already when you setup your IDE for these modules\n\nWhen using PlatformIO add these dependencies to you platformio.ini project file and let PlatformIO install the required dependencies for you:\n```ini\nlib_deps = ESP-StepperMotor-Server\n```\n\nWhen using Arduino, you need to install these libraries using the Library Manager.\n\nIf you use PlatformIO you can simply setup your project with the provided paltformio.ini file in this repository\n\n## Setting up your ESP-StepperMotor-Server\n\nTo get started you need the following elements:\n* An *ESP32* board of your choice (boards with USB-Serial Chips are recommended for the ease of programming them, other boards just work as well, yet you have to figure out how to flash the firmware yourself, since this process will not be covered in this manual)\n* A configured, Arduino compatible IDE ([Arduino](https://www.arduino.cc/en/Main/Software) or [PlatformIO](http://platformio.org))\n* A *stepper motor*\n* A *power supply* that fits to your stepper motors and drivers' specs\n* A *stepper driver* board that fits to your stepper motors specs\n\nOptional:\n* Switches for limit/homing, position and emergency stop functionality\n* Rotary encoders to control the motors directly using physical controls\n\n### Firmware installation\n\n#### Using Arduino IDE\nUSING ARDUINO IDE IS NOT SUGGESTED due to multiple issues with the dependencies and much higher manual effort to get everything running.\nIf you still want to try it, the following steps might work, but you are basically on your own here.\nDo yourself a favor and use a real IDE like Visual Studio Code with PlatformIO (it is free and way more comfortable and powerful than the Arduino IDE) :-)\n\n1. Download and install the [Arduino IDE](https://www.arduino.cc/en/main/software)\n2. open the library manager and search for \"ESP-StepperMotor-Server\", select the latest version and click on \"install\"\n3. if asked to install the required dependencies confirm to install all dependencies as well\n4. if dependency installation fails install the following dependencies manually: \n  * ArduinoJSON (the one from Benoit Blanchon)\n  * ESP-FlexyStepper\n  * at the time of writing this documentation, the following two libraries are not available via the library manager, so they need to be installed manually, if you cannot find them in the library manager:\n    * ESPAsyncWebServer: [https://github.com/me-no-dev/ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer)\n    * AsyncTCP [https://github.com/me-no-dev/AsyncTCP](https://github.com/me-no-dev/AsyncTCP)\n\n#### Using PlatformIO\n\n[PlatformIO](http://platformio.org) is an open-source ecosystem for IoT development with cross platform build system, library manager and full support for Espressif ESP32 development. It is based on the free Visual Studio Code from Microsoft, but still works on most popular host OS: Mac OS X, Windows, Linux 32/64, Linux ARM (like Raspberry Pi, BeagleBone, CubieBoard).\n\n1. Install [PlatformIO IDE](http://platformio.org/platformio-ide)\n2. Create new project using \"PlatformIO Home > New Project\"\n3. Add \"ESP-StepperMotor-Server\" to the project using [Project Configuration File `platformio.ini`](http://docs.platformio.org/page/projectconf.html) and [lib_deps](http://docs.platformio.org/page/projectconf/section_env_library.html#lib-deps) option. Here is an example platformio.ini file you can use for an ESP32 based module with ESP-StepperMotor-Server:\n```ini\n[env:esp32dev]\nplatform = espressif32\nboard = esp32dev\nframework = arduino\n\nlib_deps = ESP-StepperMotor-Server\n\nmonitor_speed = 115200\n```\n5. now you can open the main.cpp file and include the ESP-StepperMotor-Server and create an instance of the server with a minimal configuration that connects to an existing WIFI like this:\n```#include <Arduino.h>\n#include <ESPStepperMotorServer.h>\n\nESPStepperMotorServer *stepperMotorServer;\n\nconst char *wifiName= \"<your wifi ssid>\"; // enter the SSID of the wifi network to connect to\nconst char *wifiSecret = \"<your wifi password>\"; // enter the password of the the existing wifi network here\n\nvoid setup() \n{\n  Serial.begin(115200);\n  stepperMotorServer = new ESPStepperMotorServer(ESPServerRestApiEnabled | ESPServerWebserverEnabled | ESPServerSerialEnabled);\n  stepperMotorServer->setWifiCredentials(wifiName, wifiSecret);\n  stepperMotorServer->setWifiMode(ESPServerWifiModeClient); //start the server as a wifi client (DHCP client of an existing wifi network)\n  stepperMotorServer->start();\n}\n\nvoid loop() \n{\n}\n```\n*NOTE:* Replace `<your wifi ssid>` with the name of your WiFI and `<your wifi password>` with your WIFI password in the example code above\nThis is the absolute minimum example how to start the server. For further examples and more inline documentation see the provided example sketches.\n\n6. [Build/compile and upload the project](http://docs.platformio.org/en/latest/tutorials/espressif32/arduino_debugging_unit_testing.html#compiling-and-uploading-the-firmware) to your connected ESP32 module and open the console Monitor in PlatformIO (or connect to the board using your prefered serial terminal console) to see the output of the ESP-StepperMotor-Server starting up.\nIn the output on the serial monitor you should see that some output similar to this:\n```[INFO] Loading configuration file /config.json from SPIFFS\n[INFO] 2 stepper configuration entries loaded from config file\n[INFO] 2 switch configuration entries loaded from config file\n[INFO] 2 rotary encoder configuration entries loaded from config file\n[INFO] Starting ESP-StepperMotor-Server (v. 0.0.6)\n[INFO] Trying to connect to WIFI with SSID '<your wifi SSID could be here>' ....\n[INFO] Connected to network with IP address XXX.XXX.XXX.XXX\n...\n[INFO] Starting webserver on port YY\n[INFO] Webserver started, you can now open the user interface on http://XXX.XXX.XXX.XXX:YY/\n...\n```\nDuring Startup the ESP32 will check if the User Interface is installed on the ESP32s SPI Flash File System (SPIFFS). If it cannot find one or more required files it will attempt to download the files via the WIFI connection from the github repository (if the SPIFFS has been initialized at least once before). \nMore Details can be found in the section [Installation of the Web UI](#insallation-of-the-web-ui)\nOnce the UI is installed on the SPIFFS you should see the following (or a similar) output in the serial console after the Wifi Connection has been established:\n```[INFO] Listing files in root folder of SPIFFS:\n[INFO] File: /index.html (615) -1\n[INFO] File: /js/app.js.gz (266875) -1\n[INFO] File: /img/rotaryEncoderWheel.svg (13750) -1\n[INFO] File: /img/stepper.svg (22739) -1\n[INFO] File: /img/switch.svg (19709) -1\n[INFO] File: /img/logo.svg (25066) -1\n[INFO] File: /favicon.ico.gz (1565) -1\n```\n\nNow that the server has started, you can open the UI in a browser on your PC connected to the same WIFI network, by typing the IP Address you saw in the serial console before into the address bar of your browser prefixed with \"http://\".\n\nYou should now see the start screen of the ESP StepperMotor Server:\n![startup screen][startup_screen]\n\n### Reducing code size\nWhen you build the ESP-StepperMotor-Server without any optimizations it takes up a fair amount of the ESP32s flash size (in the standard OTA partition layout).\nIf you do not need all modules of the ESP-StepperMotor-Server you can use build flags to reduce the code size significantly.\nThe following build flags are supported:\n* ```ESPStepperMotorServer_COMPILE_NO_WEB```: using this flag completely disables the Web Interface, the REST API and the Websocket server. This has the biggest impact on the compiled size, since it also affects the inclusion of the external dependencies of the ESP Async WebServer and AsyncTCP libraries. If you use this flag, you will not be able to use the webinterface of the ESP Stepper motor server anymore for configuration and control of the server. You can then only interact with the server using the serial command line interface\n* ```ESPStepperMotorServer_COMPILE_NO_DEBUG```: this flag will remove all debug output and debug functions, leading to a small reduction of the size\n* ```ESPStepperMotorServer_COMPILE_NO_CLI_HELP```: this flag will remove all help texts from the Command line interface help command and by that reducing the size a bit further\n\nThe following chart shows the impact on file size when disabling one or more features (numbers base on a rather small main program as provided in the examples folder and are also just a guideline since these statistics have been create with version 0.4.6, due to changes in the dependency libraries but also due to new features in this library itself, the overall size might increase or decrease):\n![compiled size][compiled_size]\n\nTo use one or more of these build flags in PlatformIO, simply add the following line to your `platformio.ini` file of your project (e.g. to disable debug output and the help texts in the Command Line Interface):\n```\nbuild_flags = -D ESPStepperMotorServer_COMPILE_NO_DEBUG -D ESPStepperMotorServer_COMPILE_NO_CLI_HELP\n```\n\n### Installation of the Web UI\nOnce you uploaded the compiled sketch to your ESP32 (don't forget to enter your SSID and WIFI Password in the sketch!) the ESP will connect to the WIFI with the specified SSID and check if the UI files are already installed in the SPI Flash File System (SPIFFS) of the ESP. If not, it will try to download it.\nIn case your WIFI does not provide an open internet connection, you need to upload the files manually using he \"Upload File System image\" task from PlatformIO. \nMake sure your `data` folder in your platformIO project exists and contains the required files and folders for the User Interface.\nYou can find all UI files in a separate GitHub repository for the User Interface: https://github.com/pkerspe/ESP-StepperMotor-Server-UI/tree/master/data\nHint: If you want to customize the user interface of the Stepper Motor Server, you can clone the [repository](https://github.com/pkerspe/ESP-StepperMotor-Server-UI) and modify the User interface that is based on vue.js and build your own version using `npm run build` and then copy the output to the data folder of your ESP32 Stepper Motor Server project.\n\nOnce all is done you can enter the IP address of you ESP32 module in the browser and you will see the UI of the Stepper Motor Server, where you can configure the stepper motors and controls.\n\nTo figure out the IP address of your ESP-32 module, you can either check your routers admin UI or you can connect to the serial port of the ESP-32 and check the output. Once the connection to you WIFI has been established, the module will print the IP address to the serial console.\n\n### Connecting the hardware\nThe following wiring chart shows an example of a setup with an optional emergency/kill switch and two optional homing/limit switches. In a real setup, the homing switches would be attached for example to each end of a linear rail to detect when the object moved by the stepper motor reaches the end of the track.\n\n*NOTE:* in the below wiring diagram the ENABLE pin of the driver is connected to +5V, you need to check your driver if this is required or actually correct. Some drivers are DISABLED if a high signal is present on the ENABLE pin!\n\n![hardware example setup][connection_setup_example]\n(image created with [fritzing](https://fritzing.org/home/))\n\n### Connecting rotary encoders\nto connect a rotary encoder, you need to free IO Pins, one for the A and one for the B pin of your encoder.\nThe common pin on the rotary encoder needs to be connected to ground.\nThe specified IO Pins on the ESP32 will be configured as INPUT with internal pull-up resistor automatically.\nPLEASE NOTE: once you turn the rotary encoder the current stepper position of the connected stepper motor will be incremented/decremented by the number of steps you configured in the multiplier field and set as the new target position.\nThis might not be what you want it to behave, but currently it is the way it is developed. I might implement an additional option that will allow you to increase/decrease the current target position of the stepper motor with each increment on the rotary encoder.\nSimply spoken: no matter how quick you turn the rotary encoder it will always just cause the stepper to move a number of configured steps from its CURRENT physical position when the last signal from the rotary encoder has been received.\nFor now, you could change this behavior by changing the following lines in the ESPStepperMotorServer.cpp file:\n\n`stepperConfig->_flexyStepper->setTargetPositionRelativeInSteps(1 * rotaryEncoder->_stepMultiplier);` \n\nto\n\n `stepperConfig->_flexyStepper->setTargetPositionInSteps(stepperConfig->_flexyStepper->getTargetPositionInSteps() + 1 * rotaryEncoder->_stepMultiplier);` \n\nand the line\n\n`stepperConfig->_flexyStepper->setTargetPositionRelativeInSteps(newPosition);` \n\nto\n\n`stepperConfig->_flexyStepper->setTargetPositionInSteps(stepperConfig->_flexyStepper->getTargetPositionInSteps() + newPosition);`\n\n### Configuration via the web user interface\nAfter you installed everything on the hardware side, you can open the web UI to setup/configure the server.\nIn the navigation on the left side click on \"SETUP\" to open the configuration page.\n\nThe following devices can be configured:\n\n* Stepper Motors (or to be more precise \"connected stepper drivers\")\n* Switches (input signals in general): multiple types of functions are supported for the switches\n  * [Limit](https://en.wikipedia.org/wiki/Limit_switch)/Homing Switches\n  * Position Switches\n  * emergency stop / [kill switches](https://en.wikipedia.org/wiki/Kill_switch)\n  * in future versions also switches to trigger movement macros will be supported\n* [Incremental rotary encoders](https://en.wikipedia.org/wiki/Incremental_encoder) as control inputs to control the configured steppers via physical controls (you can always use the web interface or serial control commands directly to control the stepper motor position, speed etc.)\n\n![startup screen][add_stepper_dialog]\n![startup screen][add_switch_dialog]\n![startup screen][add_rotary_encoder_dialog]\n\n## Other UI masks\nBesides the regular UI views to configure the ESP-StepperMotorServer there are currently to other view you can call directly via specific paths:\n### The OTA Firmware Update function\nWhen entering the URL `http://<ip of your esp>:<port>/update` you will see the Dialog to update the firmware of the ESP-StepperMotorServer over the air (OTA).\nOnce you have the Firmware transferred to your ESP32 initially via the physical connection you do not need to reconnect it to your computer for future updates. You can always use the OTA update function to write new versions of the firmware via WIFI the connection.\nFor further details on how to create the needed firmware file please refer to your IDE and build chain documentation.\n\n*NOTE: to be able to use OTA you need to make sure that you use a flash partition pattern that allows for OTA update. Usually this is the default partition in most IDE settings, so unless you changed the partitioning manually you should be fine anyway.*\n\n### The self-test page\nWhen entering the URL `http://<ip of your esp>:<port>/selftest` you will get to a page that outputs information on your current setup / installation status of the ESP-StepperMotorServer. This page is basically for trouble shooting and will be extended over time.\nWhenever you have any issues with your installation you might want to check this page to see if any errors are shown here.\nCurrently it will output information mainly about the SPIFFS (SPI-Flash-Filesystem) status, since it seems to be a common cause for problems according to the issue list on this project. Thus, you should check it to see if any negative results are displayed.\n\n## API documentation\n\n### Library API documentation\nThe library is designed with ease of use and flexibility of the configuration options in mind. You can configure the server completely in your code or you can just use the basic example code and perform all further configuration in the Web user interface using your browser.\n\nHere is an overview of the most important API calls, in case you want to go down the road of customizing and configuration within you sketch:\n*class ESPStepperMotorServer*\nthis is the main class and the one you want to start with.\n|function|description|parameters|\n|----|----|----|\n|`ESPStepperMotorServer(byte serverMode, byte logLevel)`|constructor to create a new instance of the server. Note: Do not create more than one instance of the server in your code, to prevent issues with the interrupt service routines that are used by the server to listen to inputs.|`byte serverMode`: specify which modules of the server shall be started. Currently three modules are supported: the web interface, the REST API and the serial console control interface. The web interface relies on the REST API, so it should not be enabled without the REST API. If you do not want to connect to a WIFI or start your own AP the serial console is the only way to interact with the server. Use the constants ESPServerRestApiEnabled, ESPServerWebserverEnabled and ESPServerSerialEnabled to decide which modules to enable. e.g. `stepperMotorServer = new ESPStepperMotorServer(ESPServerRestApiEnabled | ESPServerWebserverEnabled | ESPServerSerialEnabled);`<br><br>`byte logLevel`: *optional* parameter to set the log level during instantiation. Default is INFO. Use one of `ESPServerLogLevel_DEBUG`, `ESPServerLogLevel_INFO`, `ESPServerLogLevel_WARNING`|\n|`void setHttpPort(int portNumber)`|Set the http port number on which the server should list for requests for the web interface and the REST API. Only used if the server is started in the ESPServerWebserverEnabled or ESPServerRestApiEnabled (or both) mode|`int portNumber`: the port number to start the webserver on. Only needed if the ESPServerRestApiEnabled or ESPServerWebserverEnabled module is enabled. Default port is 80|\n|`void setAccessPointName(const char *accessPointSSID)`|Set the name of the Access Point (SSID) to create by the server|`const char *accessPointSSID`: the name of the access point to open up. Only used when the server is configured to start in AP mode by using `setWifiMode(ESPServerWifiModeAccessPoint)` is used. The default value is 'ESP-StepperMotor-Server'|\n|`void setAccessPointPassword(const char *accessPointPassword)`|Set the password needed to connect to the Access Point created by the server. Only used when the server is configured to start in AP mode by using `setWifiMode(ESPServerWifiModeAccessPoint)` is used. The default value is 'Aa123456'|`const char *accessPointPassword`|\n|`void setWifiCredentials(const char *ssid, const char *pwd)`|Set the wifi credentials for your local WIFI to connect to. Only used when the server is configured in WIFI Client mode by using `setWifiMode(ESPServerWifiModeClient)`|`const char *ssid`: the name of the WIFI to connect to. `const char *pwd`: the password for the WIFI to connect to|\n|`void setWifiMode(byte wifiMode)`|Set the WIFI mode to start the server in. It can either operate in WIFI Client or Access Point mode. As a client it connects to an existing WIFI network. Requires the WIFI access credentials to be set using the `setWifiCredentials` function. In Access Point mode, the server opens it's own WIFI network and waits for clients to connect. You can specify the AP Name and Password using the `setAccessPointName` and `setAccessPointPassword` functions (otherwise default values will be used, for details see documentation of the mentioned functions and parameters)|`byte wifiMode`: the mode to use. Supported values should be provided with the constants `ESPServerWifiModeClient` and `ESPServerWifiModeAccessPoint`. To disable the WIFI modes completely use `ESPServerWifiDisabled`|\n|`void setStaticIpAddress(IPAddress staticIP, IPAddress gatewayIP, IPAddress subnetMask, IPAddress dns1, IPAddress dns2)`|Set a static IP Address, gateway IP and subnet mask. The primary and secondary DNS Server arguments are optional and can be omitted|\n|`void printWifiStatus()`|prints current WIFI connection details to the serial console. This should be called only AFTER the server has been started, since the connection to the WIFI network or setup of an Access Point is only done after calling the `start()` function of the server|none|\n|`int addOrUpdateStepper(ESPStepperMotorServer_StepperConfiguration *stepper, int stepperIndex = -1)`|Add a new stepper motor to the server configuration or update an existing one with a given id. This function returns the ID of the newly created stepper configuration for further reference. If an existing configuration has been updated, the id of the updated configuration is returned (same as provided `stepperIndex` parameter value)|`ESPStepperMotorServer_StepperConfiguration *stepper,`: pointer to a configured `ESPStepperMotorServer_StepperConfiguration` instance. Optional `int stepperIndex`: if set this parameter indicates the configuration ID of an existing stepper configuration, that shall be overwritten/replace with the new one supplied using the `stepper` parameter|\n|`int addOrUpdatePositionSwitch(ESPStepperMotorServer_PositionSwitch *posSwitchToAdd, int switchIndex = -1)`|Add a new position switch to the server configuration or update an existing one with a given id. This function returns the ID of the newly created position switch configuration for further reference. If an existing configuration has been updated, the id of the updated configuration is returned (same as provided `switchIndex` parameter value)|`ESPStepperMotorServer_PositionSwitch *posSwitchToAdd,`: pointer to a configured `ESPStepperMotorServer_PositionSwitch` instance. Optional `int switchIndex`: if set this parameter indicates the configuration ID of an existing switch configuration, that shall be overwritten/replace with the new one supplied using the `posSwitchToAdd` parameter|\n|`int addOrUpdateRotaryEncoder(ESPStepperMotorServer_RotaryEncoder *rotaryEncoder, int encoderIndex = -1)`|Add a new rotary encoder to the server configuration or update an existing one with a given id. This function returns the ID of the newly created encoder configuration for further reference. If an existing configuration has been updated, the id of the updated configuration is returned (same as provided `encoderIndex` parameter value)|`ESPStepperMotorServer_RotaryEncoder *rotaryEncoder,`: pointer to a configured `ESPStepperMotorServer_RotaryEncoder` instance. Optional `int encoderIndex`: if set this parameter indicates the configuration ID of an existing encoder configuration, that shall be overwritten/replace with the new one supplied using the `rotaryEncoder` parameter|\n|`void removePositionSwitch(int positionSwitchIndex)`|Remove/Delete a previously configured position switch|`int positionSwitchIndex`: the id of the configuration to delete|\n|`void removeStepper(byte stepperConfigurationIndex)`|Remove/Delete a previously configured stepper motor|`int stepperConfigurationIndex`: the id of the configuration to delete|\n|`void removeRotaryEncoder(byte rotaryEncoderConfigurationIndex)`|Remove/Delete a previously configured encoder|`int rotaryEncoderConfigurationIndex`: the id of the configuration to delete|\n|`void printPositionSwitchStatus()`|Print the current switch status to the serial console as JSON formatted string. This contains detailed information for each registered switch along with its current status. e.g. `{\"settings\":{\"positionSwitchCounterLimit\":10,\"statusRegisterCounter\":2},\"switchStatusRegister\":[{\"statusRegisterIndex\":0,\"status\":\"00000000\"},{\"statusRegisterIndex\":1,\"status\":\"00000000\"}],\"positionSwitches\":[{\"id\":0,\"name\":\"Limit 1 X-Axis\",\"ioPin\":14,\"position\":-1,\"stepperId\":0,\"active\":1,\"type\":{\"pinMode\":\"Active Low\"}},{\"id\":1,\"name\":\"Limit 2 X-Axis\",\"ioPin\":15,\"position\":200000,\"stepperId\":1,\"active\":1,\"type\":{\"pinMode\":\"Active Low\"}}]}`|none|\n|`void start()`|start the ESP-StepperMotor-Server. Calling this function lets all the magic begin. Depending on the configuration this includes the following tasks: starting the webserver, starting the REST API, starting the serial console command interface, setting up the configured IO pins, registering interrupt handlers for all switches and rotary encoders and printing out a whole bunch of information on the serial console to inform you about the progress of the startup sequence|none|\n|`void stop()`|start the ESP-StepperMotor-Server. Basically un-do all the stuff that has been done during the start-up sequence|none|\n|`byte getPositionSwitchStatus(int positionSwitchIndex)`|Get the switch status of a specific switch. Return 1 if the switch is currently triggered, 0 if it is not|`int positionSwitchIndex`|\n|`void getButtonStatusRegister(byte buffer[ESPServerSwitchStatusRegisterCount])`|Populate the given byte buffer with the switch status for each configured button (1 = button is triggered, 0 = button is not triggered)|`byte buffer[]`: a byte array to populate with the status register contents for all configured switches (basically the current switch status in regards to being active/inactive)|\n|`String getIpAddress()`|get the current IP address of the server. Only Available if connected to a WIFI or if started in AP mode|none|\n|`ESPStepperMotorServer_Configuration *getCurrentServerConfiguration()`|get the pointer of the ESPStepperMotorServer_Configuration instance that represents the current server complete configuration|none|\n|`ESPStepperMotorServer_CLI *getCLIHandler() const`|get the pointer of the serial CLI handler instance. This can be used to register custom CLI commands.|none|\n\n### REST API documentation\nBesides the web-based User Interface the ESP StepperMotor Server offers a REST API to control all aspects of the server that can also be controlled via the web UI (in fact the web UI uses the REST API for all operations).\n\nThe following is an excerpt of the endpoints being provided:\n| METHOD | PATH | DESCRIPTION |\n|---|---|---|\n|GET |`/api/status`|get the current stepper server status report including the following information: version string of the server, wifi information (wifi mode, IP address), spiffs information (total space and free space)|\n|POST |`/api/steppers/returnhome`|endpoint to trigger homing of the stepper motor. This is a non-blocking call, meaning the API will directly return even though the stepper motor is still performing the homing movement.<br /><br />*IMPORTANT:* this function should only be called if you previously configured a homing / limit switch for this stepper motor, otherwise the stepper will start jogging for a long time (a default limit of 2000000000 steps is configured, but can be overwritten with a POST parameter) before coming to a halt.<br/><br />*Required post parameters:*<br />__id__: the id of the stepper motor to perform the homing command for)<br />__speed__: the speed in steps per second to perform the homing command with<br /><br />*Optional POST parameters:*<br/>__switchId__: define the configuration id of the position switch to use as limit switch. __NOTE__: this switch should be assigned to the stepper motor, so you should not provide the id of a position switch that is not linked to the stepper driver defined in the mandatory __id__ parameter. Ideally the switch is also configured as a limit type switch.<br />__direction__: the homing direction for the stepper movement. Could be either 1 or -1. If parameter is not given the direction will be determined from the limit switch configuration (depending on the switch type \"begin\" or \"end\")<br/>__accel__: the acceleration for the homing procedure in steps/sec^2, if omitted the previously defined acceleration in the flexy stepper instance will be used<br />__maxSteps__: this parameter defines the maximum number of steps to perform before cancelling the homing procedure. This is kind of a safeguard to prevent endless spinning of the stepper motor. Defaults to 2000000000 steps|    \n|POST|`/api/steppers/moveby`|endpoint to set a new RELATIVE target position for the stepper motor in either mm, revs or steps. Required post parameters: id, unit, value. Optional post parameters: speed, acel, decel|\n|POST |`/api/steppers/position`|endpoint to set a new absolute target position for the stepper motor in either mm, revs or steps. Required post parameters: id, unit, value. Optional post parameters: speed, acel, decel|\n| GET |`/api/steppers` or `/api/steppers?id=<id>`|endpoint to list all configured steppers or a specific one if \"id\" query parameter is given\n|DELETE|`/api/steppers?id=<id>`|delete an existing stepper configuration entry|\n|POST |`/api/steppers`|add a new stepper configuration entry|\n| PUT|`/api/steppers?id=<id>`|update an existing stepper configuration entry|\n| GET |`/api/switches/status` or `/api/switches/status?id=<id>`|get the current switch status (active, inactive) of either one specific switch or all switches (returned as a bit mask in MSB order)|\n| GET |`/api/switches` or `/api/switches?id=<id>`|endpoint to list all position switch configurations or a specific configuration if the \"id\" query parameter is given|\n| POST |`/api/switches`|endpoint to add a new switch configuration|\n| PUT |`/api/switches?id=<id>`|endpoint to update an existing switch configuration|\n| DELETE |`/api/switches?id=<id>`|delete a specific switch configuration|\n| GET |`/api/config`|get the JSON representation of the current server configuration with all configured steppers, switches and encoders. This is the in-memory configuration (current is-state) which might differ from the persisted configuration. To persist the current configuration see `GET /api/config/save`|\n| GET |`/api/config/save`|save the current in-memory configuration of the server to the [SPIFFS](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/spiffs.html) into the `config.json` file. You can download this file using the URL schema `http://<ip of your esp>:<port>/config.json`. Calling this endpoint persists the configuration in its current state to survive also power loss / reboot / reset of the server. This should be called whenever you perform any changes on the configuration that you want to keep even after a reboot/reset of the ESP|\n\nTo get a full list of endpoints navigate to the about page in the web UI and click on the REST API documentation link\n![about screen][about_screen]\n\n### Serial command line interface\nOnce started, the stepper server offers a CLI (command line interface) on the serial port to control most of the functions that can also be controlled via the web interface or REST API and some additional functions.\nOnce the server is started (and the CLI has not been disabled in the constructor) you will see some log output on the console and also the following line:\n`[INFO] Command Line Interface started, registered XX commands. Type 'help' to get a list of all supported commands`\nNow you can input commands via the serial port.\nTo get a list of all available commands type `help` in the serial console and press enter.\nThe output should look like this (depending on the version and how up2date this manual is, you might see some more commands):\n````-------- ESP-StepperMotor-Server-CLI Help -----------\nThe following commands are available:\n\n<command> [<shortcut>]: <description>\n\nBuilt in commands:\nhelp [h]:               show a list of all available commands\nmoveby [mb]*:           move by a specified number of units. requires the id of the stepper to move, the amount of movement and also optional the unit for the movement (mm, steps, revs). If no unit is specified steps will be assumed as unit. Optionally you can also set the speed in steps/second, acceleration and deceleration, each in steps/second/second). Set speeds, acceleration and deceleration are rememebered until overwritten again. E.g. mb=0&v:-100&u:mm&s:200 to move the stepper with id 0 by -100 mm with a speed of 200 steps per second\nmoveto [mt]*:           move to an absolute position. requires the id of the stepper to move, the amount of movement and also optional the unit for the movement (mm, steps, revs). If no unit is specified steps will be assumed as unit. Optionally you can also set the speed in steps/second, acceleration and deceleration, each in steps/second/second). Set speeds, acceleration and deceleration are rememebered until overwritten again. E.g. mt=0&v:100&u:revs&a:100 to move the stepper with id 0 to the absolute position at 100 revolutions with an acceleration of 100 steps per second^2\nconfig [c]:             print the current configuration to the console as JSON formatted string\nemergencystop [es]:     trigger emergency stop for all connected steppers. This will clear all target positions and stop the motion controller module immediately. In order to proceed normal operation after this command has been issued, you need to call the `revokeemergencystop` [res] command\nrevokeemergencystop [res]:      revoke a previously triggered emergency stop. This must be called before any motions can proceed after a call to the emergency-stop command\nposition [p]*:          get the current position of a specific stepper or all steppers if no explicit index is given (e.g. by calling 'pos' or 'pos=&u:mm'). If no parameter for the unit is provided, will return the position in steps. Requires the ID of the stepper to get the position for as parameter and optional the unit using 'u:mm'/'u:steps'/'u:revs'. E.g.: p=0&u:steps to return the current position of stepper with id = 0 with unit 'steps'\nvelocity [v]*:          get the current velocity of a specific stepper or all steppers if no explicit index is given (e.g. by calling 'pos' or 'pos=&u:mm'). If no parameter for the unit is provided, will return the position in steps. Requires the ID of the stepper to get the velocity for as parameter and optional the unit using 'u:mm'/'u:steps'/'u:revs'. E.g.: v=0&u:mm to return the velocity in mm per second of stepper with id = 0\nremoveswitch [rsw]*:    remove an existing switch configuration. E.g. rsw=0 to remove the switch with the ID 0\nremovestepper [rs]*:    remove and existing stepper configuration. E.g. rs=0 to remove the stepper config with the ID 0\nremoveencoder [re]*:    remove an existing rotary encoder configuration. E.g. re=0 to remove the encoder with the ID 0\nreboot [r]:             reboot the ESP\nsave [s]:               save the current configuration to the SPIFFS in config.json\nstop [st]:              stop the stepper server (also stops the CLI!)\nloglevel [ll]*:         set or get the current log level for serial output. valid values to set are: 1 (Warning) - 4 (ALL). E.g. to set to log level DEBUG use sll=3, to get the current log-level call without any parameter\nserverstatus [ss]:      print status details of the server as JSON formatted string\nswitchstatus [pss]:     print the status of all input switches as JSON formatted string\nsetapname [san]*:       set the name of the access point to be opened up by the esp (if in AP mode)\nsetappwd [sap]*:        set the password for the access point to be opened by the esp\nsethttpport [shp]*:     set the http port to listen for for the web interface\nsetwifissid [sws]*:     set the SSID of the WIFI to connect to (if in client mode)\nsetwifipwd [swp]*:      set the password of the Wifi network to connect to\")\n\ncommands marked with a * require input parameters.\nParameters are provided with the command separated by a = for the primary parameter.\nSecondary parameters are provided in the format '&<parametername>:<parametervalue'\n````\n\nIn general, there are two types of commands:\nCommands with parameters, and commands without parameters. Some command support also optional parameters.\nEach command also has a shortcut (listed in the `help` output in `[]`) that can be used if you are not so much into typing a lot.\nCommands with parameters must be invoked following this schema:\n`<commandname or shortcut name>=<primary parameter>&<optional additional parameter name>:<optional additional parameter value>`\nAn example for a command with multiple parameters is the `moveto` command. The shortcut for this command is `mt`.\nThe command supports six parameters: the id of the stepper to move (primary parameter), the amount/value for the movement (v parameter), the unit (u parameter) for the movement (mm, steps or revolutions), the speed (s parameter) in steps per second, the acceleration (a parameter) in steps per second per second and the deceleration (d parameter) in steps per second per second.\nExample:\nIf you want to move the configured stepper motor with the id 0 by 10 revolutions with a speed of 100 steps per second the command looks as follows:\n`mt=0&v:10&u:revs&s:100`\n\n### Further documentation\nfor further details have a look at \n* the provided example files / projects in the [examples folder](https://github.com/pkerspe/ESP-StepperMotor-Server/tree/master/examples) of this repository\n\n## License\nCopyright (c) 2019 Paul Kerspe - Licensed under the MIT license.\n\n[startup_screen]: https://github.com/pkerspe/ESP-StepperMotor-Server/raw/master/doc/images/server_startup_screen.png \"ESP StepperMotor Server startup screen\"\n[add_stepper_dialog]: https://github.com/pkerspe/ESP-StepperMotor-Server/raw/master/doc/images/add_stepper_config_dialog.png \"The dialog to add a new stepper motor configuration\"\n[add_switch_dialog]: https://github.com/pkerspe/ESP-StepperMotor-Server/raw/master/doc/images/add_switch_dialog.png \"The dialog to add a new switch (input signal) configuration\"\n[add_rotary_encoder_dialog]: https://github.com/pkerspe/ESP-StepperMotor-Server/raw/master/doc/images/add_rotary_encoder_dialog.png \"The dialog to add a new rotary encoder to control a stepper\"\n[about_screen]: https://github.com/pkerspe/ESP-StepperMotor-Server/raw/master/doc/images/about_screen.png \"The about screen with the link to the REST API documentation\"\n[connection_setup_example]: https://github.com/pkerspe/ESP-StepperMotor-Server/raw/master/doc/images/connection_setup_example.png \"Example setup wiring diagram\"\n[compiled_size]: https://github.com/pkerspe/ESP-StepperMotor-Server/raw/master/doc/images/compiled_size.png \"Compile size comparison of different build options\"\n"
  },
  {
    "path": "examples/Example1_BasicESPStepperMotorServer/Example1_BasicESPStepperMotorServer.ino",
    "content": "//      ******************************************************************\n//      *     Simple example for starting the stepper motor server       *\n//      *            Paul Kerspe                31.5.2020                *\n//      ******************************************************************\n//\n// This is the simplest example of how to start the ESP Stepper Motor Server with the Webinterface to perform all setup steps via the Web UI\n//\n// This library requires that your stepper motor be connected to the ESP32\n// using an external driver that has a \"Step and Direction\" interface.\n//\n// For all driver boards, it is VERY important that you set the motor\n// current before running the example. This is typically done by adjusting\n// a potentiometer on the board or using dip switches.\n// Read the driver board's documentation to learn how to configure the driver\n//\n// all you need to do, to get started with this example, is fill in your wifi credentials in lines 26/27, then compile and upload to your ESP32.\n// In order to use the Web Interface of the server, you need to upload the contents of the \"data\" folder in this example to the SPIFFS of your ESP32\n//\n// for a detailed manual on how to use this library please visit: https://github.com/pkerspe/ESP-StepperMotor-Server/blob/master/README.md\n// ***********************************************************************\n#include <Arduino.h>\n#include <ESPStepperMotorServer.h>\n\nESPStepperMotorServer *stepperMotorServer;\n\nconst char *wifiName= \"<your wifi ssid>\"; // enter the SSID of the wifi network to connect to\nconst char *wifiSecret = \"<your wifi password>\"; // enter the password of the the existing wifi network here\n\nvoid setup() \n{\n  // start the serial interface with 115200 baud\n  // IMPORTANT: the following line is important, since the server relies on the serial console for log output \n  // Do not remove this line! (you can modify the baud rate to your needs though, but keep in mind, that slower baud rates might cause timing issues especially if you set the log level to DEBUG)\n  Serial.begin(115200);\n  // now create a new ESPStepperMotorServer instance (this must be done AFTER the Serial interface has been started)\n  // In this example We create the server instance with all modules activated and log level set to INFO (which is the default, you can also use ESPServerLogLevel_DEBUG to set it to debug instead)\n  stepperMotorServer = new ESPStepperMotorServer(ESPServerRestApiEnabled | ESPServerWebserverEnabled | ESPServerSerialEnabled, ESPServerLogLevel_INFO);\n  // connect to an existing WiFi network. Make sure you set the vairables wifiName and wifiSecret to match you SSID and wifi pasword (see above before the setup function)\n  stepperMotorServer->setWifiCredentials(wifiName, wifiSecret);\n  stepperMotorServer->setWifiMode(ESPServerWifiModeClient); //start the server as a wifi client (DHCP client of an existing wifi network)\n\n  // NOTE: if you want to start the server in a stand alone mode that opens a wifi access point, then comment out the above two lines and uncomment the following line\n  // stepperMotorServer->setWifiMode(ESPServerWifiModeAccessPoint);\n  // you can define the AP name and the password using the following two lines, otherwise the defaults will be used (Name: ESP-StepperMotor-Server, password: Aa123456)\n  // stepperMotorServer->setAccessPointName(\"<ap-name>\");\n  // stepperMotorServer->setAccessPointPassword(\"<ap password must be longer than 8 characters>\");\n\n  //start the server\n  stepperMotorServer->start();\n  // the server will now connect to the wifi and start the webserver, rest API and serial command line interface.\n  // check the serial console for more details like the URL of the WebInterface\n}\n\nvoid loop() \n{\n  //put your code here\n}\n"
  },
  {
    "path": "examples/Example2_StepperMotorServer_with_hardcoded_config/Example2_StepperMotorServer_with_hardcoded_config.ino",
    "content": "//      *****************************************************\n//      *     Example how to configure via code             *\n//      *            Paul Kerspe                31.5.2020   *\n//      *****************************************************\n//\n// This example shows how to configure the stepper server via code, to create a hardcoded configuation directly in the firmware.\n// this could be useful for example if you want to disable the Webinterface and REST API and solely control the steppers via the command line\n// and if you want to bundle a static configuration with the actual firmware to burn to multiple devices\n// this example will NOT start the webserver and REST API and will also not connect to a WiFi or open an access point.\n// If you intend to create a real server that can be controller via a User Interface or REST API see Example 1.\n// For this example you do not need SPIFFS and should not have a config.json uploaded to SPIFFS (since it would be loaded otherwise and possibly interfer with the coded config)\n//\n// This library requires that your stepper motor be connected to the ESP32\n// using an external driver that has a \"Step and Direction\" interface.\n//\n// For all driver boards, it is VERY important that you set the motor\n// current before running the example. This is typically done by adjusting\n// a potentiometer on the board or using dip switches.\n// Read the driver board's documentation to learn how to configure the driver\n//\n// for a detailed manual on how to use this library please visit: https://github.com/pkerspe/ESP-StepperMotor-Server/blob/master/README.md\n// ***********************************************************************\n#include <Arduino.h>\n#include <ESPStepperMotorServer.h>\n#include <ESPStepperMotorServer_PositionSwitch.h>\n#include <ESPStepperMotorServer_StepperConfiguration.h>\n#include <ESP_FlexyStepper.h>\n\nESPStepperMotorServer *stepperMotorServer;\nESPStepperMotorServer_StepperConfiguration *stepperConfiguration;\n\n#define STEP_PIN 16\n#define DIRECTION_PIN 17\n#define LIMIT_SWITCH_PIN 18\n\n//this function gets called whenever the stepper reaches the target position / ends the current movement\n//it is registerd in the line \"stepperConfiguration->getFlexyStepper()->registerTargetPositionReachedCallback(targetPositionReachedCallback);\"\nvoid targetPositionReachedCallback(long position)\n{\n  Serial.printf(\"Stepper reached target position %ld\\n\", position);\n}\n\nvoid setup()\n{\n  Serial.begin(115200);\n  // now create a new ESPStepperMotorServer instance (this must be done AFTER the Serial interface has been started)\n  // In this example We create the server instance with only the serial command line interface enabled\n  stepperMotorServer = new ESPStepperMotorServer(ESPServerSerialEnabled, ESPServerLogLevel_DEBUG);\n  stepperMotorServer->setWifiMode(ESPServerWifiModeDisabled);\n\n  //create a new configuration for a stepper\n  stepperConfiguration = new ESPStepperMotorServer_StepperConfiguration(STEP_PIN, DIRECTION_PIN);\n  stepperConfiguration->setDisplayName(\"X-Axis\");\n  //configure the step size and microstepping setup of the drive/motor\n  stepperConfiguration->setMicrostepsPerStep(1);\n  stepperConfiguration->setStepsPerMM(100);\n  stepperConfiguration->setStepsPerRev(200);\n\n  //optional: if your stepper has a physical brake, configure the IO Pin of the brake and the behaviour\n  // stepperConfiguration->setBrakeIoPin(20, SWITCHTYPE_STATE_ACTIVE_HIGH_BIT); //set the ESP Pin number where the brake control is connected to an confifgure as active high (brake is engaged when pin goes high)\n  // stepperConfiguration->setBrakeEngageDelayMs(0); //instantly engage the brake after the stepper stops\n  // stepperConfiguration->setBrakeReleaseDelayMs(-1); //-1 = never reease the brake unless stepper moves\n\n  //OPTIONAL: register a callback handler to get informed whenever the stepper reaches the requested target position\n  stepperConfiguration->getFlexyStepper()->registerTargetPositionReachedCallback(targetPositionReachedCallback);\n\n  // now add the configuration to the server\n  unsigned int stepperId = stepperMotorServer->addOrUpdateStepper(stepperConfiguration);\n\n  //you can now also add switch and rotary encoder configurations to the server and link them to the steppers id if needed\n  //here an example for a limit switch connected to the previously created stepper motor configuration (make sure the pin is not floating (use pull up or pull down resistor if needed))\n  ESPStepperMotorServer_PositionSwitch *positionSwitch = new ESPStepperMotorServer_PositionSwitch(LIMIT_SWITCH_PIN, stepperId, SWITCHTYPE_LIMITSWITCH_COMBINED_BEGIN_END_BIT, \"Limit Switch\");\n  stepperMotorServer->addOrUpdatePositionSwitch(positionSwitch);\n\n  //start the server\n  stepperMotorServer->start();\n  // check the serial console for more details and to send control signals\n}\n\nvoid loop()\n{\n  //put your custom code here or use the following code to let the stepper motor go back and forth in an endless loop...how useful is that? :-)\n  //to move the stepper motor we need to get the ESP FlexyStepper instance for this motor like this:\n  ESP_FlexyStepper *flexyStepper = stepperConfiguration->getFlexyStepper();\n  flexyStepper->moveRelativeInSteps(100);\n  delay(2000);\n  flexyStepper->moveRelativeInSteps(-100);\n  delay(2000);\n  //for more details on the functions of ESP_FlexyStepper see here: https://github.com/pkerspe/ESP-FlexyStepper (chapter \"function overview\")\n}\n"
  },
  {
    "path": "examples/Example3_StepperMotorServer_with_static_IP_address/Example3_StepperMotorServer_with_static_IP_address.ino",
    "content": "//      *****************************************************\n//      *     Example how to configure static IP address    *\n//      *            Paul Kerspe                9.10.2020   *\n//      *****************************************************\n//\n// This example shows how to configure the stepper server with a static IP address, gateway and subnet mask\n// you only need to modify lines 16 / 17 to configure your wifi SSID and password then compile and upload to your ESP32\n//\n// for a detailed manual on how to use this library please visit: https://github.com/pkerspe/ESP-StepperMotor-Server/blob/master/README.md\n// ***********************************************************************\n#include <Arduino.h>\n#include <ESPStepperMotorServer.h>\n\nESPStepperMotorServer *stepperMotorServer;\n\nconst char *wifiName = \"<your wifi ssid>\";       // enter the SSID of the wifi network to connect to\nconst char *wifiSecret = \"<your wifi password>\"; // enter the password of the the existing wifi network here\n\nvoid setup()\n{\n  // start the serial interface with 115200 baud\n  // IMPORTANT: the following line is important, since the server relies on the serial console for log output\n  // Do not remove this line! (you can modify the baud rate to your needs though, but keep in mind, that slower baud rates might cause timing issues especially if you set the log level to DEBUG)\n  Serial.begin(115200);\n  // now create a new ESPStepperMotorServer instance (this must be done AFTER the Serial interface has been started)\n  // In this example We create the server instance with all modules activated and log level set to INFO (which is the default, you can also use ESPServerLogLevel_DEBUG to set it to debug instead)\n  stepperMotorServer = new ESPStepperMotorServer(ESPServerRestApiEnabled | ESPServerWebserverEnabled | ESPServerSerialEnabled, ESPServerLogLevel_INFO);\n  // connect to an existing WiFi network. Make sure you set the vairables wifiName and wifiSecret to match you SSID and wifi pasword (see above before the setup function)\n  stepperMotorServer->setWifiCredentials(wifiName, wifiSecret);\n  stepperMotorServer->setWifiMode(ESPServerWifiModeClient); //start the server as a wifi client (DHCP client of an existing wifi network)\n\n  // here we set the static IP address:\n  IPAddress staticIp(192, 168, 178, 49);\n  IPAddress gatewayIp(192, 168, 178, 1);\n  IPAddress subnetMask(255, 255, 255, 0);\n  stepperMotorServer->setStaticIpAddress(staticIp, gatewayIp, subnetMask);\n\n  //start the server\n  stepperMotorServer->start();\n  \n  // the server will now connect to the wifi and start the webserver, rest API and serial command line interface.\n  // check the serial console for more details like the URL of the WebInterface\n}\n\nvoid loop()\n{\n  //put your code here\n}\n"
  },
  {
    "path": "examples/Example4_StepperMotorServer_with_user_cli_command/Example4_StepperMotorServer_with_user_cli_command.ino",
    "content": "//      *****************************************************\n//      *     Example how to register a new CLI command     *\n//      *            Tobias Ellinghaus         05.12.2023   *\n//      *****************************************************\n//\n// This examples show how to register a custom command for the CLI interface.\n// This could be useful if you want to control specific GPIO pins or trigger some other control logic.\n// This example is based on the hardcoded config example, so all remarks of that apply here, too.\n//\n// This library requires that your stepper motor be connected to the ESP32\n// using an external driver that has a \"Step and Direction\" interface.\n//\n// For all driver boards, it is VERY important that you set the motor\n// current before running the example. This is typically done by adjusting\n// a potentiometer on the board or using dip switches.\n// Read the driver board's documentation to learn how to configure the driver\n//\n// for a detailed manual on how to use this library please visit: https://github.com/pkerspe/ESP-StepperMotor-Server/blob/master/README.md\n// ***********************************************************************\n#include <Arduino.h>\n#include <ESPStepperMotorServer.h>\n#include <ESPStepperMotorServer_PositionSwitch.h>\n#include <ESPStepperMotorServer_StepperConfiguration.h>\n#include <ESP_FlexyStepper.h>\n\nESPStepperMotorServer *stepperMotorServer;\nESPStepperMotorServer_StepperConfiguration *stepperConfiguration;\nESPStepperMotorServer_CLI *cli_handler;\n\n#define STEP_PIN 16\n#define DIRECTION_PIN 17\n#define ENABLE_PIN 18\n\nvoid toggle_enable(char *cmd, char *args)\n{\n  const int stepperid = cli_handler->getValidStepperIdFromArg(args);\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n  ESPStepperMotorServer_Logger::logDebugf(\"%s called for stepper id %i\\n\", cmd, stepperid);\n#endif\n  if (stepperid > -1)\n  {\n    char value[20];\n    cli_handler->getParameterValue(args, \"v\", value);\n    if (value[0] != '\\0')\n    {\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n      ESPStepperMotorServer_Logger::logDebugf(\"enabled called with v = %s\\n\", value);\n#endif\n\n      const int on_off = (String(value).toInt());\n      digitalWrite(ENABLE_PIN, on_off ? HIGH : LOW);\n\n      Serial.println(cmd);\n    }\n    else\n    {\n      Serial.println(\"error: missing required v parameter\");\n    }\n  }\n}\n\nvoid setup()\n{\n  // configure our custom pin as an output and initialize it to LOW\n  pinMode(ENABLE_PIN, OUTPUT);\n  digitalWrite(ENABLE_PIN, LOW);\n\n  Serial.begin(115200);\n  // now create a new ESPStepperMotorServer instance (this must be done AFTER the Serial interface has been started)\n  // In this example we create the server instance with only the serial command line interface enabled\n  stepperMotorServer = new ESPStepperMotorServer(ESPServerSerialEnabled, ESPServerLogLevel_DEBUG);\n  stepperMotorServer->setWifiMode(ESPServerWifiModeDisabled);\n\n  // register a new CLI command to set the ENABLE pin\n  cli_handler = stepperMotorServer->getCLIHandler();\n  cli_handler->registerNewUserCommand({String(\"enable\"), String(\"e\"), String(\"set the ENABLE signal of a specific stepper. requires the id of the stepper and also the value. E.g. e=0&v:1 to enable the ENABLE input on the stepper with id 0\"), true}, &toggle_enable);\n\n  //create a new configuration for a stepper\n  stepperConfiguration = new ESPStepperMotorServer_StepperConfiguration(STEP_PIN, DIRECTION_PIN);\n  stepperConfiguration->setDisplayName(\"X-Axis\");\n  //configure the step size and microstepping setup of the drive/motor\n  stepperConfiguration->setMicrostepsPerStep(1);\n  stepperConfiguration->setStepsPerMM(100);\n  stepperConfiguration->setStepsPerRev(200);\n\n  // now add the configuration to the server\n  unsigned int stepperId = stepperMotorServer->addOrUpdateStepper(stepperConfiguration);\n\n  //start the server\n  stepperMotorServer->start();\n  // check the serial console for more details and to send control signals\n}\n\nvoid loop()\n{\n  //put your custom code here\n}\n"
  },
  {
    "path": "library.json",
    "content": "{\n    \"name\": \"ESP-StepperMotor-Server\",\n    \"description\": \"A stepper motor control server for ESP32 modules that provides a Web UI, a REST API and a serial control interface. Support for limit switches and rotary encoders.\",\n    \"keywords\": \"stepper,motor,http,websocket,webserver,rest,api,serial,controller,server,device-control,esp32\",\n    \"authors\": {\n        \"name\": \"Paul Kerspe\",\n        \"maintainer\": true\n    },\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"https://github.com/pkerspe/ESP-StepperMotor-Server.git\"\n    },\n    \"version\": \"0.4.12\",\n    \"license\": \"MIT\",\n    \"frameworks\": \"arduino\",\n    \"platforms\": [\n        \"espressif32\"\n    ],\n    \"dependencies\": [{\n            \"name\": \"ESP Async WebServer\",\n            \"version\": \"1.2.3\",\n            \"platforms\": \"espressif32\"\n        },\n        {\n            \"name\": \"ESP-FlexyStepper\",\n            \"version\": \"^1.4.7\",\n            \"platforms\": \"espressif32\"\n        },\n        {\n            \"name\": \"ArduinoJSON\",\n            \"version\": \"6.21.2\",\n            \"platforms\": \"arduino\"\n        },\n        {\n            \"name\": \"SPIFFS\",\n            \"version\": \"2.0.0\",\n            \"platforms\": \"arduino\"\n        }\n    ],\n    \"examples\": [\n        \"[Ee]xamples/*/*.ino\"\n    ]\n}\n"
  },
  {
    "path": "library.properties",
    "content": "name=ESP-StepperMotor-Server\nversion=0.4.12\nauthor=Paul Kerspe\nmaintainer=Paul Kerspe\nsentence=A stepper motor control server for ESP32 with Web UI, REST API and CLI\nparagraph=A stepper motor control server for ESP32 modules that provides a Web UI, a REST API and a serial control interface. Support for limit switches and rotary encoders.\ncategory=Device Control\nurl=https://github.com/pkerspe/ESP-StepperMotor-Server\narchitectures=esp32,espressif32\nincludes=ESPStepperMotorServer.h\ndepends=ArduinoJSON (=6.21.2), ESP Async WebServer, ESP-FlexyStepper (>=1.4.7), SPIFFS\n"
  },
  {
    "path": "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[env:esp32devServer]\nplatform = espressif32@6.12.0\nbuild_flags = -fexceptions\nboard = esp32dev\nframework = arduino\nlib_deps = ESP-FlexyStepper@^1.4.7\n    ESP32Async/ESPAsyncWebServer @ ^3.9.0\n    ArduinoJSON@6.21.2\n    littlefs\nboard_build.partitions = default.csv\nmonitor_speed = 115200\nmonitor_filters = esp32_exception_decoder, default\n\n;monitor_port = /dev/cu.SLAB_USBtoUART\n;upload_port = /dev/cu.SLAB_USBtoUART\n\n"
  },
  {
    "path": "src/ESPStepperMotorServer.cpp",
    "content": "//      *********************************************************\n//      *                                                       *\n//      *     ESP8266 and ESP32 Stepper Motor Server            *\n//      *                                                       *\n//      *            Copyright (c) Paul Kerspe, 2019            *\n//      *                                                       *\n//      **********************************************************\n\n// MIT License\n//\n// Copyright (c) 2019 Paul Kerspe\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 furnished\n// to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// 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 THE\n// SOFTWARE.\n\n//\n// This library is used to start a server (Webserver, REST API or via serial port) to control and configure one or more stepper motors via a stepper driver module with step and direction input as well as optional homing switches\n//\n// Usage:\n//    Near the top of the program, add:\n//        include \"ESPStepperMotorServer.h\"\n//\n//    e.g. to start the server and enable the web based user interface, the REST API and the serial server use:\n//        ESPStepperMotorServer server(ESPServerRestApiEnabled|ESPServerWebserverEnabled|ESPServerSerialEnabled);\n//    eg. to only start the web user interface and disable the rest API and serial server use:\n//        ESPStepperMotorServer server(ESPServerWebserverEnabled);\n//\n//    if the server is started with the ESPServerWebserverEnabled or ESPServerRestApiEnabled flag, you can specify a http port (default is port 80), for the server to listen for connections (e.g. port 80 in this example) if only starting in serial mode, you can ommit this step\n//        server.setHttpPort(80);\n//\n//    if you want the server to connect to an existing wifi network, you need to set the wifi ssid, username and password (replace \"somessid\", \"someUser\" and \"somePwd\" with the appropriate values for you network)\n//        server.setWifiCredentials(\"someSsid\",\"someUser\",\"somePwd\");\n//    OR if you do NOT want to connect to an EXISTING wifi network, then omit the setWifiCredentials call and instead configure the server to stat a separate Access Point by setting the Access Point name of your choice\n//    NOTE: if you do not setWifiCredentials or setAccessPointName the server will start in access point mode with an ssid of \"ESPStepperMotorServer\"\n//        server.setAccessPointName(\"myStepperServer\");\n//\n//    after configuring the basic server, start it up as the last required step in the setup function:\n//        server.start();\n//\n\n#include <ESPStepperMotorServer.h>\n\n// ---------------------------------------------------------------------------------\n//                                  Setup functions\n// ---------------------------------------------------------------------------------\n\nESPStepperMotorServer *ESPStepperMotorServer::anchor = NULL;\n\n// ESPStepperMotorServer::ESPStepperMotorServer(const ESPStepperMotorServer &espStepperMotorServer)\n// {\n//     this->startSPIFFS();\n//     this->serverConfiguration = new ESPStepperMotorServer_Configuration(espStepperMotorServer.serverConfiguration->_configFilePath, this->isSPIFFSactive);\n//     *this->serverConfiguration = *espStepperMotorServer.serverConfiguration;\n\n// #ifndef ESPStepperMotorServer_COMPILE_NO_WEB\n//     if (espStepperMotorServer.webInterfaceHandler)\n//     {\n//         this->webInterfaceHandler = new ESPStepperMotorServer_WebInterface(this);\n//         *this->webInterfaceHandler = *espStepperMotorServer.webInterfaceHandler;\n//     }\n\n//     if (espStepperMotorServer.restApiHandler)\n//     {\n//         this->restApiHandler = new ESPStepperMotorServer_RestAPI(this);\n//         *this->restApiHandler = *espStepperMotorServer.restApiHandler;\n//     }\n// #endif\n\n//     if (espStepperMotorServer.cliHandler)\n//     {\n//         this->cliHandler = new ESPStepperMotorServer_CLI(this);\n//         *this->cliHandler = *espStepperMotorServer.cliHandler;\n//     }\n\n//     if (espStepperMotorServer.motionControllerHandler)\n//     {\n//         this->motionControllerHandler = new ESPStepperMotorServer_MotionController(this);\n//         *this->motionControllerHandler = *espStepperMotorServer.motionControllerHandler;\n//     }\n\n// #ifndef ESPStepperMotorServer_COMPILE_NO_WEB\n//     if (espStepperMotorServer.httpServer)\n//     {\n//         this->httpServer = new AsyncWebServer(this->serverConfiguration->serverPort);\n//         *this->httpServer = *espStepperMotorServer.httpServer;\n//     }\n\n//     if (espStepperMotorServer.webSocketServer)\n//     {\n//         // check if needed, since in startWebserver it will be intialized again (~line 611)\n//         this->webSocketServer = new AsyncWebSocket(\"/ws\");\n//         *this->webSocketServer = *espStepperMotorServer.webSocketServer;\n//     }\n// #endif\n// }\n\nESPStepperMotorServer::~ESPStepperMotorServer()\n{\n    delete this->serverConfiguration;\n#ifndef ESPStepperMotorServer_COMPILE_NO_WEB\n    delete this->webInterfaceHandler;\n    delete this->restApiHandler;\n#endif\n    delete this->cliHandler;\n    delete this->motionControllerHandler;\n}\n\n//\n// constructor for the stepper server class\n//\nESPStepperMotorServer::ESPStepperMotorServer(byte serverMode, byte logLevel)\n{\n    ESPStepperMotorServer_Logger::setLogLevel(logLevel);\n    startSPIFFS();\n    // get config instance which tries to load config from SPIFFS by default\n    this->serverConfiguration = new ESPStepperMotorServer_Configuration(this->defaultConfigurationFilename, this->isSPIFFSactive);\n\n    this->enabledServices = serverMode;\n\n#ifndef ESPStepperMotorServer_COMPILE_NO_WEB\n    if ((this->enabledServices & ESPServerWebserverEnabled) == ESPServerWebserverEnabled)\n    {\n        this->isWebserverEnabled = true;\n        this->webInterfaceHandler = new ESPStepperMotorServer_WebInterface(this);\n    }\n    // rest api needs to be started either if web UI is enabled (which uses the REST API itself) or if REST API is enabled\n    if ((this->enabledServices & ESPServerRestApiEnabled) == ESPServerRestApiEnabled || this->isWebserverEnabled)\n    {\n        this->isRestApiEnabled = true;\n        this->restApiHandler = new ESPStepperMotorServer_RestAPI(this);\n    }\n#endif\n\n    if ((this->enabledServices & ESPServerSerialEnabled) == ESPServerSerialEnabled)\n    {\n        this->isCLIEnabled = true;\n        this->cliHandler = new ESPStepperMotorServer_CLI(this);\n    }\n\n    this->motionControllerHandler = new ESPStepperMotorServer_MotionController(this);\n\n    if (ESPStepperMotorServer::anchor != NULL)\n    {\n        ESPStepperMotorServer_Logger::logWarning(\"ESPStepperMotorServer must be used as a singleton, do not instanciate more than one server in your project\");\n    }\n    else\n    {\n        ESPStepperMotorServer::anchor = this;\n    }\n}\n\n// ---------------------------------------------------------------------------------\n//                     general service control functions\n// ---------------------------------------------------------------------------------\n\n/**\n * ask the server to perform a reboot of the ESP.\n * this might be executed instantly or with a short delay depending on the current servers state\n */\nvoid ESPStepperMotorServer::requestReboot(String rebootReason)\n{\n    Serial.printf(\"Reboot scheduled, preparing shutdown. Reaon: %s\\n\", rebootReason.c_str());\n    this->_isRebootScheduled = true;\n}\n\nvoid ESPStepperMotorServer::start()\n{\n    ESPStepperMotorServer_Logger::logInfof(\"Starting ESP-StepperMotor-Server (v. %s)\\n\", this->version);\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n    this->printCompileSettings();\n#endif\n\n    if (this->serverConfiguration->wifiMode == ESPServerWifiModeAccessPoint)\n    {\n        this->startAccessPoint();\n        if (ESPStepperMotorServer_Logger::getLogLevel() >= ESPServerLogLevel_DEBUG)\n        {\n            this->printWifiStatus();\n        }\n    }\n    else if (this->serverConfiguration->wifiMode == ESPServerWifiModeClient)\n    {\n        this->connectToWifiNetwork();\n        if (ESPStepperMotorServer_Logger::getLogLevel() >= ESPServerLogLevel_DEBUG)\n        {\n            this->printWifiStatus();\n        }\n    }\n    else if (this->serverConfiguration->wifiMode == ESPServerWifiModeDisabled)\n    {\n        ESPStepperMotorServer_Logger::logInfo(\"WiFi mode is disabled, only serial control interface will be used for controls\");\n    }\n\n#ifndef ESPStepperMotorServer_COMPILE_NO_WEB\n    this->startWebserver();\n#endif\n\n    this->setupAllIOPins();\n    this->attachAllInterrupts();\n\n    if (this->isCLIEnabled)\n    {\n        this->cliHandler->start();\n    }\n    this->motionControllerHandler->start();\n    this->isServerStarted = true;\n}\n\nvoid ESPStepperMotorServer::stop()\n{\n    ESPStepperMotorServer_Logger::logInfo(\"Stopping ESP-StepperMotor-Server\");\n    this->motionControllerHandler->stop();\n    this->detachAllInterrupts();\n    ESPStepperMotorServer_Logger::logInfo(\"detached interrupt handlers\");\n\n#ifndef ESPStepperMotorServer_COMPILE_NO_WEB\n    if (isWebserverEnabled || isRestApiEnabled)\n    {\n        this->httpServer->end();\n        ESPStepperMotorServer_Logger::logInfo(\"stopped web server\");\n    }\n#endif\n\n    if (this->isCLIEnabled)\n    {\n        this->cliHandler->stop();\n    }\n    this->isServerStarted = false;\n    ESPStepperMotorServer_Logger::logInfo(\"ESP-StepperMotor-Server stopped\");\n}\n\n// ---------------------------------------------------------------------------------\n//                                  Configuration Functions\n// ---------------------------------------------------------------------------------\n\n#ifndef ESPStepperMotorServer_COMPILE_NO_WEB\nvoid ESPStepperMotorServer::setHttpPort(int portNumber)\n{\n    this->serverConfiguration->serverPort = portNumber;\n}\n#endif\n\nESPStepperMotorServer_Configuration *ESPStepperMotorServer::getCurrentServerConfiguration()\n{\n    return this->serverConfiguration;\n}\n\nint ESPStepperMotorServer::addOrUpdateRotaryEncoder(ESPStepperMotorServer_RotaryEncoder *encoder, int encoderIndex)\n{\n    // add encoder to configuration\n    if (encoderIndex > -1)\n    {\n        this->serverConfiguration->setRotaryEncoder(encoder, encoderIndex);\n    }\n    else\n    {\n        encoderIndex = (unsigned int)this->serverConfiguration->addRotaryEncoder(encoder);\n    }\n    this->setupRotaryEncoderIOPin(encoder);\n    return encoderIndex;\n}\n\n/**\n * add or updated an existing stepper configuration\n * return the id of the stepper config (in case a new one has been added, or the given one in case of an update)\n * -1 is returned in case of an error (e.g. maximum number of configs reached for this entity)\n */\nint ESPStepperMotorServer::addOrUpdateStepper(ESPStepperMotorServer_StepperConfiguration *stepper, int stepperIndex)\n{\n    if (stepper->getStepIoPin() == ESPStepperMotorServer_StepperConfiguration::ESPServerStepperUnsetIoPinNumber ||\n        stepper->getDirectionIoPin() == ESPStepperMotorServer_StepperConfiguration::ESPServerStepperUnsetIoPinNumber)\n    {\n        ESPStepperMotorServer_Logger::logWarningf(\"Either the step IO pin (%i) or direction IO (%i) pin, or both, are not set correctly. Use a valid IO Pin value between 0 and the highest available IO Pin on your ESP\\n\", stepper->getStepIoPin(), stepper->getDirectionIoPin());\n        return -1;\n    }\n    // set IO Pins for stepper\n    pinMode(stepper->getDirectionIoPin(), OUTPUT);\n    digitalWrite(stepper->getDirectionIoPin(), LOW);\n    pinMode(stepper->getStepIoPin(), OUTPUT);\n    digitalWrite(stepper->getStepIoPin(), LOW);\n    // add stepper to configuration or update existing one\n    if (stepperIndex > -1)\n    {\n        this->serverConfiguration->setStepperConfiguration(stepper, stepperIndex);\n    }\n    else\n    {\n        stepperIndex = this->serverConfiguration->addStepperConfiguration(stepper);\n    }\n\n    return stepperIndex;\n}\n\n/**\n * remove the stepper configuration with the given index/id\n */\nvoid ESPStepperMotorServer::removeStepper(byte id)\n{\n    if (this->serverConfiguration->getStepperConfiguration(id))\n    {\n        this->serverConfiguration->removeStepperConfiguration(id);\n    }\n    else\n    {\n        ESPStepperMotorServer_Logger::logWarningf(\"Stepper configuration index %i is invalid, no entry found or stepper IDs do not match, removeStepper() canceled\\n\", id);\n    }\n}\n\nint ESPStepperMotorServer::addOrUpdatePositionSwitch(ESPStepperMotorServer_PositionSwitch *posSwitchToAdd, int switchIndex)\n{\n    if (switchIndex > -1)\n    {\n        this->serverConfiguration->setSwitch(posSwitchToAdd, switchIndex);\n    }\n    else\n    {\n        switchIndex = (unsigned int)this->serverConfiguration->addSwitch(posSwitchToAdd);\n    }\n    // Setup IO Pin\n    this->setupPositionSwitchIOPin(posSwitchToAdd);\n\n    ESPStepperMotorServer_Logger::logInfof(\"Added switch '%s' for IO pin %i at configuration index %i\\n\", this->serverConfiguration->getSwitch(switchIndex)->getPositionName().c_str(), this->serverConfiguration->getSwitch(switchIndex)->getIoPinNumber(), switchIndex);\n    return switchIndex;\n}\n\nvoid ESPStepperMotorServer::removePositionSwitch(int positionSwitchIndex)\n{\n    ESPStepperMotorServer_PositionSwitch *posSwitch = this->serverConfiguration->getSwitch(positionSwitchIndex);\n    if (posSwitch)\n    {\n        this->detachInterruptForPositionSwitch(posSwitch);\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n        ESPStepperMotorServer_Logger::logDebugf(\"Removing position switch '%s' (id: %i) from configured switches\\n\", posSwitch->getPositionName().c_str(), positionSwitchIndex);\n#endif\n        this->serverConfiguration->removeSwitch(positionSwitchIndex);\n    }\n    else\n    {\n        ESPStepperMotorServer_Logger::logWarning(\"Switch index %i is invalid, no switch configuration present at this index, removePositionSwitch() canceled\\n\", positionSwitchIndex);\n    }\n}\n\nvoid ESPStepperMotorServer::removeRotaryEncoder(byte id)\n{\n    if (this->serverConfiguration->getRotaryEncoder(id))\n    {\n        this->serverConfiguration->removeRotaryEncoder(id);\n    }\n    else\n    {\n        ESPStepperMotorServer_Logger::logWarningf(\"rotary encoder index %i is invalid, no rotary encoder pointer present at this configuration index or rotary encoder IDs do not match, removeRotaryEncoder() canceled\\n\", id);\n    }\n}\n\n// ---------------------------------------------------------------------------------\n//                                  Status and Service Functions\n// ---------------------------------------------------------------------------------\n\nvoid ESPStepperMotorServer::getFormattedPositionSwitchStatusRegister(byte registerIndex, String &output)\n{\n    String binary = String(this->buttonStatus[registerIndex], BIN);\n    for (int i = 0; i < 8 - binary.length(); i++)\n    {\n        output += \"0\";\n    }\n    output += binary;\n}\n\nvoid ESPStepperMotorServer::printPositionSwitchStatus()\n{\n    // TODO reuse code in REST API\n    const int docSize = 150 * ESPServerMaxSwitches;\n    StaticJsonDocument<docSize> doc;\n    JsonObject root = doc.to<JsonObject>();\n    JsonObject settings = root.createNestedObject(\"settings\");\n    settings[\"positionSwitchCounterLimit\"] = ESPServerMaxSwitches;\n    settings[\"statusRegisterCounter\"] = ESPServerSwitchStatusRegisterCount;\n\n    JsonArray data = root.createNestedArray(\"switchStatusRegister\");\n\n    for (byte i = 0; i < ESPServerSwitchStatusRegisterCount; i++)\n    {\n        JsonObject positionSwitchRegister = data.createNestedObject();\n        positionSwitchRegister[\"statusRegisterIndex\"] = i;\n\n        String binaryPadded = \"\";\n        this->getFormattedPositionSwitchStatusRegister(i, binaryPadded);\n        positionSwitchRegister[\"status\"] = binaryPadded;\n    }\n\n    JsonArray switches = root.createNestedArray(\"positionSwitches\");\n    for (int i = 0; i < ESPServerMaxSwitches; i++)\n    {\n        if (this->serverConfiguration->getSwitch(i))\n        {\n            JsonObject positionSwitch = switches.createNestedObject();\n            positionSwitch[\"id\"] = i;\n            positionSwitch[\"name\"] = this->serverConfiguration->getSwitch(i)->getPositionName();\n            positionSwitch[\"ioPin\"] = this->serverConfiguration->getSwitch(i)->getIoPinNumber();\n            positionSwitch[\"position\"] = this->serverConfiguration->getSwitch(i)->getSwitchPosition();\n            positionSwitch[\"stepperId\"] = this->serverConfiguration->getSwitch(i)->getStepperIndex();\n            positionSwitch[\"active\"] = this->getPositionSwitchStatus(i);\n\n            JsonObject positionSwichType = positionSwitch.createNestedObject(\"type\");\n            if ((this->serverConfiguration->getSwitch(i)->getSwitchType() & ESPServerSwitchType_ActiveHigh) == ESPServerSwitchType_ActiveHigh)\n            {\n                positionSwichType[\"pinMode\"] = \"Active High\";\n            }\n            else\n            {\n                positionSwichType[\"pinMode\"] = \"Active Low\";\n            }\n            if ((this->serverConfiguration->getSwitch(i)->getSwitchType() & ESPServerSwitchType_HomingSwitchBegin) == ESPServerSwitchType_HomingSwitchBegin)\n            {\n                positionSwichType[\"switchType\"] = \"Homing switch (start-position)\";\n            }\n            else if ((this->serverConfiguration->getSwitch(i)->getSwitchType() & ESPServerSwitchType_HomingSwitchEnd) == ESPServerSwitchType_HomingSwitchEnd)\n            {\n                positionSwichType[\"switchType\"] = \"Homing switch (end-position)\";\n            }\n            else if ((this->serverConfiguration->getSwitch(i)->getSwitchType() & ESPServerSwitchType_GeneralPositionSwitch) == ESPServerSwitchType_GeneralPositionSwitch)\n            {\n                positionSwichType[\"switchType\"] = \"General position switch\";\n            }\n            else if ((this->serverConfiguration->getSwitch(i)->getSwitchType() & ESPServerSwitchType_EmergencyStopSwitch) == ESPServerSwitchType_EmergencyStopSwitch)\n            {\n                positionSwichType[\"switchType\"] = \"Emergency shut down switch\";\n            }\n        }\n    }\n\n    serializeJson(root, Serial);\n    Serial.println();\n}\n\n/**\n * checks if the configured position switch at the given configuration index is triggered (active) or not\n * This function takes the configured type ESPServerSwitchType_ActiveHigh or ESPServerSwitchType_ActiveLow into account when determining the current active state\n * e.g. if a switch is configured to be ESPServerSwitchType_ActiveLow the function will return 1 when the switch is triggered (low signal on IO pin).\n * For a switch that is configured ESPServerSwitchType_ActiveHigh on the other side, the function will return 0 when a low signal is on the IO pin, and 1 when a high signal is present\n */\nbyte ESPStepperMotorServer::getPositionSwitchStatus(int positionSwitchIndex)\n{\n    ESPStepperMotorServer_PositionSwitch *posSwitch = this->serverConfiguration->getSwitch(positionSwitchIndex);\n    if (posSwitch)\n    {\n        byte buttonState = digitalRead(posSwitch->getIoPinNumber());\n        if ((posSwitch->getSwitchType() & ESPServerSwitchType_ActiveHigh) == ESPServerSwitchType_ActiveHigh)\n        {\n            return (buttonState) ? 1 : 0;\n        }\n        else\n        {\n            return (buttonState) ? 0 : 1;\n        }\n    }\n    return 0;\n}\n\n// ---------------------------------------------------------------------------------\n//                          CLI API functions\n// ---------------------------------------------------------------------------------\n\nESPStepperMotorServer_CLI *ESPStepperMotorServer::getCLIHandler() const\n{\n    return this->cliHandler;\n}\n\n// ---------------------------------------------------------------------------------\n//                          Web Server and REST API functions\n// ---------------------------------------------------------------------------------\nvoid ESPStepperMotorServer::startSPIFFS()\n{\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n    ESPStepperMotorServer_Logger::logDebug(\"Checking LittleFS for existence and free space\");\n#endif\n    bool littleFsBeginSuccess = LittleFS.begin();\n    if (!littleFsBeginSuccess)\n    {\n        ESPStepperMotorServer_Logger::logWarning(\"LittleFS cannot be mounted, trying to format LittleFS\");\n        if (LittleFS.format())\n        {\n            ESPStepperMotorServer_Logger::logInfo(\"LittleFS formatted, trying to mount again\");\n            littleFsBeginSuccess = LittleFS.begin();\n        }\n        else\n        {\n            ESPStepperMotorServer_Logger::logWarning(\"LittleFS formatting failed\");\n        }\n    }\n\n    if (littleFsBeginSuccess)\n    {\n        this->isSPIFFSactive = true;\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n        if (ESPStepperMotorServer_Logger::getLogLevel() >= ESPServerLogLevel_DEBUG)\n        {\n            ESPStepperMotorServer_Logger::logDebug(\"SPIFFS started\");\n            printSPIFFSStats();\n        }\n#endif\n    }\n    else\n    {\n        this->isSPIFFSactive = false;\n        if (this->isWebserverEnabled)\n        {\n            ESPStepperMotorServer_Logger::logWarning(\"Unable to activate LittleFS. Files for web interface cannot be loaded\");\n        }\n    }\n}\n\nbool ESPStepperMotorServer::isSPIFFSMounted()\n{\n    return this->isSPIFFSactive;\n}\n\nvoid ESPStepperMotorServer::printSPIFFSStats()\n{\n    if (this->isSPIFFSMounted())\n    {\n        Serial.println(\"LittleFS stats:\");\n        Serial.printf(\"Total bytes: %i\\n\", (int)LittleFS.totalBytes());\n        Serial.printf(\"bytes used: %i\\n\", (int)LittleFS.usedBytes());\n        Serial.printf(\"bytes free: %i\\n\", getSPIFFSFreeSpace());\n    }\n    else\n    {\n        Serial.println(\"printSPIFFSStats: LittleFS not mounted\");\n    }\n}\n\nvoid ESPStepperMotorServer::printSPIFFSRootFolderContents()\n{\n    if (this->isSPIFFSMounted())\n    {\n        File root = LittleFS.open(\"/\");\n\n        if (!root)\n        {\n            ESPStepperMotorServer_Logger::logWarning(\"Failed to open root folder on SPIFFS for reading\");\n        }\n        if (root.isDirectory())\n        {\n            ESPStepperMotorServer_Logger::logInfo(\"Listing files in root folder of SPIFFS:\");\n            File file = root.openNextFile();\n            while (file)\n            {\n                ESPStepperMotorServer_Logger::logInfof(\"File: %s (%i) %ld\\n\", file.name(), file.size(), file.getLastWrite());\n                file = root.openNextFile();\n            }\n            root.close();\n        }\n    }\n    else\n    {\n        ESPStepperMotorServer_Logger::logWarning(\"SPIFFS not mounted, printSPIFFSRootFolderContents() canceled\");\n    }\n}\n\nint ESPStepperMotorServer::getSPIFFSFreeSpace()\n{\n    if (this->isSPIFFSMounted())\n    {\n        return ((int)LittleFS.totalBytes() - (int)LittleFS.usedBytes());\n    }\n    else\n    {\n        return 0;\n    }\n}\n\n#ifndef ESPStepperMotorServer_COMPILE_NO_WEB\n// TODO: test this part and implement usefull behavior\nvoid ESPStepperMotorServer::onWebSocketEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len)\n{\n    if (type == WS_EVT_CONNECT)\n    {\n        ESPStepperMotorServer_Logger::logInfof(\"ws[%s][%u] connect\\n\", server->url(), client->id());\n        client->printf(\"Hello Client %u :)\", client->id());\n        client->ping();\n    }\n    else if (type == WS_EVT_DISCONNECT)\n    {\n        ESPStepperMotorServer_Logger::logInfof(\"ws[%s][%i] disconnect: %i\\n\", server->url(), client->id(), client->id());\n    }\n    else if (type == WS_EVT_ERROR)\n    {\n        ESPStepperMotorServer_Logger::logWarningf(\"ws[%s][%u] error(%i): %s\\n\", server->url(), client->id(), *((uint16_t *)arg), (char *)data);\n    }\n    else if (type == WS_EVT_PONG)\n    {\n        ESPStepperMotorServer_Logger::logInfof(\"ws[%s][%i] pong[%i]: %s\\n\", server->url(), client->id(), (int)len, (len) ? (char *)data : \"\");\n    }\n    else if (type == WS_EVT_DATA)\n    {\n        AwsFrameInfo *info = (AwsFrameInfo *)arg;\n        String msg = \"\";\n        if (info->final && info->index == 0 && info->len == len)\n        {\n            // the whole message is in a single frame and we got all of it's data\n            Serial.printf(\"ws[%s][%u] %s-message[%llu]: \", server->url(), client->id(), (info->opcode == WS_TEXT) ? \"text\" : \"binary\", info->len);\n\n            if (info->opcode == WS_TEXT)\n            {\n                for (size_t i = 0; i < info->len; i++)\n                {\n                    msg += (char)data[i];\n                }\n                String commandFromClient = String((char *)data);\n                if (commandFromClient.equals(\"status\"))\n                {\n                    client->text(\"Here is your status: OK\");\n                }\n            }\n            else\n            {\n                char buff[10];\n                for (size_t i = 0; i < info->len; i++)\n                {\n                    sprintf(buff, \"%02x \", (uint8_t)data[i]);\n                    msg += buff;\n                }\n            }\n            Serial.printf(\"%s\\n\", msg.c_str());\n\n            if (info->opcode == WS_TEXT)\n                client->text(\"I got your text message\");\n            else\n                client->binary(\"I got your binary message\");\n        }\n        else\n        {\n            // message is comprised of multiple frames or the frame is split into multiple packets\n            if (info->index == 0)\n            {\n                if (info->num == 0)\n                    Serial.printf(\"ws[%s][%u] %s-message start\\n\", server->url(), client->id(), (info->message_opcode == WS_TEXT) ? \"text\" : \"binary\");\n                Serial.printf(\"ws[%s][%u] frame[%u] start[%llu]\\n\", server->url(), client->id(), info->num, info->len);\n            }\n\n            Serial.printf(\"ws[%s][%u] frame[%u] %s[%llu - %llu]: \", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT) ? \"text\" : \"binary\", info->index, info->index + len);\n\n            if (info->opcode == WS_TEXT)\n            {\n                for (size_t i = 0; i < info->len; i++)\n                {\n                    msg += (char)data[i];\n                }\n            }\n            else\n            {\n                char buff[10];\n                for (size_t i = 0; i < info->len; i++)\n                {\n                    sprintf(buff, \"%02x \", (uint8_t)data[i]);\n                    msg += buff;\n                }\n            }\n            Serial.printf(\"%s\\n\", msg.c_str());\n\n            if ((info->index + len) == info->len)\n            {\n                Serial.printf(\"ws[%s][%u] frame[%u] end[%llu]\\n\", server->url(), client->id(), info->num, info->len);\n                if (info->final)\n                {\n                    Serial.printf(\"ws[%s][%u] %s-message end\\n\", server->url(), client->id(), (info->message_opcode == WS_TEXT) ? \"text\" : \"binary\");\n                    if (info->message_opcode == WS_TEXT)\n                        client->text(\"I got your text message\");\n                    else\n                        client->binary(\"I got your binary message\");\n                }\n            }\n        }\n    }\n}\n\nvoid ESPStepperMotorServer::startWebserver()\n{\n    if (isWebserverEnabled || isRestApiEnabled)\n    {\n        printSPIFFSRootFolderContents();\n\n        httpServer = new AsyncWebServer(this->serverConfiguration->serverPort);\n        ESPStepperMotorServer_Logger::logInfof(\"Starting webserver on port %i\\n\", this->serverConfiguration->serverPort);\n\n        webSocketServer = new AsyncWebSocket(\"/ws\");\n        webSocketServer->onEvent(std::bind(&ESPStepperMotorServer::onWebSocketEvent, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6));\n        httpServer->addHandler(webSocketServer);\n\n        if (isWebserverEnabled)\n        {\n            this->webInterfaceHandler->registerWebInterfaceUrls(this->httpServer);\n        }\n        if (isRestApiEnabled)\n        {\n            this->restApiHandler->registerRestEndpoints(this->httpServer);\n        }\n        // SETUP CORS responses/headers\n        DefaultHeaders::Instance().addHeader(\"Access-Control-Allow-Origin\", \"*\");\n        DefaultHeaders::Instance().addHeader(\"Access-Control-Allow-Methods\", \"GET,POST,PUT,DELETE\");\n        DefaultHeaders::Instance().addHeader(\"Access-Control-Allow-Headers\", \"Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers\");\n\n        httpServer->onNotFound([](AsyncWebServerRequest *request)\n                               {\n                                   if (request->method() == HTTP_OPTIONS)\n                                   {\n                                       request->send(200);\n                                   }\n                                   else\n                                   {\n                                       request->send(404, \"text/html\", \"<html><body><h1>ESP-StepperMotor-Server</h1><p>The requested file could not be found.<br/>Either you have a typo in your URL or the web User Interface is not installed in the SPIFFS of your ESP. In the later case please Upload the User Interface files to SPIFFS before proceeding.</p><p>For more details refer to the <a href=\\\"https://github.com/pkerspe/ESP-StepperMotor-Server/blob/master/README.md\\\">installation manual</a></body></html>\");\n                                   } });\n\n        httpServer->begin();\n        ESPStepperMotorServer_Logger::logInfof(\"Webserver started, you can now open the user interface on http://%s:%i/\\n\", this->getIpAddress().c_str(), this->serverConfiguration->serverPort);\n    }\n}\n\nvoid ESPStepperMotorServer::sendSocketMessageToAllClients(const char *message, size_t len)\n{\n    // try sending message if clients are connected at all and if buffer is not already full\n    if (this->webSocketServer->count() > 0 && this->webSocketServer->availableForWriteAll())\n    {\n        this->webSocketServer->textAll(message, len);\n    }\n}\n#endif\n\nString ESPStepperMotorServer::getIpAddress()\n{\n    if (this->serverConfiguration->wifiMode == ESPServerWifiModeAccessPoint)\n    {\n        return WiFi.softAPIP().toString();\n    }\n    else if (this->serverConfiguration->wifiMode == ESPServerWifiModeClient)\n    {\n        return WiFi.localIP().toString();\n    }\n    else\n    {\n        return \"not connected\";\n    }\n}\n\n/**\n * get some server status information as a JSON formatted string.\n * Returns: version, wifi mode, ip address, spiffs information, enabled server modules\n */\nvoid ESPStepperMotorServer::getServerStatusAsJsonString(String &statusString)\n{\n    StaticJsonDocument<200> doc;\n    JsonObject root = doc.to<JsonObject>();\n    root[\"version\"] = this->version;\n\n    JsonObject wifiStatus = root.createNestedObject(\"wifi\");\n    wifiStatus[\"mode\"] = (this->serverConfiguration->wifiMode == ESPServerWifiModeAccessPoint) ? \"ap\" : \"client\";\n    wifiStatus[\"ip\"] = (this->serverConfiguration->wifiMode == ESPServerWifiModeAccessPoint) ? WiFi.dnsIP().toString() : WiFi.localIP().toString();\n\n    JsonObject spiffsStatus = root.createNestedObject(\"littlefs\");\n    if (this->isSPIFFSMounted())\n    {\n        spiffsStatus[\"total_space\"] = (int)LittleFS.totalBytes();\n        spiffsStatus[\"free_space\"] = this->getSPIFFSFreeSpace();\n    }\n    else\n    {\n        spiffsStatus[\"not_mounted\"] = true;\n    }\n\n    JsonObject activeModules = root.createNestedObject(\"activeModules\");\n    activeModules[\"serial_cli\"] = (this->cliHandler != NULL);\n    activeModules[\"rest_api\"] = (this->isRestApiEnabled);\n    activeModules[\"web_ui\"] = (this->isWebserverEnabled);\n\n    serializeJson(root, statusString);\n}\n\n// ---------------------------------------------------------------------------------\n//             helper functions for stepper communication\n// ---------------------------------------------------------------------------------\nbool ESPStepperMotorServer::isIoPinUsed(int pinToCheck)\n{\n    // TODO move to server configuration class\n    // check stepper configurations\n    for (int i = 0; i < ESPServerMaxSteppers; i++)\n    {\n        ESPStepperMotorServer_StepperConfiguration *stepperConfig = this->serverConfiguration->getStepperConfiguration(i);\n        if (stepperConfig && (stepperConfig->getDirectionIoPin() == pinToCheck || stepperConfig->getStepIoPin() == pinToCheck || stepperConfig->getBrakeIoPin() == pinToCheck))\n        {\n            return true;\n        }\n    }\n    // check switch configurations\n    for (int i = 0; i < ESPServerMaxSwitches; i++)\n    {\n        ESPStepperMotorServer_PositionSwitch *switchConfig = this->serverConfiguration->getSwitch(i);\n        if (switchConfig && switchConfig->getIoPinNumber() == pinToCheck)\n        {\n            return true;\n        }\n    }\n    return false;\n\n    // check encoder configurations\n    for (int i = 0; i < ESPServerMaxRotaryEncoders; i++)\n    {\n        ESPStepperMotorServer_RotaryEncoder *encoderConfig = this->serverConfiguration->getRotaryEncoder(i);\n        if (encoderConfig && (encoderConfig->getPinAIOPin() == pinToCheck || encoderConfig->getPinBIOPin() == pinToCheck))\n        {\n            return true;\n        }\n    }\n    return false;\n}\n\n// ---------------------------------------------------------------------------------\n//                                  WiFi functions\n// ---------------------------------------------------------------------------------\n\nvoid ESPStepperMotorServer::setAccessPointName(const char *accessPointSSID)\n{\n    this->serverConfiguration->apName = accessPointSSID;\n}\n\nvoid ESPStepperMotorServer::setAccessPointPassword(const char *accessPointPassword)\n{\n    this->serverConfiguration->apPassword = accessPointPassword;\n}\n\nvoid ESPStepperMotorServer::setWifiMode(byte wifiMode)\n{\n    switch (wifiMode)\n    {\n    case ESPServerWifiModeAccessPoint:\n        this->serverConfiguration->wifiMode = ESPServerWifiModeAccessPoint;\n        break;\n    case ESPServerWifiModeClient:\n        this->serverConfiguration->wifiMode = ESPServerWifiModeClient;\n        break;\n    case ESPServerWifiModeDisabled:\n        this->serverConfiguration->wifiMode = ESPServerWifiModeDisabled;\n        break;\n    default:\n        ESPStepperMotorServer_Logger::logWarning(\"Invalid WiFi mode given in setWifiMode\");\n        break;\n    }\n}\n\nvoid ESPStepperMotorServer::printCompileSettings()\n{\n    ESPStepperMotorServer_Logger::logDebugf(\"ESPStepperMotorServer compile settings (marcos):\\nMax steppers: %i\\nMax switches: %i\\nMax encoders: %i\\n\", ESPServerMaxSteppers, ESPServerMaxSwitches, ESPServerMaxRotaryEncoders);\n}\n\n/**\n * print the wifi status (ssid, IP Address etc.) on the serial port\n */\nvoid ESPStepperMotorServer::printWifiStatus()\n{\n    ESPStepperMotorServer_Logger::logInfo(\"ESPStepperMotorServer WiFi details:\");\n\n    if (this->serverConfiguration->staticIP != 0)\n    {\n        ESPStepperMotorServer_Logger::logInfof(\"Static IP address has been configured:\\nIP: %s\\nGateway: %s\\nSubnet Mask:%s\\n\", this->serverConfiguration->staticIP.toString().c_str(), this->serverConfiguration->gatewayIP.toString().c_str(), this->serverConfiguration->subnetMask.toString().c_str());\n    }\n\n    if (this->serverConfiguration->wifiMode == ESPServerWifiModeClient)\n    {\n        ESPStepperMotorServer_Logger::logInfo(\"WiFi status: server acts as wifi client in existing network with DHCP\");\n        ESPStepperMotorServer_Logger::logInfof(\"SSID: %s\\n\", this->getCurrentServerConfiguration()->wifiSsid);\n        ESPStepperMotorServer_Logger::logInfof(\"IP address: %s\\n\", WiFi.localIP().toString().c_str());\n        ESPStepperMotorServer_Logger::logInfof(\"Strength: %i dBm\\n\", WiFi.RSSI()); // Received Signal Strength Indicator\n    }\n    else if (this->serverConfiguration->wifiMode == ESPServerWifiModeAccessPoint)\n    {\n        ESPStepperMotorServer_Logger::logInfo(\"WiFi status: access point started\");\n        ESPStepperMotorServer_Logger::logInfof(\"SSID: %s\\n\", this->serverConfiguration->apName);\n        ESPStepperMotorServer_Logger::logInfof(\"IP Address: %s\\n\", WiFi.softAPIP().toString().c_str());\n    }\n    else\n    {\n        ESPStepperMotorServer_Logger::logInfo(\"WiFi is disabled\");\n    }\n}\n\nvoid ESPStepperMotorServer::setWifiSSID(const char *ssid)\n{\n    this->getCurrentServerConfiguration()->wifiSsid = ssid;\n}\n\nvoid ESPStepperMotorServer::setWifiPassword(const char *pwd)\n{\n    this->getCurrentServerConfiguration()->wifiPassword = pwd;\n}\n\nvoid ESPStepperMotorServer::setWifiCredentials(const char *ssid, const char *pwd)\n{\n    this->setWifiSSID(ssid);\n    this->setWifiPassword(pwd);\n}\n\nvoid ESPStepperMotorServer::setStaticIpAddress(IPAddress staticIP, IPAddress gatewayIP, IPAddress subnetMask, IPAddress dns1, IPAddress dns2)\n{\n    this->getCurrentServerConfiguration()->staticIP = staticIP;\n    this->getCurrentServerConfiguration()->gatewayIP = gatewayIP;\n    this->getCurrentServerConfiguration()->subnetMask = subnetMask;\n    this->getCurrentServerConfiguration()->dns1IP = dns1;\n    this->getCurrentServerConfiguration()->dns2IP = dns2;\n}\n\nvoid ESPStepperMotorServer::startAccessPoint()\n{\n    WiFi.softAP(this->serverConfiguration->apName, this->serverConfiguration->apPassword);\n    ESPStepperMotorServer_Logger::logInfof(\"Started Access Point with name %s and IP %s\\n\", this->serverConfiguration->apName, WiFi.softAPIP().toString().c_str());\n}\n\nvoid ESPStepperMotorServer::connectToWifiNetwork()\n{\n    if (WiFi.status() == WL_CONNECTED)\n    {\n        ESPStepperMotorServer_Logger::logInfo(\"Module is already conencted to WiFi network. Will skip WiFi connection procedure\");\n        return;\n    }\n\n    if (this->serverConfiguration->staticIP != 0)\n    {\n        ESPStepperMotorServer_Logger::logInfof(\"Static IP address has been configured, will use %s\\n\", this->serverConfiguration->staticIP.toString().c_str());\n        WiFi.config(this->serverConfiguration->staticIP, this->serverConfiguration->gatewayIP, this->serverConfiguration->subnetMask, this->serverConfiguration->dns1IP, this->serverConfiguration->dns2IP);\n    }\n\n    if ((this->getCurrentServerConfiguration()->wifiSsid != NULL) && (this->getCurrentServerConfiguration()->wifiSsid[0] == '\\0'))\n    {\n        ESPStepperMotorServer_Logger::logWarning(\"No SSID has been configured to connect to. Connection to existing WiFi network aborted\");\n        return;\n    }\n\n    bool noWifiPwd = (!this->getCurrentServerConfiguration()->wifiPassword || this->getCurrentServerConfiguration()->wifiPassword[0] == '\\0');\n    ESPStepperMotorServer_Logger::logInfof(\"Trying to connect to WiFi with SSID '%s' %s...\", this->getCurrentServerConfiguration()->wifiSsid, (noWifiPwd ? \"without password\" : \"\"));\n    if (noWifiPwd)\n    {\n        WiFi.begin(this->getCurrentServerConfiguration()->wifiSsid);\n    }\n    else\n    {\n        WiFi.begin(this->getCurrentServerConfiguration()->wifiSsid, this->getCurrentServerConfiguration()->wifiPassword);\n    }\n    int retryIntervalMs = 500;\n    int timeoutCounter = this->wifiClientConnectionTimeoutSeconds * (1000 / retryIntervalMs);\n    while (WiFi.status() != WL_CONNECTED && timeoutCounter > 0)\n    {\n        delay(retryIntervalMs);\n        ESPStepperMotorServer_Logger::logInfo(\".\", false, true);\n        if (timeoutCounter == (this->wifiClientConnectionTimeoutSeconds * 2 - 3))\n        {\n            WiFi.reconnect();\n        }\n        timeoutCounter--;\n    }\n    ESPStepperMotorServer_Logger::logInfo(\"\\n\", false, true);\n\n    if (timeoutCounter > 0)\n    {\n        ESPStepperMotorServer_Logger::logInfof(\"Connected to network with IP address %s\\n\", WiFi.localIP().toString().c_str());\n    }\n    else\n    {\n        ESPStepperMotorServer_Logger::logWarningf(\"Connection to WiFi network with SSID '%s' failed with timeout\\n\", this->getCurrentServerConfiguration()->wifiSsid);\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n        ESPStepperMotorServer_Logger::logDebugf(\"Connection timeout is set to %i seconds\\n\", this->wifiClientConnectionTimeoutSeconds);\n#endif\n        ESPStepperMotorServer_Logger::logWarningf(\"starting server in access point mode with SSID '%s' and password '%s' as fallback\\n\", this->serverConfiguration->apName, this->serverConfiguration->apPassword);\n        this->setWifiMode(ESPServerWifiModeAccessPoint);\n        this->startAccessPoint();\n    }\n}\n\nvoid ESPStepperMotorServer::scanWifiNetworks()\n{\n    int numberOfNetworks = WiFi.scanNetworks();\n\n    Serial.print(\"Number of networks found:\");\n    Serial.println(numberOfNetworks);\n\n    for (int i = 0; i < numberOfNetworks; i++)\n    {\n        Serial.print(\"SSID: \");\n        Serial.println(WiFi.SSID(i));\n\n        Serial.print(\"Signal strength: \");\n        Serial.println(WiFi.RSSI(i));\n    }\n}\n\n// ---------------------------------------------------------------------------------\n//                                 IO Setup and Interrupt functions\n// ---------------------------------------------------------------------------------\n\n/**\n * setup the IO Pin to INPUT mode\n */\nvoid ESPStepperMotorServer::setupPositionSwitchIOPin(ESPStepperMotorServer_PositionSwitch *posSwitch)\n{\n    if (posSwitch)\n    {\n        if (posSwitch->isActiveHigh())\n        {\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n            ESPStepperMotorServer_Logger::logDebugf(\"Setting up IO pin %i as input for active high switch '%s' (%i)\\n\", posSwitch->getIoPinNumber(), posSwitch->getPositionName().c_str(), posSwitch->getId());\n#endif\n            pinMode(posSwitch->getIoPinNumber(), INPUT);\n        }\n        else\n        {\n            if (posSwitch->getIoPinNumber() == 34 || posSwitch->getIoPinNumber() == 35 || posSwitch->getIoPinNumber() == 36 || posSwitch->getIoPinNumber() == 39)\n            {\n                ESPStepperMotorServer_Logger::logWarningf(\"The configured IO pin %i cannot be used for active low switches unless an external pull up resistor is in place. The ESP does not provide internal pullups on this IO pin. Make sure you have a pull up resistor in place for the switch %s (%i)\\n\", posSwitch->getIoPinNumber(), posSwitch->getPositionName(), posSwitch->getId());\n            }\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n            ESPStepperMotorServer_Logger::logDebugf(\"Setting up IO pin %i as input with pullup for active low switch '%s' (%i)\\n\", posSwitch->getIoPinNumber(), posSwitch->getPositionName().c_str(), posSwitch->getId());\n#endif\n            pinMode(posSwitch->getIoPinNumber(), INPUT_PULLUP);\n        }\n    }\n}\n\nvoid ESPStepperMotorServer::setupRotaryEncoderIOPin(ESPStepperMotorServer_RotaryEncoder *rotaryEncoder)\n{\n// set Pins for encoder\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n    ESPStepperMotorServer_Logger::logDebugf(\"Setting up IO pin %i as Pin A input with internal pullup for rotary encoder '%s' (%i)\\n\", rotaryEncoder->getPinAIOPin(), rotaryEncoder->getDisplayName().c_str(), rotaryEncoder->getId());\n#endif\n    pinMode(rotaryEncoder->getPinAIOPin(), INPUT_PULLUP);\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n    ESPStepperMotorServer_Logger::logDebugf(\"Setting up IO pin %i as Pin B input with internal pullup for rotary encoder '%s' (%i)\\n\", rotaryEncoder->getPinBIOPin(), rotaryEncoder->getDisplayName().c_str(), rotaryEncoder->getId());\n#endif\n    pinMode(rotaryEncoder->getPinBIOPin(), INPUT_PULLUP);\n}\n\n/**\n * setup the IO Pins for all configured switches and encoders\n */\nvoid ESPStepperMotorServer::setupAllIOPins()\n{\n    // setup IO pins for all switches\n    for (byte switchId = 0; switchId < ESPServerMaxSwitches; switchId++)\n    {\n        ESPStepperMotorServer_PositionSwitch *switchConfig = this->serverConfiguration->getSwitch(switchId);\n        if (switchConfig)\n        {\n            this->setupPositionSwitchIOPin(switchConfig);\n        }\n    }\n    // Setup IO pins for all encoders\n    for (byte encoderId = 0; encoderId < ESPServerMaxRotaryEncoders; encoderId++)\n    {\n        ESPStepperMotorServer_RotaryEncoder *encoderConfig = this->serverConfiguration->getRotaryEncoder(encoderId);\n        if (encoderConfig)\n        {\n            this->setupRotaryEncoderIOPin(encoderConfig);\n        }\n    }\n\n    this->updateSwitchStatusRegister();\n}\n\n/**\n * Register ISR according to switch type (active high or active low) for all configured position switches\n */\nvoid ESPStepperMotorServer::attachAllInterrupts()\n{\n    for (int i = 0; i < ESPServerMaxSwitches; i++)\n    {\n        ESPStepperMotorServer_PositionSwitch *posSwitch = this->serverConfiguration->getSwitch(i);\n        if (posSwitch)\n        {\n            char irqNum = digitalPinToInterrupt(posSwitch->getIoPinNumber());\n            if (irqNum == NOT_AN_INTERRUPT)\n            {\n                ESPStepperMotorServer_Logger::logWarningf(\"Failed to determine IRQ# for given IO pin %i, thus setting up of interrupt for the position switch '%s' failed\\n\", posSwitch->getIoPinNumber(), posSwitch->getPositionName().c_str());\n            }\n            else\n            {\n                _BV(irqNum); // clear potentially pending interrupts\n                // register emergency stop switches\n                if (posSwitch->isEmergencySwitch())\n                {\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n                    ESPStepperMotorServer_Logger::logDebugf(\"Attaching interrupt service routine for emergency stop switch '%s' on IO pin %i\\n\", posSwitch->getPositionName().c_str(), posSwitch->getIoPinNumber());\n#endif\n                    attachInterrupt(irqNum, staticEmergencySwitchISR, CHANGE);\n                }\n                // register limit switches\n                else if (posSwitch->isLimitSwitch())\n                {\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n                    ESPStepperMotorServer_Logger::logDebugf(\"Attaching interrupt service routine for limit switch '%s' on IO pin %i\\n\", posSwitch->getPositionName().c_str(), posSwitch->getIoPinNumber());\n#endif\n                    if (posSwitch->isTypeBitSet(SWITCHTYPE_LIMITSWITCH_POS_END_BIT))\n                    {\n                        attachInterrupt(irqNum, staticLimitSwitchISR_POS_END, CHANGE);\n                    }\n                    else if (posSwitch->isTypeBitSet(SWITCHTYPE_LIMITSWITCH_POS_BEGIN_BIT))\n                    {\n                        attachInterrupt(irqNum, staticLimitSwitchISR_POS_BEGIN, CHANGE);\n                    }\n                    else if (posSwitch->isTypeBitSet(SWITCHTYPE_LIMITSWITCH_COMBINED_BEGIN_END_BIT))\n                    {\n                        attachInterrupt(irqNum, staticLimitSwitchISR_COMBINED, CHANGE);\n                    }\n                }\n                // register general position switches & others\n                else\n                {\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n                    ESPStepperMotorServer_Logger::logDebugf(\"Attaching interrupt service routine for general position switch '%s' on IO pin %i\\n\", posSwitch->getPositionName().c_str(), posSwitch->getIoPinNumber());\n#endif\n                    attachInterrupt(irqNum, staticPositionSwitchISR, CHANGE);\n                }\n            }\n        }\n    }\n\n    for (int i = 0; i < ESPServerMaxRotaryEncoders; i++)\n    {\n        ESPStepperMotorServer_RotaryEncoder *rotaryEncoder = this->serverConfiguration->getRotaryEncoder(i);\n        if (rotaryEncoder != NULL)\n        {\n            // we do a loop here to save some program memory, could also externalize code block in another function\n            const unsigned char pins[2] = {rotaryEncoder->getPinAIOPin(), rotaryEncoder->getPinBIOPin()};\n            for (int i = 0; i < 2; i++)\n            {\n                char irqNum = digitalPinToInterrupt(pins[i]);\n                if (irqNum == NOT_AN_INTERRUPT)\n                {\n                    ESPStepperMotorServer_Logger::logWarningf(\"Failed to determine IRQ# for given IO pin %i, thus setting up of interrupt for the rotary encoder failed for pin %s\\n\", pins[i], rotaryEncoder->getDisplayName().c_str());\n                }\n\n                _BV(irqNum); // clear potentially pending interrupts\n                attachInterrupt(irqNum, staticRotaryEncoderISR, CHANGE);\n            }\n        }\n    }\n}\n\nvoid ESPStepperMotorServer::detachInterruptForPositionSwitch(ESPStepperMotorServer_PositionSwitch *posSwitch)\n{\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n    ESPStepperMotorServer_Logger::logDebugf(\"detaching interrupt for position switch %s on IO Pin %i\\n\", posSwitch->getPositionName().c_str(), posSwitch->getIoPinNumber());\n#endif\n    detachInterrupt(digitalPinToInterrupt(posSwitch->getIoPinNumber()));\n}\n\nvoid ESPStepperMotorServer::detachInterruptForRotaryEncoder(ESPStepperMotorServer_RotaryEncoder *rotaryEncoder)\n{\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n    ESPStepperMotorServer_Logger::logDebugf(\"detaching interrupts for rotary encoder %s on IO Pins %i and %i\\n\", rotaryEncoder->getDisplayName().c_str(), rotaryEncoder->getPinAIOPin(), rotaryEncoder->getPinBIOPin());\n#endif\n    // Pin A of rotary encoder\n    if (digitalPinToInterrupt(rotaryEncoder->getPinAIOPin()) != NOT_AN_INTERRUPT)\n    {\n        detachInterrupt(digitalPinToInterrupt(rotaryEncoder->getPinAIOPin()));\n    }\n    // Pin B of rotary encoder\n    if (digitalPinToInterrupt(rotaryEncoder->getPinBIOPin()) != NOT_AN_INTERRUPT)\n    {\n        detachInterrupt(digitalPinToInterrupt(rotaryEncoder->getPinBIOPin()));\n    }\n}\n\n/**\n * clear/disable all interrupts for position switches\n **/\nvoid ESPStepperMotorServer::detachAllInterrupts()\n{\n    for (int i = 0; i < ESPServerMaxSwitches; i++)\n    {\n        ESPStepperMotorServer_PositionSwitch *posSwitch = this->serverConfiguration->getSwitch(i);\n        if (posSwitch)\n        {\n            this->detachInterruptForPositionSwitch(posSwitch);\n        }\n    }\n    for (int i = 0; i < ESPServerMaxRotaryEncoders; i++)\n    {\n        ESPStepperMotorServer_RotaryEncoder *rotaryEncoder = this->serverConfiguration->getRotaryEncoder(i);\n        if (rotaryEncoder != NULL)\n        {\n            this->detachInterruptForRotaryEncoder(rotaryEncoder);\n        }\n    }\n}\n\n/**\n * Trigger an emergency stop. Can be called with optional stepper ID to only trigger emergency stop for a specific stepper.\n * If called without the parameter, all configured steppers will be stopped\n **************************************************************************************************************************\n * IMPORTANT NOTE: This function can be called manually, but will also be called from the ISR of the Emegerncy switches,  *\n * so it should be kept as short as possible and not use the CoProcessor (E.g. for floating point arithmetic operations)  *\n **************************************************************************************************************************\n */\nvoid ESPStepperMotorServer::performEmergencyStop(int stepperId)\n{\n    this->emergencySwitchIsActive = true;\n    // only perform emergency stop for one stepper\n    if (stepperId > -1 && stepperId != 255)\n    {\n        ESPStepperMotorServer_StepperConfiguration *stepper = this->serverConfiguration->getStepperConfiguration(stepperId);\n        if (stepper)\n        {\n            stepper->getFlexyStepper()->emergencyStop();\n        }\n    }\n    else\n    {\n        // perform complete stop on all steppers\n        ESP_FlexyStepper **configuredFlexySteppers = this->serverConfiguration->getConfiguredFlexySteppers();\n        for (byte i = 0; i < ESPServerMaxSteppers; i++)\n        {\n            if (configuredFlexySteppers[i])\n            {\n                configuredFlexySteppers[i]->emergencyStop();\n            }\n            else\n            {\n                break;\n            }\n        }\n    }\n}\n\nvoid ESPStepperMotorServer::revokeEmergencyStop()\n{\n    this->emergencySwitchIsActive = false;\n}\n\n/**\n * Update the switch status register by reading all configured IO pins.\n * Returns the pin Number of the last IO pin where a change has been detected for since last the update of the register.\n * -1 is returned if not change could be detected / no switch is configured\n */\nsigned char ESPStepperMotorServer::updateSwitchStatusRegister()\n{\n    byte registerIndex = 0;\n    signed char changedSwitchIndex = -1;\n    signed char *allSwitchIoPins = this->serverConfiguration->allSwitchIoPins;\n    volatile byte *buttonStatus = this->buttonStatus;\n    // iterate over all configured position switch IO pins and read state and write to status registers\n    for (int switchIndex = 0; switchIndex < ESPServerMaxSwitches; switchIndex++)\n    {\n        signed char ioPin = allSwitchIoPins[switchIndex];\n        if (ioPin > -1)\n        {\n            if (switchIndex > 7) // write to next register if needed\n            {\n                registerIndex = (byte)(ceil)((switchIndex + 1) / 8);\n            }\n            byte previousPinState = bitRead(buttonStatus[registerIndex], switchIndex % 8);\n            byte currentPinState = digitalRead(ioPin);\n\n            if (currentPinState == HIGH && previousPinState == LOW)\n            {\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n                if (ESPStepperMotorServer_Logger::isDebugEnabled())\n                    ESPStepperMotorServer_Logger::logDebugf(\"Setting bit %i to high in register for switch %i with io pin %i\\n\", (switchIndex % 8), switchIndex, ioPin);\n#endif\n                bitSet(this->buttonStatus[registerIndex], switchIndex % 8);\n                changedSwitchIndex = switchIndex;\n            }\n            else if (currentPinState == LOW && previousPinState == HIGH)\n            {\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n                if (ESPStepperMotorServer_Logger::isDebugEnabled())\n                    ESPStepperMotorServer_Logger::logDebugf(\"Setting bit %i to low in register for switch %i with io pin %i\\n\", (switchIndex % 8), switchIndex, ioPin);\n#endif\n                bitClear(buttonStatus[registerIndex], switchIndex % 8);\n                changedSwitchIndex = switchIndex;\n            }\n        }\n    }\n    return changedSwitchIndex;\n}\n\nvoid IRAM_ATTR ESPStepperMotorServer::staticPositionSwitchISR()\n{\n    anchor->internalSwitchISR(SWITCHTYPE_POSITION_SWITCH_BIT);\n}\n\nvoid IRAM_ATTR ESPStepperMotorServer::staticEmergencySwitchISR()\n{\n    anchor->internalEmergencySwitchISR();\n}\n\nvoid IRAM_ATTR ESPStepperMotorServer::staticLimitSwitchISR_POS_BEGIN()\n{\n    anchor->internalSwitchISR(SWITCHTYPE_LIMITSWITCH_POS_BEGIN_BIT);\n}\n\nvoid IRAM_ATTR ESPStepperMotorServer::staticLimitSwitchISR_POS_END()\n{\n    anchor->internalSwitchISR(SWITCHTYPE_LIMITSWITCH_POS_END_BIT);\n}\n\nvoid IRAM_ATTR ESPStepperMotorServer::staticLimitSwitchISR_COMBINED()\n{\n    anchor->internalSwitchISR(SWITCHTYPE_LIMITSWITCH_COMBINED_BEGIN_END_BIT);\n}\n\nvoid IRAM_ATTR ESPStepperMotorServer::staticRotaryEncoderISR()\n{\n    anchor->internalRotaryEncoderISR();\n}\n\nvoid IRAM_ATTR ESPStepperMotorServer::internalEmergencySwitchISR()\n{\n    ESPStepperMotorServer_PositionSwitch *switchConfig;\n    for (byte i = 0; i < ESPServerMaxSwitches; i++)\n    {\n        switchConfig = this->serverConfiguration->configuredEmergencySwitches[i];\n        if (switchConfig != NULL)\n        {\n            byte registerIndex = 0;\n            byte switchId = switchConfig->getId();\n            if (switchId > 7) // write to next register if needed\n                registerIndex = (byte)(ceil)((switchId + 1) / 8);\n\n            byte pinState = digitalRead(switchConfig->getIoPinNumber());\n            if (pinState)\n                bitSet(this->buttonStatus[registerIndex], (switchId % 8));\n            else\n                bitClear(this->buttonStatus[registerIndex], (switchId % 8));\n\n            bool isActiveHigh = switchConfig->isActiveHigh();\n            bool switchIsActive = ((pinState && isActiveHigh) || (!pinState && !isActiveHigh));\n            if (switchIsActive)\n                this->performEmergencyStop(switchConfig->getStepperIndex());\n            else\n                // TODO: this might cause issues with multiple Emergency Switches connected since it will revoke the global flag\n                // so if only the first switch is triggered, the second one will be checked in this loop and revoke the flag\n                this->emergencySwitchIsActive = false;\n        }\n        else\n            break;\n    }\n}\n\n/**\n * ISR for general switch interrupts\n * NOTE: this ISR is not called for emergency switches (since fastest possible processing time is required and we need to avoid all these loops).\n * Look at internalEmergencySwitchISR() instead for emergency switch handling\n */\nvoid IRAM_ATTR ESPStepperMotorServer::internalSwitchISR(byte switchType)\n{\n    signed char changedStausSwitchId = this->updateSwitchStatusRegister();\n    if (changedStausSwitchId > -1 && (switchType == SWITCHTYPE_LIMITSWITCH_POS_BEGIN_BIT || switchType == SWITCHTYPE_LIMITSWITCH_POS_END_BIT || switchType == SWITCHTYPE_LIMITSWITCH_COMBINED_BEGIN_END_BIT))\n    {\n        ESPStepperMotorServer_Configuration *configuration = this->serverConfiguration;\n        ESPStepperMotorServer_PositionSwitch *switchConfig = configuration->allConfiguredSwitches[changedStausSwitchId];\n        if (switchConfig)\n        {\n            bool isActiveHigh = switchConfig->_switchType & (1 << (SWITCHTYPE_STATE_ACTIVE_HIGH_BIT - 1)); // we do not use the helper function isActiveHigh due to performance reasons\n            bool inputState = digitalRead(switchConfig->_ioPinNumber);\n            ESPStepperMotorServer_StepperConfiguration *stepper = configuration->getStepperConfiguration(switchConfig->_stepperIndex);\n            if (stepper)\n            {\n                if ((inputState && isActiveHigh) || (!inputState && !isActiveHigh))\n                {\n                    switch (switchType)\n                    {\n                    case SWITCHTYPE_LIMITSWITCH_POS_BEGIN_BIT:\n                        stepper->_flexyStepper->setLimitSwitchActive(ESP_FlexyStepper::LIMIT_SWITCH_BEGIN);\n                        break;\n                    case SWITCHTYPE_LIMITSWITCH_POS_END_BIT:\n                        stepper->_flexyStepper->setLimitSwitchActive(ESP_FlexyStepper::LIMIT_SWITCH_END);\n                        break;\n                    case SWITCHTYPE_LIMITSWITCH_COMBINED_BEGIN_END_BIT:\n                        stepper->_flexyStepper->setLimitSwitchActive(ESP_FlexyStepper::LIMIT_SWITCH_COMBINED_BEGIN_AND_END);\n                        break;\n                    }\n                }\n                else\n                {\n                    stepper->_flexyStepper->clearLimitSwitchActive();\n                }\n            }\n        }\n        else\n        {\n            ESPStepperMotorServer_Logger::logWarningf(\"A IO Pin change has been detected for switch id %i which is not a limit switch, but the ISR was triggered for a switch of type limit switch. It is possible that a limit switch status change has not been detected properly\\n\", changedStausSwitchId);\n        }\n    }\n}\n\n/**\n * the ISR to handle rotary encoder related pin interrupts and trigger the stepper position change\n */\nvoid IRAM_ATTR ESPStepperMotorServer::internalRotaryEncoderISR()\n{\n    ESPStepperMotorServer_Configuration *configuration = this->serverConfiguration;\n    for (int i = 0; i < ESPServerMaxRotaryEncoders; i++)\n    {\n        ESPStepperMotorServer_RotaryEncoder *rotaryEncoder = configuration->configuredRotaryEncoders[i];\n        if (rotaryEncoder != NULL)\n        {\n            unsigned char result = rotaryEncoder->process();\n            ESPStepperMotorServer_StepperConfiguration *stepperConfig = configuration->configuredSteppers[rotaryEncoder->_stepperIndex];\n            if (stepperConfig)\n            {\n                if (result == DIR_CW)\n                {\n                    stepperConfig->_flexyStepper->setTargetPositionRelativeInSteps(1 * rotaryEncoder->_stepMultiplier);\n                }\n                else if (result == DIR_CCW)\n                {\n                    signed long newPosition = -1L * rotaryEncoder->_stepMultiplier;\n                    stepperConfig->_flexyStepper->setTargetPositionRelativeInSteps(newPosition);\n                }\n            }\n            else\n            {\n                ESPStepperMotorServer_Logger::logWarningf(\"Invalid stepper config id %i for rotary enc. (id=%i)\\n\", rotaryEncoder->_stepperIndex, i);\n            }\n        }\n    }\n}\n\n// ----------------- delegator functions to ease API usage -------------------------\n\nvoid ESPStepperMotorServer::setLogLevel(byte logLevel)\n{\n    ESPStepperMotorServer_Logger::setLogLevel(logLevel);\n}\n\n// -------------------------------------- End --------------------------------------\n"
  },
  {
    "path": "src/ESPStepperMotorServer.h",
    "content": "\n//      ******************************************************************\n//      *                                                                *\n//      *             Header file for ESPStepperMotorServer.cpp          *\n//      *                                                                *\n//      *               Copyright (c) Paul Kerspe, 2019                  *\n//      *                                                                *\n//      ******************************************************************\n\n// this project is not supposed to replace a controller of a CNC machine but more of a general approach on working with stepper motors\n// for a good Arduino/ESP base Gerber compatible controller Project see:\n// https://github.com/gnea/grbl\n// and for ESP32: https://github.com/bdring/Grbl_Esp32\n// currently no G-Code (http://linuxcnc.org/docs/html/gcode.html) parser is implemented, yet it might be part of a future release\n// other usefull informaion when connecting your ESP32 board to your driver boards and you are not sure which pins to use: https://randomnerdtutorials.com/esp32-pinout-reference-gpios/\n\n// MIT License\n//\n// Copyright (c) 2019 Paul Kerspe\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 furnished\n// to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// 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 THE\n// SOFTWARE.\n\n#ifndef ESPStepperMotorServer_h\n#define ESPStepperMotorServer_h\n\n#define ESPServerMaxSwitches 10\n#define ESPServerSwitchStatusRegisterCount 2 //NOTE: this value must be chosen according to the value of ESPServerMaxSwitches: val = ceil(ESPServerMaxSwitches / 8)\n#define ESPServerMaxSteppers 10\n#define ESPServerMaxRotaryEncoders 5\n#define ESPStepperMotorServer_SwitchDisplayName_MaxLength 20\n\n#include <ESP_FlexyStepper.h>\n#include <LittleFS.h>\n#include <ArduinoJson.h>\n#include <WiFi.h>\n\n#ifndef ESPStepperMotorServer_COMPILE_NO_WEB\n#include <ESPAsyncWebServer.h>\n#include <ESPStepperMotorServer_WebInterface.h>\n#endif\n\n#include <ESPStepperMotorServer_CLI.h>\n#include <ESPStepperMotorServer_Configuration.h>\n#include <ESPStepperMotorServer_MotionController.h>\n#include <ESPStepperMotorServer_MacroAction.h>\n#include <ESPStepperMotorServer_PositionSwitch.h>\n#include <ESPStepperMotorServer_StepperConfiguration.h>\n#include <ESPStepperMotorServer_RotaryEncoder.h>\n#include <ESPStepperMotorServer_Logger.h>\n\n#ifndef ESPStepperMotorServer_COMPILE_NO_WEB\n#include <ESPStepperMotorServer_RestAPI.h>\n#endif\n\n#define ESPServerWifiModeDisabled 0\n#define ESPServerWifiModeAccessPoint 1\n#define ESPServerWifiModeClient 2\n\n#define ESPServerRestApiEnabled 2\n#define ESPServerWebserverEnabled 4\n#define ESPServerSerialEnabled 8\n\n#define ESPServerSwitchType_ActiveHigh 1\n#define ESPServerSwitchType_ActiveLow 2\n\n#define ESPServerSwitchType_HomingSwitchBegin 4\n#define ESPServerSwitchType_HomingSwitchEnd 8\n#define ESPServerSwitchType_GeneralPositionSwitch 16\n#define ESPServerSwitchType_EmergencyStopSwitch 32\n\n#define ESPStepperHighestAllowedIoPin 50\n\n//just forward declare class here for compiler, since we have a circular dependency (due to bad api design :-))\nclass ESPStepperMotorServer_CLI;\nclass ESPStepperMotorServer_RestAPI;\nclass ESPStepperMotorServer_Configuration;\nclass ESPStepperMotorServer_MotionController;\nclass ESPStepperMotorServer_MacroAction;\n\n#ifndef ESPStepperMotorServer_COMPILE_NO_WEB\nclass ESPStepperMotorServer_WebInterface;\nclass ESPStepperMotorServer_RestAPI;\n#endif\n//\n// the ESPStepperMotorServer class\n// TODO: remove all wifi stuff if not needed using: #if defined(ESPServerWifiModeClient) || defined(ESPServerWifiModeAccessPoint)\nclass ESPStepperMotorServer\n{\n  friend class ESPStepperMotorServer_MotionController;\n\npublic:\n  ESPStepperMotorServer(byte serverMode, byte logLevel = ESPServerLogLevel_INFO);\n  ESPStepperMotorServer(const ESPStepperMotorServer &espStepperMotorServer);\n  ~ESPStepperMotorServer();\n\n#ifndef ESPStepperMotorServer_COMPILE_NO_WEB\n  void setHttpPort(int portNumber);\n  void onWebSocketEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len);\n  void sendSocketMessageToAllClients(const char *message, size_t len);\n#endif\n\n  void setAccessPointName(const char *accessPointSSID);\n  void setAccessPointPassword(const char *accessPointPassword);\n  void setWifiCredentials(const char *ssid, const char *pwd);\n  void setWifiSSID(const char *ssid);\n  void setWifiPassword(const char *pwd);\n  void setWifiMode(byte wifiMode);\n  void setStaticIpAddress(IPAddress staticIP, IPAddress gatewayIP, IPAddress subnetMask, IPAddress dns1 = (uint32_t) 0x00000000, IPAddress dns2 = (uint32_t) 0x00000000);\n  void printWifiStatus();\n  void printCompileSettings();\n  int addOrUpdateStepper(ESPStepperMotorServer_StepperConfiguration *stepper, int stepperIndex = -1);\n  int addOrUpdatePositionSwitch(ESPStepperMotorServer_PositionSwitch *posSwitchToAdd, int switchIndex = -1);\n  int addOrUpdateRotaryEncoder(ESPStepperMotorServer_RotaryEncoder *rotaryEncoder, int encoderIndex = -1);\n  void removePositionSwitch(int positionSwitchIndex);\n  void removeStepper(byte stepperConfigurationIndex);\n  void removeRotaryEncoder(byte rotaryEncoderConfigurationIndex);\n  void getFormattedPositionSwitchStatusRegister(byte registerIndex, String &output);\n  void printPositionSwitchStatus();\n  void performEmergencyStop(int stepperIndex = -1);\n  void revokeEmergencyStop();\n  void start();\n  void stop();\n  byte getPositionSwitchStatus(int positionSwitchIndex);\n  signed char updateSwitchStatusRegister();\n  String getIpAddress();\n  ESPStepperMotorServer_Configuration *getCurrentServerConfiguration();\n  ESPStepperMotorServer_CLI *getCLIHandler() const;\n  void requestReboot(String rebootReason);\n  bool isSPIFFSMounted();\n\n  //delegator functions only\n  void setLogLevel(byte);\n  void getServerStatusAsJsonString(String &statusString);\n  byte getFirstAvailableConfigurationSlotForRotaryEncoder();\n  bool isIoPinUsed(int);\n\n  //\n  // public member variables\n  //\n  const char *defaultConfigurationFilename = \"/config.json\";\n  int wifiClientConnectionTimeoutSeconds = 25;\n  // a boolean indicating if a position switch that has been configure as emegrency switch, has been triggered\n  volatile boolean emergencySwitchIsActive = false;\n\n  const char *version = \"0.4.7\";\n\nprivate:\n  void scanWifiNetworks();\n  void connectToWifiNetwork();\n  void startAccessPoint();\n\n#ifndef ESPStepperMotorServer_COMPILE_NO_WEB\n  void startWebserver();\n  void registerWebInterfaceUrls();\n  bool checkIfGuiExistsInSpiffs();\n  bool downloadFileToSpiffs(const char *url, const char *targetPath);\n#endif\n\n  void startSPIFFS();\n  void printSPIFFSStats();\n  int getSPIFFSFreeSpace();\n  void printSPIFFSRootFolderContents();\n  void setupAllIOPins();\n  void setupPositionSwitchIOPin(ESPStepperMotorServer_PositionSwitch *posSwitch);\n  void setupRotaryEncoderIOPin(ESPStepperMotorServer_RotaryEncoder *rotaryEncoder);\n  void detachInterruptForPositionSwitch(ESPStepperMotorServer_PositionSwitch *posSwitch);\n  void detachInterruptForRotaryEncoder(ESPStepperMotorServer_RotaryEncoder *rotaryEncoder);\n  void detachAllInterrupts();\n  void attachAllInterrupts();\n  void setPositionSwitchStatus(int positionSwitchIndex, byte status);\n\n  // ISR handling\n  static void staticPositionSwitchISR();\n  static void staticEmergencySwitchISR();\n  static void staticLimitSwitchISR_POS_END();\n  static void staticLimitSwitchISR_POS_BEGIN();\n  static void staticLimitSwitchISR_COMBINED();\n  static void staticRotaryEncoderISR();\n\n  void internalEmergencySwitchISR();\n  void internalSwitchISR(byte switchType);\n  void internalRotaryEncoderISR();\n\n  //\n  // private member variables\n  //\n  byte enabledServices;\n  boolean isWebserverEnabled = false;\n  boolean isRestApiEnabled = false;\n  boolean isCLIEnabled = false;\n  boolean isServerStarted = false;\n  boolean isSPIFFSactive = false;\n  boolean _isRebootScheduled = false;\n\n  ESPStepperMotorServer_Configuration *serverConfiguration;\n\n#ifndef ESPStepperMotorServer_COMPILE_NO_WEB\n  ESPStepperMotorServer_WebInterface *webInterfaceHandler;\n  ESPStepperMotorServer_RestAPI *restApiHandler;\n  AsyncWebServer *httpServer;\n  AsyncWebSocket *webSocketServer;\n#endif\n\n  ESPStepperMotorServer_CLI *cliHandler;\n  ESPStepperMotorServer_MotionController *motionControllerHandler;\n  static ESPStepperMotorServer *anchor; //used for self-reference in ISR\n  // the button status register for all configured button switches\n  volatile byte buttonStatus[ESPServerSwitchStatusRegisterCount] = {0};\n};\n\n// ------------------------------------ End ---------------------------------\n#endif\n"
  },
  {
    "path": "src/ESPStepperMotorServer_CLI.cpp",
    "content": "\n//      *********************************************************\n//      *                                                       *\n//      *     ESP32 Stepper Motor Command Line Interface        *\n//      *                                                       *\n//      *            Copyright (c) Paul Kerspe, 2019            *\n//      *                                                       *\n//      **********************************************************\n\n// MIT License\n//\n// Copyright (c) 2019 Paul Kerspe\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 furnished\n// to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// 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 THE\n// SOFTWARE.\n\n#include <ESPStepperMotorServer_CLI.h>\n\n#define CR '\\r'\n#define LF '\\n'\n#define BS '\\b'\n#define NULLCHAR '\\0'\n#define COMMAND_BUFFER_LENGTH 50 //length of serial buffer for incoming commands\n\n// ---------------------------------------------------------------------------------\n//                                  Setup functions\n// ---------------------------------------------------------------------------------\n//\n// constructor for the command line interface module\n// creates a freeRTOS Task that runs in the background and polls the serial interface for input to parse\n//\nESPStepperMotorServer_CLI::ESPStepperMotorServer_CLI(ESPStepperMotorServer *serverRef)\n{\n  this->serverRef = serverRef;\n}\n\nESPStepperMotorServer_CLI::~ESPStepperMotorServer_CLI()\n{\n  if (this->xHandle != NULL)\n  {\n    this->stop();\n  }\n}\n\nvoid ESPStepperMotorServer_CLI::start()\n{\n  xTaskCreate(\n      ESPStepperMotorServer_CLI::processSerialInput, /* Task function. */\n      \"SerialInterfacePoller\",                       /* String with name of task. */\n      10000,                                         /* Stack size in bytes. */\n      this,                                          /* Parameter passed as input of the task */\n      1,                                             /* Priority of the task. */\n      &this->xHandle);                               /* Task handle. */\n  this->registerCommands();\n  ESPStepperMotorServer_Logger::logInfof(\"Command Line Interface started, registered %i commands. Type 'help' to get a list of all supported commands\\n\", this->commandCounter);\n}\n\nvoid ESPStepperMotorServer_CLI::stop()\n{\n  vTaskDelete(this->xHandle);\n  this->xHandle = NULL;\n  ESPStepperMotorServer_Logger::logInfo(\"Command Line Interface stopped\");\n}\n\nvoid ESPStepperMotorServer_CLI::executeCommand(String cmd)\n{\n  char cmdCharArray[cmd.length() + 1];\n  strcpy(cmdCharArray, cmd.c_str());\n  char *pureCommand = strtok(cmdCharArray, _CMD_PARAM_SEPARATOR);\n  char *arguments = strtok(NULL, \"=\");\n\n  // first try the built in commands\n  for (int i = 0; i < this->commandCounter; i++)\n  {\n    if (this->allRegisteredCommands[i].command.equals(pureCommand) || this->allRegisteredCommands[i].shortCut.equals(pureCommand))\n    {\n      (this->*command_functions[i])(pureCommand, arguments);\n      return;\n    }\n  }\n  // next, see if it's a user defined command\n  for (int i = 0; i < this->userCommandCounter; i++)\n  {\n    if (this->allRegisteredUserCommands[i].command.equals(pureCommand) || this->allRegisteredUserCommands[i].shortCut.equals(pureCommand))\n    {\n      (this->user_command_functions[i])(pureCommand, arguments);\n      return;\n    }\n  }\n  Serial.printf(\"error: Command '%s' is unknown\\n\", cmd.c_str());\n}\n\nvoid ESPStepperMotorServer_CLI::processSerialInput(void *parameter)\n{\n  ESPStepperMotorServer_CLI *ref = static_cast<ESPStepperMotorServer_CLI *>(parameter);\n  char commandLine[COMMAND_BUFFER_LENGTH + 1];\n  uint8_t charsRead = 0;\n  char buffer[2];\n  char c;\n  while (true)\n  {\n    while (Serial.available())\n    {\n      Serial.readBytes(buffer, 1); //using this step to avoid the annoying warnings of code quality check to Check buffer boundaries\n      c = buffer[0];\n      switch (c)\n      {\n      case CR: //likely have full command in buffer now, commands are terminated by CR and/or LS\n      case LF:\n        commandLine[charsRead] = NULLCHAR; //null terminate our command char array\n        if (charsRead > 0)\n        {\n          charsRead = 0; //charsRead behaves like 'static' in this task, so have to reset\n          try\n          {\n            ref->executeCommand(String(commandLine));\n          }\n          catch (...)\n          {\n            ESPStepperMotorServer_Logger::logWarningf(\"Caught an exception wil trying to execute command line '%s'\\n\", commandLine);\n          }\n        }\n        break;\n      case BS: // handle backspace in input: put a blank in last char\n        if (charsRead > 0)\n        { //and adjust commandLine and charsRead\n          commandLine[--charsRead] = NULLCHAR;\n        }\n        break;\n      default:\n        if (charsRead < COMMAND_BUFFER_LENGTH)\n        {\n          commandLine[charsRead++] = c;\n        }\n        commandLine[charsRead] = NULLCHAR; //just in case\n        break;\n      }\n    }\n    vTaskDelay(10);\n  }\n}\n\n// ---------------------------------------------------------------------------------\n//                          Command interpreter functions\n// ---------------------------------------------------------------------------------\n\nvoid ESPStepperMotorServer_CLI::registerCommands()\n{\n#ifdef ESPStepperMotorServer_COMPILE_NO_CLI_HELP\n  String emptyHelp = String(\"\");\n#endif\n\n#ifdef ESPStepperMotorServer_COMPILE_NO_CLI_HELP\n  this->registerNewCommand({String(\"help\"), String(\"h\"), emptyHelp, false}, &ESPStepperMotorServer_CLI::cmdHelp);\n  this->registerNewCommand({String(\"moveby\"), String(\"mb\"), emptyHelp, true}, &ESPStepperMotorServer_CLI::cmdMoveBy);\n  this->registerNewCommand({String(\"moveto\"), String(\"mt\"), emptyHelp, true}, &ESPStepperMotorServer_CLI::cmdMoveTo);\n  this->registerNewCommand({String(\"config\"), String(\"c\"), emptyHelp, false}, &ESPStepperMotorServer_CLI::cmdPrintConfig);\n  this->registerNewCommand({String(\"emergencystop\"), String(\"es\"), emptyHelp, false}, &ESPStepperMotorServer_CLI::cmdEmergencyStop);\n  this->registerNewCommand({String(\"revokeemergencystop\"), String(\"res\"), emptyHelp, false}, &ESPStepperMotorServer_CLI::cmdRevokeEmergencyStop);\n  this->registerNewCommand({String(\"position\"), String(\"p\"), emptyHelp, true}, &ESPStepperMotorServer_CLI::cmdGetPosition);\n  this->registerNewCommand({String(\"velocity\"), String(\"v\"), emptyHelp, true}, &ESPStepperMotorServer_CLI::cmdGetCurrentVelocity);\n  this->registerNewCommand({String(\"removeswitch\"), String(\"rsw\"), emptyHelp, true}, &ESPStepperMotorServer_CLI::cmdRemoveSwitch);\n  this->registerNewCommand({String(\"removestepper\"), String(\"rs\"), emptyHelp, true}, &ESPStepperMotorServer_CLI::cmdRemoveStepper);\n  this->registerNewCommand({String(\"removeencoder\"), String(\"re\"), emptyHelp, true}, &ESPStepperMotorServer_CLI::cmdRemoveEncoder);\n  this->registerNewCommand({String(\"reboot\"), String(\"r\"), emptyHelp, false}, &ESPStepperMotorServer_CLI::cmdReboot);\n  this->registerNewCommand({String(\"save\"), String(\"s\"), emptyHelp, false}, &ESPStepperMotorServer_CLI::cmdSaveConfiguration);\n  this->registerNewCommand({String(\"stop\"), String(\"st\"), emptyHelp, false}, &ESPStepperMotorServer_CLI::cmdStopServer);\n  this->registerNewCommand({String(\"loglevel\"), String(\"ll\"), emptyHelp, true}, &ESPStepperMotorServer_CLI::cmdSetLogLevel);\n  this->registerNewCommand({String(\"serverstatus\"), String(\"ss\"), emptyHelp, false}, &ESPStepperMotorServer_CLI::cmdServerStatus);\n  this->registerNewCommand({String(\"switchstatus\"), String(\"pss\"), emptyHelp, false}, &ESPStepperMotorServer_CLI::cmdSwitchStatus);\n  this->registerNewCommand({String(\"setapname\"), String(\"san\"), emptyHelp, true}, &ESPStepperMotorServer_CLI::cmdSetApName);\n  this->registerNewCommand({String(\"setappwd\"), String(\"sap\"), emptyHelp, true}, &ESPStepperMotorServer_CLI::cmdSetApPassword);\n  this->registerNewCommand({String(\"setwifissid\"), String(\"sws\"), emptyHelp, true}, &ESPStepperMotorServer_CLI::cmdSetSSID);\n  this->registerNewCommand({String(\"setwifipwd\"), String(\"swp\"), emptyHelp, true}, &ESPStepperMotorServer_CLI::cmdSetWifiPassword);\n#else\n  this->registerNewCommand({String(\"help\"), String(\"h\"), String(\"show a list of all available commands\"), false}, &ESPStepperMotorServer_CLI::cmdHelp);\n  this->registerNewCommand({String(\"moveby\"), String(\"mb\"), String(\"move by a specified number of units. requires the id of the stepper to move, the amount of movement and also optional the unit for the movement (mm, steps, revs). If no unit is specified steps will be assumed as unit. Optionally you can also set the speed in steps/second, acceleration and deceleration, each in steps/second/second). Set speeds, acceleration and deceleration are rememebered until overwritten again. E.g. mb=0&v:-100&u:mm&s:200 to move the stepper with id 0 by -100 mm with a speed of 200 steps per second\"), true}, &ESPStepperMotorServer_CLI::cmdMoveBy);\n  this->registerNewCommand({String(\"moveto\"), String(\"mt\"), String(\"move to an absolute position. requires the id of the stepper to move, the amount of movement and also optional the unit for the movement (mm, steps, revs). If no unit is specified steps will be assumed as unit. Optionally you can also set the speed in steps/second, acceleration and deceleration, each in steps/second/second). Set speeds, acceleration and deceleration are rememebered until overwritten again. E.g. mt=0&v:100&u:revs&a:100 to move the stepper with id 0 to the absolute position at 100 revolutions with an acceleration of 100 steps per second^2\"), true}, &ESPStepperMotorServer_CLI::cmdMoveTo);\n  this->registerNewCommand({String(\"config\"), String(\"c\"), String(\"print the current configuration to the console as JSON formatted string\"), false}, &ESPStepperMotorServer_CLI::cmdPrintConfig);\n  this->registerNewCommand({String(\"emergencystop\"), String(\"es\"), String(\"trigger emergency stop for all connected steppers. This will clear all target positions and stop the motion controller module immediately. In order to proceed normal operation after this command has been issued, you need to call the revokeemergencystop [res] command\"), false}, &ESPStepperMotorServer_CLI::cmdEmergencyStop);\n  this->registerNewCommand({String(\"revokeemergencystop\"), String(\"res\"), String(\"revoke a previously triggered emergency stop. This must be called before any motions can proceed after a call to the emergencystop command\"), false}, &ESPStepperMotorServer_CLI::cmdRevokeEmergencyStop);\n  this->registerNewCommand({String(\"position\"), String(\"p\"), String(\"get the current position of a specific stepper or all steppers if no explicit index is given (e.g. by calling 'pos' or 'pos=&u:mm'). If no parameter for the unit is provided, will return the position in steps. Requires the ID of the stepper to get the position for as parameter and optional the unit using 'u:mm'/'u:steps'/'u:revs'. E.g.: p=0&u:steps to return the current position of stepper with id = 0 with unit 'steps'\"), true}, &ESPStepperMotorServer_CLI::cmdGetPosition);\n  this->registerNewCommand({String(\"velocity\"), String(\"v\"), String(\"get the current velocity of a specific stepper or all steppers if no explicit index is given (e.g. by calling 'pos' or 'pos=&u:mm'). If no parameter for the unit is provided, will return the position in steps. Requires the ID of the stepper to get the velocity for as parameter and optional the unit using 'u:mm'/'u:steps'/'u:revs'. E.g.: v=0&u:mm to return the velocity in mm per second of stepper with id = 0\"), true}, &ESPStepperMotorServer_CLI::cmdGetCurrentVelocity);\n  this->registerNewCommand({String(\"removeswitch\"), String(\"rsw\"), String(\"remove an existing switch configuration. E.g. rsw=0 to remove the switch with the ID 0\"), true}, &ESPStepperMotorServer_CLI::cmdRemoveSwitch);\n  this->registerNewCommand({String(\"removestepper\"), String(\"rs\"), String(\"remove and existing stepper configuration. E.g. rs=0 to remove the stepper config with the ID 0\"), true}, &ESPStepperMotorServer_CLI::cmdRemoveStepper);\n  this->registerNewCommand({String(\"removeencoder\"), String(\"re\"), String(\"remove an existing rotary encoder configuration. E.g. re=0 to remove the encoder with the ID 0\"), true}, &ESPStepperMotorServer_CLI::cmdRemoveEncoder);\n  this->registerNewCommand({String(\"reboot\"), String(\"r\"), String(\"reboot the ESP (config changes that have not been saved will be lost)\"), false}, &ESPStepperMotorServer_CLI::cmdReboot);\n  this->registerNewCommand({String(\"save\"), String(\"s\"), String(\"save the current configuration to the SPIFFS in config.json\"), false}, &ESPStepperMotorServer_CLI::cmdSaveConfiguration);\n  this->registerNewCommand({String(\"stop\"), String(\"st\"), String(\"stop the stepper server (also stops the CLI!)\"), false}, &ESPStepperMotorServer_CLI::cmdStopServer);\n  this->registerNewCommand({String(\"loglevel\"), String(\"ll\"), String(\"set or get the current log level for serial output. valid values to set are: 1 (Warning) - 4 (ALL). E.g. to set to log level DEBUG use ll=3 to get the current loglevel call without parameter\"), true}, &ESPStepperMotorServer_CLI::cmdSetLogLevel);\n  this->registerNewCommand({String(\"serverstatus\"), String(\"ss\"), String(\"print status details of the server as JSON formated string\"), false}, &ESPStepperMotorServer_CLI::cmdServerStatus);\n  this->registerNewCommand({String(\"switchstatus\"), String(\"pss\"), String(\"print the status of all input switches as JSON formated string\"), false}, &ESPStepperMotorServer_CLI::cmdSwitchStatus);\n  this->registerNewCommand({String(\"setapname\"), String(\"san\"), String(\"set the name of the access point to be opened up by the esp (if in AP mode)\"), true}, &ESPStepperMotorServer_CLI::cmdSetApName);\n  this->registerNewCommand({String(\"setappwd\"), String(\"sap\"), String(\"set the password for the access point to be opened by the esp\"), true}, &ESPStepperMotorServer_CLI::cmdSetApPassword);\n  this->registerNewCommand({String(\"setwifissid\"), String(\"sws\"), String(\"set the SSID of the WiFi to connect to (if in client mode)\"), true}, &ESPStepperMotorServer_CLI::cmdSetSSID);\n  this->registerNewCommand({String(\"setwifipwd\"), String(\"swp\"), String(\"set the password of the Wifi network to connect to\"), true}, &ESPStepperMotorServer_CLI::cmdSetWifiPassword);\n\n#ifndef ESPStepperMotorServer_COMPILE_NO_WEB\n  this->registerNewCommand({String(\"sethttpport\"), String(\"shp\"), String(\"set the http port to listen for for the web interface\"), true}, &ESPStepperMotorServer_CLI::cmdSetHttpPort);\n#endif\n\n#endif\n\n  //TODO: implement missing cmd functions\n  // this->registerNewCommand(\"addswitch\", \"asw\", 1, \"add a new switch configuration\", &ESPStepperMotorServer_CLI::cmdAddSwitch);\n  // this->registerNewCommand(\"addstepper\", \"as\", 1, \"add a new stepper configuration\", &ESPStepperMotorServer_CLI::cmdAddStepper);\n  // this->registerNewCommand(\"addencoder\", \"ae\", 1, \"add a new rotary encoder configuration\", &ESPStepperMotorServer_CLI::cmdAddEncoder);\n  // this->registerNewCommand(\"listencoders\", \"le\", 0, \"list all configured rotary encoders\", &ESPStepperMotorServer_CLI::cmdListEncoders);\n  // this->registerNewCommand(\"liststeppers\", \"ls\", 0, \"list all configured steppers\", &ESPStepperMotorServer_CLI::cmdListSteppers);\n  // this->registerNewCommand(\"listswitches\", \"lsw\", 0, \"list all configured input switches\", &ESPStepperMotorServer_CLI::cmdListSwitches);\n  // this->registerNewCommand(\"returnhome\", \"rh\", 1, \"Return to the home position (only possible if a homing switch is connected). Requires the ID of the stepper to get the position for as parameter. E.g.: rh=0\", &ESPStepperMotorServer_CLI::cmdReturnHome);\n  // create set wifi mode function\n}\n\nvoid ESPStepperMotorServer_CLI::registerNewCommand(commandDetailsStructure commandDetails, void (ESPStepperMotorServer_CLI::*cmdFunction)(char *, char *))\n{\n  if (this->commandCounter < MAX_CLI_CMD_COUNTER)\n  {\n    //check if command is already registered\n    for (int i = 0; i < this->commandCounter; i++)\n    {\n      if (this->allRegisteredCommands[i].command.equals(commandDetails.command) || this->allRegisteredCommands[i].shortCut.equals(commandDetails.shortCut))\n      {\n        ESPStepperMotorServer_Logger::logWarningf(\"A command with the same name / shortcut is already registered. Will not add the command '%s' [%s] to the list of registered commands\", commandDetails.command, commandDetails.shortCut);\n        return;\n      }\n    }\n    this->allRegisteredCommands[this->commandCounter] = commandDetails;\n    this->command_functions[this->commandCounter] = cmdFunction;\n    this->commandCounter++;\n  }\n  else\n  {\n    ESPStepperMotorServer_Logger::logWarningf(\"The maximum number of CLI commands has been exceeded. You need to increase the MAX_CLI_CMD_COUNTER value to add more than %i commands\\n\", MAX_CLI_CMD_COUNTER);\n  }\n}\n\nvoid ESPStepperMotorServer_CLI::registerNewUserCommand(commandDetailsStructure commandDetails, void (*cmdFunction)(char *, char *))\n{\n  if (this->userCommandCounter < MAX_CLI_USER_CMD_COUNTER)\n  {\n    //check if command is already registered as a built in command\n    for (int i = 0; i < this->commandCounter; i++)\n    {\n      if (this->allRegisteredCommands[i].command.equals(commandDetails.command) || this->allRegisteredCommands[i].shortCut.equals(commandDetails.shortCut))\n      {\n        ESPStepperMotorServer_Logger::logWarningf(\"A command with the same name / shortcut is already registered. Will not add the command '%s' [%s] to the list of registered user commands\", commandDetails.command, commandDetails.shortCut);\n        return;\n      }\n    }\n    // next check if it's already registered as a user command\n    for (int i = 0; i < this->userCommandCounter; i++)\n    {\n      if (this->allRegisteredUserCommands[i].command.equals(commandDetails.command) || this->allRegisteredUserCommands[i].shortCut.equals(commandDetails.shortCut))\n      {\n        ESPStepperMotorServer_Logger::logWarningf(\"A user command with the same name / shortcut is already registered. Will not add the command '%s' [%s] to the list of registered user commands\", commandDetails.command, commandDetails.shortCut);\n        return;\n      }\n    }\n    this->allRegisteredUserCommands[this->userCommandCounter] = commandDetails;\n    this->user_command_functions[this->userCommandCounter] = cmdFunction;\n    this->userCommandCounter++;\n  }\n  else\n  {\n    ESPStepperMotorServer_Logger::logWarningf(\"The maximum number of CLI user commands has been exceeded. You need to increase the MAX_CLI_USER_CMD_COUNTER value to add more than %i commands\\n\", MAX_CLI_USER_CMD_COUNTER);\n  }\n}\n\nvoid ESPStepperMotorServer_CLI::cmdPrintConfig(char *cmd, char *args)\n{\n  this->serverRef->getCurrentServerConfiguration()->printCurrentConfigurationAsJsonToSerial();\n}\n\nconst char *setterConfirmationTemplate = \"%s set to %s (please save and reboot for the changes to take effect)\\n\";\nconst char *setterMissingParameterTemplate = \"No or invalid value given as parameter. Usage is %s=<value>\\n\";\n\nvoid ESPStepperMotorServer_CLI::cmdSetApName(char *cmd, char *args)\n{\n  if (args != NULL)\n  {\n    this->serverRef->setAccessPointName(args);\n    Serial.printf(setterConfirmationTemplate, \"AP name\", args);\n  }\n  else\n  {\n    Serial.printf(setterMissingParameterTemplate, cmd);\n  }\n}\n\nvoid ESPStepperMotorServer_CLI::cmdSetApPassword(char *cmd, char *args)\n{\n  if (args != NULL)\n  {\n    this->serverRef->setAccessPointPassword(args);\n    Serial.printf(setterConfirmationTemplate, \"AP password\", args);\n  }\n  else\n  {\n    Serial.printf(setterMissingParameterTemplate, cmd);\n  }\n}\n\n#ifndef ESPStepperMotorServer_COMPILE_NO_WEB\nvoid ESPStepperMotorServer_CLI::cmdSetHttpPort(char *cmd, char *args)\n{\n  int port = (int)(String(args)).toInt();\n  if (port >= 80)\n  {\n    this->serverRef->setHttpPort(port);\n    Serial.printf(setterConfirmationTemplate, \"HTTP port\", args);\n  }\n  else\n  {\n    Serial.printf(setterMissingParameterTemplate, cmd);\n  }\n}\n#endif\n\nvoid ESPStepperMotorServer_CLI::cmdSetSSID(char *cmd, char *args)\n{\n  if (args != NULL)\n  {\n    this->serverRef->setWifiSSID(args);\n    Serial.printf(setterConfirmationTemplate, \"WiFi SSID\", args);\n  }\n  else\n  {\n    Serial.printf(setterMissingParameterTemplate, cmd);\n  }\n}\n\nvoid ESPStepperMotorServer_CLI::cmdSetWifiPassword(char *cmd, char *args)\n{\n  if (args != NULL)\n  {\n    this->serverRef->setWifiPassword(args);\n    Serial.printf(setterConfirmationTemplate, \"WiFi password\", args);\n  }\n  else\n  {\n    Serial.printf(setterMissingParameterTemplate, cmd);\n  }\n}\n\nvoid ESPStepperMotorServer_CLI::cmdHelp(char *cmd, char *args)\n{\n#ifdef ESPStepperMotorServer_COMPILE_NO_CLI_HELP\n  Serial.println(\"<command> [<shortcut>]\");\n#else\n  Serial.println(\"\\n-------- ESP-StepperMotor-Server-CLI Help -----------\\nThe following commands are available:\\n\");\n  Serial.println(\"<command> [<shortcut>]: <description>\");\n#endif\n  Serial.println(\"\\nBuilt in commands:\");\n  for (int i = 0; i < this->commandCounter; i++)\n  {\n    const char *hint = this->allRegisteredCommands[i].hasParameters ? \"*\" : \"\";\n    const char *tabString = (this->allRegisteredCommands[i].command.length() + this->allRegisteredCommands[i].shortCut.length() < 12) ? \"\\t\" : \"\";\n    Serial.printf(\"%s [%s]%s:\\t%s%s\\n\", this->allRegisteredCommands[i].command.c_str(), this->allRegisteredCommands[i].shortCut.c_str(), hint, tabString, this->allRegisteredCommands[i].description.c_str());\n  }\n  if (this->userCommandCounter > 0)\n  {\n    Serial.println(\"\\nUser commands:\");\n    for (int i = 0; i < this->userCommandCounter; i++)\n    {\n      const char *hint = this->allRegisteredUserCommands[i].hasParameters ? \"*\" : \"\";\n      const char *tabString = (this->allRegisteredUserCommands[i].command.length() + this->allRegisteredUserCommands[i].shortCut.length() < 12) ? \"\\t\" : \"\";\n      Serial.printf(\"%s [%s]%s:\\t%s%s\\n\", this->allRegisteredUserCommands[i].command.c_str(), this->allRegisteredUserCommands[i].shortCut.c_str(), hint, tabString, this->allRegisteredUserCommands[i].description.c_str());\n    }\n  }\n#ifndef ESPStepperMotorServer_COMPILE_NO_CLI_HELP\n  Serial.println(\"\\ncommmands marked with a * require input parameters.\\nParameters are provided with the command separarted by a = for the primary parameter.\\nSecondary parameters are provided in the format '&<parametername>:<parametervalue>'\\n\");\n  Serial.println(\"-------------------------------------------------------\");\n#endif\n}\n\nvoid ESPStepperMotorServer_CLI::cmdReboot(char *cmd, char *args)\n{\n  Serial.println(\"initiating restart\");\n  ESP.restart();\n}\n\nvoid ESPStepperMotorServer_CLI::cmdSwitchStatus(char *cmd, char *args)\n{\n  this->serverRef->printPositionSwitchStatus();\n}\n\nvoid ESPStepperMotorServer_CLI::cmdServerStatus(char *cmd, char *args)\n{\n  String result;\n  this->serverRef->getServerStatusAsJsonString(result);\n  Serial.println(result);\n}\n\nvoid ESPStepperMotorServer_CLI::cmdStopServer(char *cmd, char *args)\n{\n  this->serverRef->stop();\n  Serial.println(cmd);\n}\n\nvoid ESPStepperMotorServer_CLI::cmdEmergencyStop(char *cmd, char *args)\n{\n  this->serverRef->performEmergencyStop();\n  Serial.println(cmd);\n}\n\nvoid ESPStepperMotorServer_CLI::cmdRevokeEmergencyStop(char *cmd, char *args)\n{\n  this->serverRef->revokeEmergencyStop();\n  Serial.println(cmd);\n}\n\nvoid ESPStepperMotorServer_CLI::cmdRemoveSwitch(char *cmd, char *args)\n{\n  unsigned int id = (String(args)).toInt();\n  if (id > ESPServerMaxSwitches || !this->serverRef->getCurrentServerConfiguration()->getSwitch((byte)id))\n  {\n    Serial.println(\"error: invalid switch id given\");\n  }\n  else\n  {\n    this->serverRef->removePositionSwitch((byte)id);\n    Serial.println(cmd);\n  }\n}\n\nvoid ESPStepperMotorServer_CLI::cmdRemoveStepper(char *cmd, char *args)\n{\n  int stepperid = this->getValidStepperIdFromArg(args);\n  if (stepperid > -1)\n  {\n    this->serverRef->removeStepper((byte)stepperid);\n    Serial.println(cmd);\n  }\n}\n\nvoid ESPStepperMotorServer_CLI::cmdRemoveEncoder(char *cmd, char *args)\n{\n  unsigned int id = (String(args)).toInt();\n  if (id > ESPServerMaxRotaryEncoders || !this->serverRef->getCurrentServerConfiguration()->getRotaryEncoder((byte)id))\n  {\n    Serial.println(\"error: invalid encoder id given\");\n  }\n  else\n  {\n    this->serverRef->removeRotaryEncoder((byte)id);\n    Serial.println(cmd);\n  }\n}\n\nvoid ESPStepperMotorServer_CLI::cmdGetCurrentVelocity(char *cmd, char *args)\n{\n  ESPStepperMotorServer_Configuration *config = this->serverRef->getCurrentServerConfiguration();\n  int stepperid = this->getValidStepperIdFromArg(args);\n  char unit[10];\n  this->getUnitWithFallback(args, unit);\n\n  if (stepperid > -1)\n  {\n    if (strcmp(unit, \"mm\") == 0)\n      Serial.printf(\"%f mm/s\\n\", config->getStepperConfiguration((byte)stepperid)->getFlexyStepper()->getCurrentVelocityInMillimetersPerSecond());\n    else if (strcmp(unit, \"revs\") == 0)\n      Serial.printf(\"%f revs/s\\n\", config->getStepperConfiguration((byte)stepperid)->getFlexyStepper()->getCurrentVelocityInRevolutionsPerSecond());\n    else\n      Serial.printf(\"%f steps/s\\n\", config->getStepperConfiguration((byte)stepperid)->getFlexyStepper()->getCurrentVelocityInStepsPerSecond());\n  }\n  else\n  {\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n    ESPStepperMotorServer_Logger::logDebugf(\"%s called without parameter for stepper index\\n\", cmd);\n#endif\n    for (stepperid = 0; stepperid < ESPServerMaxSteppers; stepperid++)\n    {\n      ESPStepperMotorServer_StepperConfiguration *stepper = this->serverRef->getCurrentServerConfiguration()->getStepperConfiguration(stepperid);\n      if (stepper)\n      {\n        if (strcmp(unit, \"mm\") == 0)\n          Serial.printf(\"%i:%f mm/s\\n\", stepperid, stepper->getFlexyStepper()->getCurrentVelocityInMillimetersPerSecond());\n        else if (strcmp(unit, \"revs\") == 0)\n          Serial.printf(\"%i:%f revs/s\\n\", stepperid, stepper->getFlexyStepper()->getCurrentVelocityInRevolutionsPerSecond());\n        else\n          Serial.printf(\"%i:%f steps/s\\n\", stepperid, stepper->getFlexyStepper()->getCurrentVelocityInStepsPerSecond());\n      }\n    }\n  }\n}\n\nvoid ESPStepperMotorServer_CLI::cmdGetPosition(char *cmd, char *args)\n{\n  ESPStepperMotorServer_Configuration *config = this->serverRef->getCurrentServerConfiguration();\n  int stepperid = this->getValidStepperIdFromArg(args);\n  char unit[10];\n  this->getUnitWithFallback(args, unit);\n\n  if (stepperid > -1)\n  {\n    if (strcmp(unit, \"mm\") == 0)\n      Serial.printf(\"%f mm\\n\", config->getStepperConfiguration((byte)stepperid)->getFlexyStepper()->getCurrentPositionInMillimeters());\n    else if (strcmp(unit, \"revs\") == 0)\n      Serial.printf(\"%f revs\\n\", config->getStepperConfiguration((byte)stepperid)->getFlexyStepper()->getCurrentPositionInRevolutions());\n    else\n      Serial.printf(\"%ld steps\\n\", config->getStepperConfiguration((byte)stepperid)->getFlexyStepper()->getCurrentPositionInSteps());\n  }\n  else\n  {\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n    ESPStepperMotorServer_Logger::logDebugf(\"%s called without parameter for stepper index\\n\", cmd);\n#endif\n    for (stepperid = 0; stepperid < ESPServerMaxSteppers; stepperid++)\n    {\n      ESPStepperMotorServer_StepperConfiguration *stepper = config->getStepperConfiguration(stepperid);\n      if (stepper)\n      {\n        if (strcmp(unit, \"mm\") == 0)\n          Serial.printf(\"%i:%f mm\\n\", stepperid, stepper->getFlexyStepper()->getCurrentPositionInMillimeters());\n        else if (strcmp(unit, \"revs\") == 0)\n          Serial.printf(\"%i:%f revs\\n\", stepperid, stepper->getFlexyStepper()->getCurrentPositionInRevolutions());\n        else\n          Serial.printf(\"%i:%ld steps\\n\", stepperid, stepper->getFlexyStepper()->getCurrentPositionInSteps());\n      }\n    }\n  }\n}\n\nvoid ESPStepperMotorServer_CLI::setMoveSpeedAccelHelper(ESP_FlexyStepper *flexyStepper, char *args)\n{\n  char buffer[20];\n\n  this->getParameterValue(args, \"s\", buffer);\n  if (buffer[0] != NULLCHAR)\n  {\n    float speed = (String(buffer).toFloat());\n    if (speed > 0)\n    {\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n      ESPStepperMotorServer_Logger::logDebugf(\"Setting speed to %f steps / second\\n\", speed);\n#endif\n      flexyStepper->setSpeedInStepsPerSecond(speed);\n    }\n  }\n\n  this->getParameterValue(args, \"a\", buffer);\n  if (buffer[0] != NULLCHAR)\n  {\n    float accel = (String(buffer).toFloat());\n    if (accel > 0)\n    {\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n      ESPStepperMotorServer_Logger::logDebugf(\"Setting acceleration to %f steps / second^2\\n\", accel);\n#endif\n      flexyStepper->setAccelerationInStepsPerSecondPerSecond(accel);\n      //in case deceleration is not explicitly given, we just use the same value\n      flexyStepper->setDecelerationInStepsPerSecondPerSecond(accel);\n    }\n  }\n\n  this->getParameterValue(args, \"d\", buffer);\n  if (buffer[0] != NULLCHAR)\n  {\n    float decel = (String(buffer).toFloat());\n    if (decel > 0)\n    {\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n      ESPStepperMotorServer_Logger::logDebugf(\"Setting deceleration to %f steps / second^2\\n\", decel);\n#endif\n      flexyStepper->setDecelerationInStepsPerSecondPerSecond(decel);\n    }\n  }\n}\n\nvoid ESPStepperMotorServer_CLI::cmdMoveTo(char *cmd, char *args)\n{\n  int stepperid = this->getValidStepperIdFromArg(args);\n  if (stepperid > -1)\n  {\n    ESP_FlexyStepper *flexyStepper = this->serverRef->getCurrentServerConfiguration()->getStepperConfiguration(stepperid)->getFlexyStepper();\n\n    this->setMoveSpeedAccelHelper(flexyStepper, args);\n\n    char value[20];\n    this->getParameterValue(args, \"v\", value);\n    if (value[0] != NULLCHAR)\n    {\n      char unit[10];\n      this->getParameterValue(args, \"u\", unit);\n      if (unit[0] == NULLCHAR || strcmp(unit, \"steps\") == 0)\n      {\n        if (unit[0] == NULLCHAR)\n        {\n          Serial.println(\"no unit provided, will use 'steps' as default\");\n        }\n        flexyStepper->setTargetPositionInSteps((String(value).toInt()));\n      }\n      else if (strcmp(unit, \"revs\") == 0)\n      {\n        flexyStepper->setTargetPositionInRevolutions((String(value).toFloat()));\n      }\n      else if (strcmp(unit, \"mm\") == 0)\n      {\n        flexyStepper->setTargetPositionInMillimeters((String(value).toFloat()));\n      }\n      else\n      {\n        Serial.println(\"error: provided unit not supported. Must be one of mm, steps or revs\");\n        return;\n      }\n      Serial.println(cmd);\n    }\n    else\n    {\n      Serial.println(\"error: missing required v parameter\");\n    }\n  }\n}\n\nvoid ESPStepperMotorServer_CLI::cmdMoveBy(char *cmd, char *args)\n{\n  int stepperid = this->getValidStepperIdFromArg(args);\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n  ESPStepperMotorServer_Logger::logDebugf(\"%s called for stepper id %i\\n\", cmd, stepperid);\n#endif\n  if (stepperid > -1)\n  {\n    ESP_FlexyStepper *flexyStepper = this->serverRef->getCurrentServerConfiguration()->getStepperConfiguration(stepperid)->getFlexyStepper();\n\n    this->setMoveSpeedAccelHelper(flexyStepper, args);\n\n    char value[20];\n    this->getParameterValue(args, \"v\", value);\n    if (value[0] != NULLCHAR)\n    {\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n      ESPStepperMotorServer_Logger::logDebugf(\"cmdMoveBy called with v = %s\\n\", value);\n#endif\n      char unit[10];\n      this->getParameterValue(args, \"u\", unit);\n      if (unit[0] == NULLCHAR || strcmp(unit, \"steps\") == 0)\n      {\n        if (unit[0] == NULLCHAR)\n        {\n          Serial.println(\"no unit provided, will use 'steps' as default\");\n        }\n        int targetPosition = (String(value).toInt());\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n        ESPStepperMotorServer_Logger::logDebugf(\"Setting target position to %i steps\\n\", targetPosition);\n#endif\n        flexyStepper->setTargetPositionRelativeInSteps(targetPosition);\n      }\n      else if (strcmp(unit, \"revs\") == 0)\n      {\n        float targetPosition = (String(value).toFloat());\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n        ESPStepperMotorServer_Logger::logDebugf(\"Setting target position to %f revs\\n\", targetPosition);\n#endif\n        flexyStepper->setTargetPositionRelativeInRevolutions(targetPosition);\n      }\n      else if (strcmp(unit, \"mm\") == 0)\n      {\n        float targetPosition = (String(value).toFloat());\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n        ESPStepperMotorServer_Logger::logDebugf(\"Setting target position to %f mm\\n\", targetPosition);\n#endif\n        flexyStepper->setTargetPositionRelativeInMillimeters(targetPosition);\n      }\n      else\n      {\n        Serial.println(\"error: provided unit not supported. Must be one of mm, steps or revs\");\n        return;\n      }\n      Serial.println(cmd);\n    }\n    else\n    {\n      Serial.println(\"error: missing required v parameter\");\n    }\n  }\n}\n\nvoid ESPStepperMotorServer_CLI::cmdSaveConfiguration(char *cmd, char *args)\n{\n  if (this->serverRef->getCurrentServerConfiguration()->saveCurrentConfiguationToSpiffs())\n  {\n    Serial.println(cmd);\n  }\n  else\n  {\n    Serial.println(\"error: saving configuration to SPIFFS failed\");\n  }\n}\n\nvoid ESPStepperMotorServer_CLI::cmdSetLogLevel(char *cmd, char *args)\n{\n  unsigned int logLevelToSet = (String(args)).toInt();\n  if (logLevelToSet == 0)\n  {\n    Serial.printf(\"%s=%i\\n\", cmd, ESPStepperMotorServer_Logger::getLogLevel());\n  }\n  else\n  {\n    if (logLevelToSet > ESPServerLogLevel_ALL || logLevelToSet < ESPServerLogLevel_WARNING)\n    {\n      Serial.printf(\"error: Invalid log level given. Must be in the range of %i (Warning) and %i (All)\\n\", ESPServerLogLevel_WARNING, ESPServerLogLevel_ALL);\n    }\n    ESPStepperMotorServer_Logger::setLogLevel(logLevelToSet);\n  }\n}\n\n/**\n * convert given char* to int value and check if it represents a valid stepper config id (within the allowed limits and with an existing stepper configuation existing)\n * -1 is returned if not valid and an error os printed to the serial interface\n */\nint ESPStepperMotorServer_CLI::getValidStepperIdFromArg(char *arg)\n{\n  if (arg && isdigit(arg[0]))\n  {\n    int id = (String(arg)).toInt();\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n    ESPStepperMotorServer_Logger::logDebugf(\"extracted stepper id %i from argument string %s\\n\", id, arg);\n#endif\n    if (id > ESPServerMaxSteppers || !this->serverRef->getCurrentServerConfiguration()->getStepperConfiguration((byte)id))\n    {\n      Serial.println(\"error: invalid stepper id given\");\n      return -1;\n    }\n    return (byte)id;\n  }\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n  else\n  {\n    ESPStepperMotorServer_Logger::logDebug(\"no argument string given to extract stepper id from, will return -1\");\n  }\n#endif\n  return -1;\n}\n\n/**\n * helper function to extract parameters and values from the given argument string\n */\nvoid ESPStepperMotorServer_CLI::getParameterValue(const char *args, const char *parameterNameToGetValueFor, char *result)\n{\n  if (args == NULL)\n  {\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n    ESPStepperMotorServer_Logger::logDebugf(\"getParameterValue called with on NULL and %s\\n\", parameterNameToGetValueFor);\n#endif\n    strcpy(result, \"\");\n    return;\n  }\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n  ESPStepperMotorServer_Logger::logDebugf(\"getParameterValue called with %s and %s\\n\", args, parameterNameToGetValueFor);\n#endif\n  char *parameterValue;\n  char *save_ptr;\n  String argumentString = String(args);\n  char workingCopy[argumentString.length() + 1];\n  strcpy(workingCopy, args);\n\n  char *keyValuePairString = strtok_r(workingCopy, this->_PARAM_PARAM_SEPRATOR, &save_ptr);\n  while (keyValuePairString != NULL)\n  {\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n    ESPStepperMotorServer_Logger::logDebugf(\"Found a key value pair: %s\\n\", keyValuePairString);\n#endif\n    char *restKeyValuePair = keyValuePairString;\n    char *parameterName = strtok_r(restKeyValuePair, this->_PARAM_VALUE_SEPRATOR, &restKeyValuePair);\n    if (parameterName != NULL)\n    {\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n      ESPStepperMotorServer_Logger::logDebugf(\"Found parameter '%s'\\n\", parameterName);\n#endif\n      if (strcmp(parameterName, parameterNameToGetValueFor) == 0)\n      {\n        if (restKeyValuePair && strcmp(restKeyValuePair, \"\") != 0)\n        {\n          parameterValue = strtok_r(restKeyValuePair, this->_PARAM_VALUE_SEPRATOR, &restKeyValuePair);\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n          ESPStepperMotorServer_Logger::logDebugf(\"Found matching parameter: %s with value %s\\n\", parameterName, parameterValue);\n#endif\n          strcpy(result, parameterValue);\n          return;\n        }\n      }\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n      else\n      {\n        ESPStepperMotorServer_Logger::logDebug(\"parameter did not match requested parameter\");\n      }\n#endif\n    }\n    keyValuePairString = strtok_r(NULL, this->_PARAM_PARAM_SEPRATOR, &save_ptr);\n  }\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n  ESPStepperMotorServer_Logger::logDebug(\"No match found\");\n#endif\n  strcpy(result, \"\");\n}\n\n////// internal helpers to prevent code duplication\nvoid ESPStepperMotorServer_CLI::getUnitWithFallback(char *args, char *unit)\n{\n  this->getParameterValue(args, \"u\", unit);\n  if (unit[0] == NULLCHAR)\n  {\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n    ESPStepperMotorServer_Logger::logDebug(\"no unit provided, will use 'steps' as default\");\n#endif\n    strcpy(unit, \"steps\");\n  }\n}\n\n// -------------------------------------- End --------------------------------------\n"
  },
  {
    "path": "src/ESPStepperMotorServer_CLI.h",
    "content": "//      ******************************************************************\n//      *                                                                *\n//      *       Header file for ESPStepperMotorServer_CLI.cpp            *\n//      *                                                                *\n//      *               Copyright (c) Paul Kerspe, 2019                  *\n//      *                                                                *\n//      ******************************************************************\n\n// MIT License\n//\n// Copyright (c) 2019 Paul Kerspe\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 furnished\n// to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// 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 THE\n// SOFTWARE.\n\n#ifndef ESPStepperMotorServer_CLI_h\n#define ESPStepperMotorServer_CLI_h\n\n#define MAX_CLI_CMD_COUNTER 50\n#define MAX_CLI_USER_CMD_COUNTER 5\n\n#include <ESPStepperMotorServer.h>\n#include <ESPStepperMotorServer_Logger.h>\n\n//need this forward declaration here due to circular dependency (use in constructor/member variable)\nclass ESPStepperMotorServer;\n\nstruct commandDetailsStructure\n{\n  String command;\n  String shortCut;\n  String description;\n  bool hasParameters;\n};\n\nclass ESPStepperMotorServer_CLI\n{\npublic:\n  ESPStepperMotorServer_CLI(ESPStepperMotorServer *serverRef);\n  ~ESPStepperMotorServer_CLI();\n  static void processSerialInput(void *parameter);\n  void executeCommand(String cmd);\n  void start();\n  void stop();\n  void registerNewUserCommand(commandDetailsStructure commandDetails, void (*f)(char *, char *));\n  int getValidStepperIdFromArg(char *arg);\n  void getParameterValue(const char *args, const char *parameterNameToGetValueFor, char *result);\n  void getUnitWithFallback(char *args, char *unit);\n\nprivate:\n  void cmdHelp(char *cmd, char *args);\n  void cmdEmergencyStop(char *cmd, char *args);\n  void cmdRevokeEmergencyStop(char *cmd, char *args);\n  void cmdGetPosition(char *cmd, char *args);\n  void cmdGetCurrentVelocity(char *cmd, char *args);\n  void cmdMoveBy(char *cmd, char *args);\n  void cmdMoveTo(char *cmd, char *args);\n  void cmdPrintConfig(char *cmd, char *args);\n  void cmdRemoveSwitch(char *cmd, char *args);\n  void cmdReboot(char *cmd, char *args);\n  void cmdRemoveStepper(char *cmd, char *args);\n  void cmdRemoveEncoder(char *cmd, char *args);\n  void cmdStopServer(char *cmd, char *args);\n  void cmdSwitchStatus(char *cmd, char *args);\n  void cmdServerStatus(char *cmd, char *args);\n  void cmdSetLogLevel(char *cmd, char *args);\n  void cmdSaveConfiguration(char *cmd, char *args);\n  void cmdSetApName(char *cmd, char *args);\n  void cmdSetApPassword(char *cmd, char *args);\n#ifndef ESPStepperMotorServer_COMPILE_NO_WEB\n  void cmdSetHttpPort(char *cmd, char *args);\n#endif\n  void cmdSetSSID(char *cmd, char *args);\n  void cmdSetWifiPassword(char *cmd, char *args);\n  void registerCommands();\n  void registerNewCommand(commandDetailsStructure commandDetails, void (ESPStepperMotorServer_CLI::*f)(char *, char *));\n  void setMoveSpeedAccelHelper(ESP_FlexyStepper *flexyStepper, char *args);\n\n  TaskHandle_t xHandle = NULL;\n  ESPStepperMotorServer *serverRef;\n  void (ESPStepperMotorServer_CLI::*command_functions[MAX_CLI_CMD_COUNTER + 1])(char *, char *);\n  void (*user_command_functions[MAX_CLI_USER_CMD_COUNTER + 1])(char *, char *);\n  //const char *command_details[MAX_CLI_CMD_COUNTER +1 ][4];\n  commandDetailsStructure allRegisteredCommands[MAX_CLI_CMD_COUNTER + 1];\n  commandDetailsStructure allRegisteredUserCommands[MAX_CLI_USER_CMD_COUNTER + 1];\n  unsigned int commandCounter = 0;\n  unsigned int userCommandCounter = 0;\n\n  const char *_CMD_PARAM_SEPARATOR = \"=\";\n  const char *_PARAM_PARAM_SEPRATOR = \"&\";\n  const char *_PARAM_VALUE_SEPRATOR = \":\";\n};\n\n#endif\n"
  },
  {
    "path": "src/ESPStepperMotorServer_Configuration.cpp",
    "content": "\n//      *********************************************************\n//      *                                                       *\n//      *           Stepper Motor Server Configuration           *\n//      *                                                       *\n//      *            Copyright (c) Paul Kerspe, 2019            *\n//      *                                                       *\n//      **********************************************************\n// this class repesents the complete configuration object and provide\n// helper functions to persist and load the configuration form the SPIFFS of the ESP\n\n// MIT License\n//\n// Copyright (c) 2019 Paul Kerspe\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 furnished\n// to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// 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 THE\n// SOFTWARE.\n\n#include \"ESPStepperMotorServer_Configuration.h\"\n#include <LittleFS.h>\n\n#define RESERVED_JSON_SIZE_ESPStepperMotorServer_Configuration 300\n\n//\n// constructor for the stepper server configuration class\n//\nESPStepperMotorServer_Configuration::ESPStepperMotorServer_Configuration(const char *configFilePath, bool isSPIFFSactive)\n{\n    this->_configFilePath = configFilePath;\n    this->_isSPIFFSactive = isSPIFFSactive;\n    this->loadConfiguationFromSpiffs();\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n    if (ESPStepperMotorServer_Logger::getLogLevel() >= ESPServerLogLevel_DEBUG)\n    {\n        this->printCurrentConfigurationAsJsonToSerial();\n    }\n#endif\n}\n\nunsigned int ESPStepperMotorServer_Configuration::calculateRequiredJsonDocumentSizeForCurrentConfiguration()\n{\n    unsigned int size = 0;\n    size += RESERVED_JSON_SIZE_ESPStepperMotorServer_PositionSwitch * ESPServerMaxSwitches;\n    size += RESERVED_JSON_SIZE_ESPStepperMotorServer_RotaryEncoder * ESPServerMaxRotaryEncoders;\n    size += RESERVED_JSON_SIZE_ESPStepperMotorServer_StepperConfiguration * ESPServerMaxSteppers;\n    size += RESERVED_JSON_SIZE_ESPStepperMotorServer_Configuration;\n    return size;\n}\n\nvoid ESPStepperMotorServer_Configuration::printCurrentConfigurationAsJsonToSerial()\n{\n    DynamicJsonDocument doc(this->calculateRequiredJsonDocumentSizeForCurrentConfiguration());\n    this->serializeServerConfiguration(doc, false);\n    serializeJsonPretty(doc, Serial);\n    Serial.println();\n}\n\nString ESPStepperMotorServer_Configuration::getCurrentConfigurationAsJSONString(bool prettyPrint, bool includePasswords)\n{\n    DynamicJsonDocument doc(this->calculateRequiredJsonDocumentSizeForCurrentConfiguration());\n    this->serializeServerConfiguration(doc, includePasswords);\n    String output;\n    if (prettyPrint)\n    {\n        serializeJsonPretty(doc, output);\n    }\n    else\n    {\n        serializeJson(doc, output);\n    }\n    return output;\n}\n\nvoid ESPStepperMotorServer_Configuration::serializeServerConfiguration(JsonDocument &doc, bool includePasswords)\n{\n    // Set the values in the document\n    doc[JSON_SECTION_NAME_SERVER_CONFIGURATION][JSON_PROPERTY_NAME_PORT_NUMBER] = this->serverPort;\n    doc[JSON_SECTION_NAME_SERVER_CONFIGURATION][JSON_PROPERTY_NAME_WIFI_MODE] = this->wifiMode;\n    doc[JSON_SECTION_NAME_SERVER_CONFIGURATION][JSON_PROPERTY_NAME_WIFI_SSID] = this->wifiSsid;\n    doc[JSON_SECTION_NAME_SERVER_CONFIGURATION][JSON_PROPERTY_NAME_WIFI_PASSWORD] = (includePasswords) ? this->wifiPassword : \"*****\";\n    doc[JSON_SECTION_NAME_SERVER_CONFIGURATION][JSON_PROPERTY_NAME_WIFI_AP_NAME] = this->apName;\n    doc[JSON_SECTION_NAME_SERVER_CONFIGURATION][JSON_PROPERTY_NAME_WIFI_AP_PASSWORD] = (includePasswords) ? this->apPassword : \"*****\";\n    doc[JSON_SECTION_NAME_SERVER_CONFIGURATION][JSON_PROPERTY_NAME_CPUCORE_FOR_MOTIONCONTROLLER_SERVICE] = this->motionControllerCpuCore;\n\n    ESPStepperMotorServer_Logger::logInfof(\"Serializing config \\n\");\n\n    if (this->staticIP != 0)\n    {\n        ESPStepperMotorServer_Logger::logInfof(\"static ip = %s \\n\", this->staticIP.toString().c_str());\n        doc[JSON_SECTION_NAME_SERVER_CONFIGURATION][JSON_PROPERTY_NAME_WIFI_STATIC_IP_ADDRESS] = this->staticIP.toString();\n    }\n\n    if (this->gatewayIP != 0)\n    {\n        ESPStepperMotorServer_Logger::logInfof(\"gateway ip = %s \\n\", this->gatewayIP.toString().c_str());\n        doc[JSON_SECTION_NAME_SERVER_CONFIGURATION][JSON_PROPERTY_NAME_WIFI_STATIC_IP_GATEWAY] = this->gatewayIP.toString();\n    }\n    if (this->subnetMask != 0)\n    {\n        ESPStepperMotorServer_Logger::logInfof(\"subnetMask = %s \\n\", this->subnetMask.toString().c_str());\n        doc[JSON_SECTION_NAME_SERVER_CONFIGURATION][JSON_PROPERTY_NAME_WIFI_STATIC_IP_SUBNETMASK] = this->subnetMask.toString();\n    }\n    if (this->dns1IP != 0)\n    {\n        ESPStepperMotorServer_Logger::logInfof(\"DNS1 ip = %s \\n\", this->dns1IP.toString().c_str());\n        doc[JSON_SECTION_NAME_SERVER_CONFIGURATION][JSON_PROPERTY_NAME_WIFI_STATIC_IP_DNS1] = this->dns1IP.toString();\n    }\n    if (this->dns2IP != 0)\n    {\n        ESPStepperMotorServer_Logger::logInfof(\"DNS2 ip = %s \\n\", this->dns2IP.toString().c_str());\n        doc[JSON_SECTION_NAME_SERVER_CONFIGURATION][JSON_PROPERTY_NAME_WIFI_STATIC_IP_DNS2] = this->dns2IP.toString();\n    }\n\n    // add all stepper configs\n    JsonArray stepperConfigArray = doc.createNestedArray(JSON_SECTION_NAME_STEPPER_CONFIGURATIONS);\n    for (byte stepperConfigIndex = 0; stepperConfigIndex < ESPServerMaxSteppers; stepperConfigIndex++)\n    {\n        ESPStepperMotorServer_StepperConfiguration *stepperConfig = this->getStepperConfiguration(stepperConfigIndex);\n        if (stepperConfig)\n        {\n            JsonObject nestedStepperConfig = stepperConfigArray.createNestedObject();\n            nestedStepperConfig[\"id\"] = stepperConfig->getId();\n            nestedStepperConfig[\"name\"] = stepperConfig->getDisplayName();\n            nestedStepperConfig[\"stepPin\"] = stepperConfig->getStepIoPin();\n            nestedStepperConfig[\"directionPin\"] = stepperConfig->getDirectionIoPin();\n            nestedStepperConfig[\"stepsPerRev\"] = stepperConfig->getStepsPerRev();\n            nestedStepperConfig[\"stepsPerMM\"] = stepperConfig->getStepsPerMM();\n            nestedStepperConfig[\"microsteppingDivisor\"] = stepperConfig->getMicrostepsPerStep();\n            nestedStepperConfig[\"rpmLimit\"] = stepperConfig->getRpmLimit();\n            nestedStepperConfig[\"breakPin\"] = stepperConfig->getBrakeIoPin();\n            nestedStepperConfig[\"breakPinActiveState\"] = stepperConfig->getBrakePinActiveState();\n            nestedStepperConfig[\"breakEngageDelay\"] = stepperConfig->getBrakeEngageDelayMs();\n            nestedStepperConfig[\"breakReleaseDelay\"] = stepperConfig->getBrakeReleaseDelayMs();\n        }\n    }\n\n    // add all switch configs\n    JsonArray switchConfigArray = doc.createNestedArray(JSON_SECTION_NAME_SWITCH_CONFIGURATIONS);\n    for (byte switchConfigIndex = 0; switchConfigIndex < ESPServerMaxSwitches; switchConfigIndex++)\n    {\n        ESPStepperMotorServer_PositionSwitch *switchConfig = this->getSwitch(switchConfigIndex);\n        if (switchConfig)\n        {\n            JsonObject nestedSwitchConfig = switchConfigArray.createNestedObject();\n            nestedSwitchConfig[\"id\"] = switchConfig->getId();\n            nestedSwitchConfig[\"name\"] = switchConfig->getPositionName();\n            nestedSwitchConfig[\"ioPin\"] = switchConfig->getIoPinNumber();\n            nestedSwitchConfig[\"stepperIndex\"] = switchConfig->getStepperIndex();\n            nestedSwitchConfig[\"switchType\"] = switchConfig->getSwitchType();\n            nestedSwitchConfig[\"switchPosition\"] = switchConfig->getSwitchPosition();\n            if (switchConfig->hasMacroActions())\n            {\n                JsonArray macroActionsJsonArray = nestedSwitchConfig.createNestedArray(JSON_SECTION_NAME_SWITCH_CONFIGURATION_MACROACTIONS);\n                switchConfig->serializeMacroActionsToJsonArray(macroActionsJsonArray);\n            }\n        }\n    }\n\n    // add all rotary encoder configs\n    JsonArray encoderConfigArray = doc.createNestedArray(JSON_SECTION_NAME_ROTARY_ENCODER_CONFIGURATIONS);\n    for (byte encoderConfigIndex = 0; encoderConfigIndex < ESPServerMaxRotaryEncoders; encoderConfigIndex++)\n    {\n        ESPStepperMotorServer_RotaryEncoder *encoderConfig = this->getRotaryEncoder(encoderConfigIndex);\n        if (encoderConfig)\n        {\n            JsonObject nestedEncoderConfig = encoderConfigArray.createNestedObject();\n            nestedEncoderConfig[\"id\"] = encoderConfig->getId();\n            nestedEncoderConfig[\"name\"] = encoderConfig->getDisplayName();\n            nestedEncoderConfig[\"pinA\"] = encoderConfig->getPinAIOPin();\n            nestedEncoderConfig[\"pinB\"] = encoderConfig->getPinBIOPin();\n            nestedEncoderConfig[\"stepMultiplier\"] = encoderConfig->getStepMultiplier();\n            nestedEncoderConfig[\"stepperIndex\"] = encoderConfig->getStepperIndex();\n        }\n    }\n}\n\nbool ESPStepperMotorServer_Configuration::saveCurrentConfiguationToSpiffs(String filename)\n{\n    if (!this->_isSPIFFSactive)\n    {\n        ESPStepperMotorServer_Logger::logWarningf(\"Failed to save configuration file '%s' in SPIFFS, since SPIFFS is not mounted\\n\", filename.c_str());\n        return false;\n    }\n\n    if (filename == \"\")\n    {\n        filename = this->_configFilePath;\n    }\n    // assemble the json object first, to check if all goes well\n    // Allocate a temporary JsonDocument\n    DynamicJsonDocument doc(this->calculateRequiredJsonDocumentSizeForCurrentConfiguration());\n    this->serializeServerConfiguration(doc, true);\n\n    // Delete existing file, otherwise the configuration is appended to the file\n    LittleFS.remove(filename);\n    // Open file for writing\n    File file = LittleFS.open(filename, FILE_WRITE);\n    if (!file)\n    {\n        ESPStepperMotorServer_Logger::logWarningf(\"Failed to create new configuration file '%s' in LittleFS\\n\", filename.c_str());\n        return false;\n    }\n    bool success = false;\n    // Serialize JSON to file\n    if (serializeJson(doc, file) == 0)\n    {\n        ESPStepperMotorServer_Logger::logWarningf(\"Failed to write new configuration to file '%s' in SPIFFS\\n\", filename.c_str());\n    }\n    else\n    {\n        ESPStepperMotorServer_Logger::logInfof(\"New configuration file written in SPIFFS to '%s'\\n\", filename.c_str());\n        success = true;\n    }\n\n    // Close the file\n    file.close();\n \n    return success;\n}\n\nbool ESPStepperMotorServer_Configuration::loadConfiguationFromSpiffs(String filename)\n{\n    filename = (filename == \"\") ? this->_configFilePath : filename;\n    filename = (filename.startsWith(\"/\")) ? filename : \"/\" + filename;\n\n    if (this->_isSPIFFSactive && LittleFS.exists(filename))\n    {\n        ESPStepperMotorServer_Logger::logInfof(\"Loading configuration file %s from LittleFS\\n\", filename.c_str());\n        File configFile = LittleFS.open(filename, FILE_READ);\n        DynamicJsonDocument doc(this->calculateRequiredJsonDocumentSizeForCurrentConfiguration());\n        this->serializeServerConfiguration(doc);\n        // Deserialize the JSON document\n        DeserializationError error = deserializeJson(doc, configFile);\n        if (error)\n            ESPStepperMotorServer_Logger::logWarningf(\"Failed to read configuration file %s. Will use fallback default configuration\\n\", filename.c_str());\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n        else\n        {\n            ESPStepperMotorServer_Logger::logDebug(\"File loaded and deserialized\");\n        }\n#endif\n        // Copy values from the JsonDocument to the Config\n\n        // SERVER CONFIG\n        this->serverPort = doc[JSON_SECTION_NAME_SERVER_CONFIGURATION][JSON_PROPERTY_NAME_PORT_NUMBER] | DEFAULT_SERVER_PORT;\n        this->wifiMode = doc[JSON_SECTION_NAME_SERVER_CONFIGURATION][JSON_PROPERTY_NAME_WIFI_MODE] | DEFAULT_WIFI_MODE;\n\n        JsonVariant value = doc[JSON_SECTION_NAME_SERVER_CONFIGURATION][JSON_PROPERTY_NAME_WIFI_AP_NAME];\n        this->apName = (value) ? value.as<const char *>() : \"ESP-StepperMotor-Server\";\n\n        value = doc[JSON_SECTION_NAME_SERVER_CONFIGURATION][JSON_PROPERTY_NAME_WIFI_AP_PASSWORD];\n        this->apPassword = (value) ? value.as<const char *>() : \"Aa123456\";\n\n        value = doc[JSON_SECTION_NAME_SERVER_CONFIGURATION][JSON_PROPERTY_NAME_CPUCORE_FOR_MOTIONCONTROLLER_SERVICE];\n        this->motionControllerCpuCore = (value) ? value.as<int>() : 0;\n\n        this->wifiSsid = doc[JSON_SECTION_NAME_SERVER_CONFIGURATION][JSON_PROPERTY_NAME_WIFI_SSID].as<const char *>();\n        this->wifiPassword = doc[JSON_SECTION_NAME_SERVER_CONFIGURATION][JSON_PROPERTY_NAME_WIFI_PASSWORD].as<const char *>();\n\n        // read static IP settings if any\n        if (doc[JSON_SECTION_NAME_SERVER_CONFIGURATION].containsKey(JSON_PROPERTY_NAME_WIFI_STATIC_IP_ADDRESS))\n        {\n            this->staticIP.fromString(doc[JSON_SECTION_NAME_SERVER_CONFIGURATION][JSON_PROPERTY_NAME_WIFI_STATIC_IP_ADDRESS].as<const char *>());\n        }\n        if (doc[JSON_SECTION_NAME_SERVER_CONFIGURATION].containsKey(JSON_PROPERTY_NAME_WIFI_STATIC_IP_GATEWAY))\n        {\n            this->gatewayIP.fromString(doc[JSON_SECTION_NAME_SERVER_CONFIGURATION][JSON_PROPERTY_NAME_WIFI_STATIC_IP_GATEWAY].as<const char *>());\n        }\n        if (doc[JSON_SECTION_NAME_SERVER_CONFIGURATION].containsKey(JSON_PROPERTY_NAME_WIFI_STATIC_IP_SUBNETMASK))\n        {\n            this->subnetMask.fromString(doc[JSON_SECTION_NAME_SERVER_CONFIGURATION][JSON_PROPERTY_NAME_WIFI_STATIC_IP_SUBNETMASK].as<const char *>());\n        }\n        if (doc[JSON_SECTION_NAME_SERVER_CONFIGURATION].containsKey(JSON_PROPERTY_NAME_WIFI_STATIC_IP_DNS1))\n        {\n            this->dns1IP.fromString(doc[JSON_SECTION_NAME_SERVER_CONFIGURATION][JSON_PROPERTY_NAME_WIFI_STATIC_IP_DNS1].as<const char *>());\n        }\n        if (doc[JSON_SECTION_NAME_SERVER_CONFIGURATION].containsKey(JSON_PROPERTY_NAME_WIFI_STATIC_IP_DNS2))\n        {\n            this->dns2IP.fromString(doc[JSON_SECTION_NAME_SERVER_CONFIGURATION][JSON_PROPERTY_NAME_WIFI_STATIC_IP_DNS2].as<const char *>());\n        }\n\n        // STEPPER CONFIG\n        JsonArray configs = doc[JSON_SECTION_NAME_STEPPER_CONFIGURATIONS].as<JsonArray>();\n        byte configCounter = 0;\n        if (!configs)\n        {\n            ESPStepperMotorServer_Logger::logInfo(\"No stepper configuration present in config file\");\n        }\n        else\n        {\n            for (JsonVariant stepperConfigEntry : configs)\n            {\n                const char *value = stepperConfigEntry[\"name\"].as<const char *>();\n                ESPStepperMotorServer_StepperConfiguration *stepperConfig = new ESPStepperMotorServer_StepperConfiguration(\n                    (stepperConfigEntry[\"stepPin\"] | 255),\n                    (stepperConfigEntry[\"directionPin\"] | 255),\n                    ((value) ? value : \"undefined\"),\n                    (stepperConfigEntry[\"stepsPerRev\"] | 200),\n                    (stepperConfigEntry[\"stepsPerMM\"] | 100),\n                    (stepperConfigEntry[\"microsteppingDivisor\"] | ESPSMS_MICROSTEPS_OFF),\n                    (stepperConfigEntry[\"rpmLimit\"] | 1000));\n                \n                //set break settings\n                stepperConfig->setBrakeIoPin(stepperConfigEntry[\"breakPin\"] | stepperConfig->ESPServerStepperUnsetIoPinNumber, stepperConfigEntry[\"breakPinActiveState\"] | 1);\n                stepperConfig->setBrakeEngageDelayMs(stepperConfigEntry[\"breakEngageDelay\"] | 0);\n                stepperConfig->setBrakeReleaseDelayMs(stepperConfigEntry[\"breakReleaseDelay\"] | -1);\n\n                if (stepperConfigEntry[\"id\"])\n                {\n                    this->setStepperConfiguration(stepperConfig, stepperConfigEntry[\"id\"]);\n                }\n                else\n                {\n                    this->addStepperConfiguration(stepperConfig);\n                }\n                configCounter++;\n            }\n            ESPStepperMotorServer_Logger::logInfof(\"%i stepper configuration entr%s loaded from config file\\n\", configCounter, (configCounter == 1) ? \"y\" : \"ies\");\n        }\n\n        // SWITCH CONFIG\n        configCounter = 0;\n        configs = doc[JSON_SECTION_NAME_SWITCH_CONFIGURATIONS].as<JsonArray>();\n        if (!configs)\n        {\n            ESPStepperMotorServer_Logger::logInfo(\"No switch configuration present in config file\");\n        }\n        else\n        {\n            for (JsonVariant switchConfigEntry : configs)\n            {\n                const char *value = switchConfigEntry[\"name\"].as<const char *>();\n                ESPStepperMotorServer_PositionSwitch *switchConfig = new ESPStepperMotorServer_PositionSwitch(\n                    (switchConfigEntry[\"ioPin\"] | 255),\n                    (switchConfigEntry[\"stepperIndex\"] | 255),\n                    (switchConfigEntry[\"switchType\"] | 255),\n                    ((value) ? value : \"undefined\"),\n                    (switchConfigEntry[\"switchPosition\"] | 0));\n                //check for macro actions\n                if (switchConfigEntry[JSON_SECTION_NAME_SWITCH_CONFIGURATION_MACROACTIONS])\n                {\n                    JsonArray macroActionsJsonArray = switchConfigEntry[JSON_SECTION_NAME_SWITCH_CONFIGURATION_MACROACTIONS].as<JsonArray>();\n                    if (macroActionsJsonArray)\n                    {\n                        for (JsonVariant macroActionJson : macroActionsJsonArray)\n                        {\n                            switchConfig->addMacroAction(ESPStepperMotorServer_MacroAction::fromJsonObject(macroActionJson));\n                        }\n                    }\n                }\n\n                if (switchConfigEntry[\"id\"])\n                {\n                    this->setSwitch(switchConfig, switchConfigEntry[\"id\"]);\n                }\n                else\n                {\n                    this->addSwitch(switchConfig);\n                }\n                configCounter++;\n            }\n            ESPStepperMotorServer_Logger::logInfof(\"%i switch configuration entr%s loaded from config file\\n\", configCounter, (configCounter == 1) ? \"y\" : \"ies\");\n        }\n\n        // ENCODER CONFIG\n        configCounter = 0;\n        configs = doc[JSON_SECTION_NAME_ROTARY_ENCODER_CONFIGURATIONS].as<JsonArray>();\n        if (!configs)\n        {\n            ESPStepperMotorServer_Logger::logInfo(\"No rotary encoder configuration present in config file\");\n        }\n        else\n        {\n            for (JsonVariant encoderConfigEntry : configs)\n            {\n                const char *value = encoderConfigEntry[\"name\"].as<const char *>();\n                //char pinA, char pinB, String displayName, int stepMultiplier, byte stepperIndex\n                ESPStepperMotorServer_RotaryEncoder *encoderConfig = new ESPStepperMotorServer_RotaryEncoder(\n                    (encoderConfigEntry[\"pinA\"] | 255),\n                    (encoderConfigEntry[\"pinB\"] | 255),\n                    ((value) ? value : \"undefined\"),\n                    (encoderConfigEntry[\"stepMultiplier\"] | 255),\n                    (encoderConfigEntry[\"stepperIndex\"] | 255));\n                if (encoderConfigEntry[\"id\"])\n                {\n                    this->setRotaryEncoder(encoderConfig, encoderConfigEntry[\"id\"]);\n                }\n                else\n                {\n                    this->addRotaryEncoder(encoderConfig);\n                }\n                configCounter++;\n            }\n            ESPStepperMotorServer_Logger::logInfof(\"%i rotary encoder configuration entr%s loaded from config file\\n\", configCounter, (configCounter == 1) ? \"y\" : \"ies\");\n        }\n\n        // Close the file\n        configFile.close();\n        return true;\n    }\n    else\n    {\n        ESPStepperMotorServer_Logger::logWarningf(\"Failed to load configuration file from LittleFS. File %s not found or LittleFS is not enabled/mounted\\n\", filename.c_str());\n        return false;\n    }\n}\n\nbyte ESPStepperMotorServer_Configuration::addStepperConfiguration(ESPStepperMotorServer_StepperConfiguration *stepperConfig)\n{\n    //find first index that is not NULL and use as id\n    byte id = 0;\n    for (id = 0; id < ESPServerMaxSteppers; id++)\n    {\n        if (this->configuredSteppers[id] == NULL)\n        {\n            stepperConfig->setId(id);\n            this->configuredSteppers[id] = stepperConfig;\n            this->updateConfiguredFlexyStepperCache();\n            return id;\n        }\n    }\n    ESPStepperMotorServer_Logger::logWarningf(\"The maximum amount of stepper configurations (%i) that can be configured has been reached, no more stepper configs can be added\\n\", ESPServerMaxSteppers);\n    return 255;\n}\n\nbyte ESPStepperMotorServer_Configuration::addSwitch(ESPStepperMotorServer_PositionSwitch *positionSwitch)\n{\n    //find first index that is not NULL and use as id\n    byte id = 0;\n    for (id = 0; id < ESPServerMaxSwitches; id++)\n    {\n        if (this->allConfiguredSwitches[id] == NULL)\n        {\n            positionSwitch->setId(id);\n            this->allConfiguredSwitches[id] = positionSwitch;\n            this->updateSwitchCaches();\n            return id;\n        }\n    }\n    ESPStepperMotorServer_Logger::logWarningf(\"The maximum amount of switches (%i) that can be configured has been reached, no more switches can be added\\n\", ESPServerMaxSwitches);\n    return 255;\n}\n\nbyte ESPStepperMotorServer_Configuration::addRotaryEncoder(ESPStepperMotorServer_RotaryEncoder *encoder)\n{\n    //find first index that is not NULL and use as id\n    byte id = 0;\n    for (id = 0; id < ESPServerMaxRotaryEncoders; id++)\n    {\n        if (this->configuredRotaryEncoders[id] == NULL)\n        {\n            encoder->setId(id);\n            this->configuredRotaryEncoders[id] = encoder;\n            return id;\n        }\n    }\n    ESPStepperMotorServer_Logger::logWarningf(\"The maximum amount of rotary encoders (%i) that can be configured has been reached, no more encoders can be added\\n\", ESPServerMaxRotaryEncoders);\n    return 255;\n}\n\nvoid ESPStepperMotorServer_Configuration::setStepperConfiguration(ESPStepperMotorServer_StepperConfiguration *stepperConfig, byte id)\n{\n    if (id >= ESPServerMaxSteppers)\n    {\n        ESPStepperMotorServer_Logger::logWarningf(\"The given stepper id/index (%i) exceeds the allowed max amount of %i. Stepper config will not be set\\n\", id, ESPServerMaxSteppers);\n    }\n    else\n    {\n        stepperConfig->setId(id);\n        this->configuredSteppers[id] = stepperConfig;\n    }\n    this->updateConfiguredFlexyStepperCache();\n}\n\nvoid ESPStepperMotorServer_Configuration::setSwitch(ESPStepperMotorServer_PositionSwitch *positionSwitch, byte id)\n{\n    if (id >= ESPServerMaxSwitches)\n    {\n        ESPStepperMotorServer_Logger::logWarningf(\"The given switch id/index (%i) exceeds the allowed max amount of %i. Switch config will not be set\\n\", id, ESPServerMaxSteppers);\n    }\n    else\n    {\n        positionSwitch->setId(id);\n        this->allConfiguredSwitches[id] = positionSwitch;\n        this->updateSwitchCaches();\n    }\n}\n\nvoid ESPStepperMotorServer_Configuration::setRotaryEncoder(ESPStepperMotorServer_RotaryEncoder *encoder, byte id)\n{\n    if (id >= ESPServerMaxRotaryEncoders)\n    {\n        ESPStepperMotorServer_Logger::logWarningf(\"The given rotray encoder id/index (%i) exceeds the allowed max amount of %i. Rotray encoder config will not be set\\n\", id, ESPServerMaxSteppers);\n    }\n    else\n    {\n        encoder->setId(id);\n        this->configuredRotaryEncoders[id] = encoder;\n    }\n}\n\nESPStepperMotorServer_StepperConfiguration *ESPStepperMotorServer_Configuration::getStepperConfiguration(unsigned char id)\n{\n    if (id >= ESPServerMaxSteppers)\n    {\n        ESPStepperMotorServer_Logger::logWarningf(\"Invalid stepper config requested with id %i. Will retun NULL\\n\", id);\n        return NULL;\n    }\n    return this->configuredSteppers[id];\n}\n\nvoid ESPStepperMotorServer_Configuration::updateConfiguredFlexyStepperCache()\n{\n    byte flexyStepperCounter = 0;\n    ESPStepperMotorServer_StepperConfiguration *stepper;\n\n    //clear list first\n    for (byte i = 0; i < ESPServerMaxSteppers; i++)\n    {\n        //TODO: check if this delete call is appropriate, currently it casues kernel panic\n        //delete (this->configuredFlexySteppers[i]);\n        this->configuredFlexySteppers[i] = NULL;\n    }\n\n    for (byte i = 0; i < ESPServerMaxSteppers; i++)\n    {\n        stepper = this->getStepperConfiguration(i);\n        if (stepper)\n        {\n            this->configuredFlexySteppers[flexyStepperCounter] = stepper->getFlexyStepper();\n            flexyStepperCounter++;\n        }\n    }\n}\n\nESP_FlexyStepper **ESPStepperMotorServer_Configuration::getConfiguredFlexySteppers()\n{\n    return this->configuredFlexySteppers;\n}\n\nESPStepperMotorServer_PositionSwitch *ESPStepperMotorServer_Configuration::getSwitch(byte id)\n{\n    if (id < 0 || id > ESPServerMaxSwitches - 1)\n    {\n        ESPStepperMotorServer_Logger::logWarningf(\"Invalid switch config requested with id %i. Will retun NULL\\n\", id);\n        return NULL;\n    }\n    return this->allConfiguredSwitches[id];\n}\n\nESPStepperMotorServer_PositionSwitch *ESPStepperMotorServer_Configuration::getFirstConfiguredLimitSwitchForStepper(unsigned char steperConfigId)\n{\n    for (byte i = 0; i < ESPServerMaxSwitches; i++)\n    {\n        ESPStepperMotorServer_PositionSwitch *positionSwitch = this->configuredLimitSwitches[i];\n        if (positionSwitch != NULL && positionSwitch->getStepperIndex() == steperConfigId)\n        {\n            return positionSwitch;\n        }\n    }\n    return NULL;\n}\n\nESPStepperMotorServer_RotaryEncoder *ESPStepperMotorServer_Configuration::getRotaryEncoder(unsigned char id)\n{\n    if (id >= ESPServerMaxRotaryEncoders)\n    {\n        ESPStepperMotorServer_Logger::logWarningf(\"Invalid rotary encoder config requested with id %i. Will retun NULL\\n\", id);\n        return NULL;\n    }\n    return this->configuredRotaryEncoders[id];\n}\n\nvoid ESPStepperMotorServer_Configuration::removeStepperConfiguration(byte id)\n{\n    //check if any switches are connected to this stepper and delete those\n    for (byte switchIndex = 0; switchIndex < ESPServerMaxSwitches; switchIndex++)\n    {\n        ESPStepperMotorServer_PositionSwitch *switchConfig = this->getSwitch(switchIndex);\n        if (switchConfig && switchConfig->getStepperIndex() == id)\n        {\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n            ESPStepperMotorServer_Logger::logDebugf(\"Found switch configuration (id=%i) that is linked to stepper config (id=%i) to be deleted. Will delete switch config as well\\n\", switchConfig->getId(), id);\n#endif\n            this->removeSwitch(switchIndex);\n        }\n    }\n    //check if any switches are connected to this stepper and delete those\n    for (byte encoderIndex = 0; encoderIndex < ESPServerMaxRotaryEncoders; encoderIndex++)\n    {\n        ESPStepperMotorServer_RotaryEncoder *encoderConfig = this->getRotaryEncoder(encoderIndex);\n        if (encoderConfig && encoderConfig->getStepperIndex() == id)\n        {\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n            ESPStepperMotorServer_Logger::logDebugf(\"Found encoder configuration (id=%i) that is linked to stepper config (id=%i) to be deleted. Will delete encoder config as well\\n\", encoderConfig->getId(), id);\n#endif\n            this->removeRotaryEncoder(encoderIndex);\n        }\n    }\n    //finally delete the stepper config itself\n    //TODO: check if this delete call is appropriate, currently it casues kernel panic\n    //delete (this->configuredSteppers[id]);\n    this->configuredSteppers[id] = NULL;\n    this->updateConfiguredFlexyStepperCache();\n}\n\nvoid ESPStepperMotorServer_Configuration::removeSwitch(byte id)\n{\n    //TODO: check if this delete call is appropriate, currently it casues kernel panic\n    //delete (this->allConfiguredSwitches[id]);\n    this->allConfiguredSwitches[id] = NULL;\n    this->updateSwitchCaches();\n}\n\nvoid ESPStepperMotorServer_Configuration::updateSwitchCaches()\n{\n    //reset all caches first\n    for (byte i = 0; i < ESPServerMaxSwitches; i++)\n    {\n        //TODO: check if this is appropriate, currently it causes kernel panic\n        //delete (this->configuredEmergencySwitches[i]);\n        this->configuredEmergencySwitches[i] = NULL;\n        //TODO: check if this delete call is appropriate, currently it casues kernel panic\n        //delete (this->configuredLimitSwitches[i]);\n        this->configuredLimitSwitches[i] = NULL;\n        this->allSwitchIoPins[i] = (signed char)-1;\n    }\n\n    //now rebuild the caches\n    byte emergencySwitchCacheIndex = 0;\n    byte limitSwitchCacheIndex = 0;\n\n    for (byte i = 0; i < ESPServerMaxSwitches; i++)\n    {\n        if (this->allConfiguredSwitches[i])\n        {\n            this->allSwitchIoPins[i] = this->allConfiguredSwitches[i]->getIoPinNumber();\n\n            if (this->allConfiguredSwitches[i]->isEmergencySwitch())\n            {\n                this->configuredEmergencySwitches[emergencySwitchCacheIndex] = this->allConfiguredSwitches[i];\n                emergencySwitchCacheIndex++;\n            }\n            else if (this->allConfiguredSwitches[i]->isLimitSwitch())\n            {\n                this->configuredLimitSwitches[limitSwitchCacheIndex] = this->allConfiguredSwitches[i];\n                limitSwitchCacheIndex++;\n            }\n        }\n    }\n}\n\nvoid ESPStepperMotorServer_Configuration::removeRotaryEncoder(byte id)\n{\n    //TODO: check if this delete call is appropriate, currently it casues kernel panic\n    //delete (this->configuredRotaryEncoders[id]);\n    this->configuredRotaryEncoders[id] = NULL;\n}\n"
  },
  {
    "path": "src/ESPStepperMotorServer_Configuration.h",
    "content": "\n//      ******************************************************************\n//      *                                                                *\n//      *    Header file for ESPStepperMotorServer_Configuration.cpp     *\n//      *                                                                *\n//      *               Copyright (c) Paul Kerspe, 2019                  *\n//      *                                                                *\n//      ******************************************************************\n\n// this class repesents the complete configuration object and provide\n// helper functions to persist and load the configuration form the SPIFFS of the ESP\n\n// MIT License\n//\n// Copyright (c) 2019 Paul Kerspe\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 furnished\n// to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// 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 THE\n// SOFTWARE.\n\n#ifndef ESPStepperMotorServer_Configuration_h\n#define ESPStepperMotorServer_Configuration_h\n\n#include <ArduinoJson.h>\n#include <IPAddress.h>\n#include <FS.h>\n#include <ESPStepperMotorServer.h>\n#include <ESPStepperMotorServer_Logger.h>\n#include <ESPStepperMotorServer_PositionSwitch.h>\n#include <ESPStepperMotorServer_RotaryEncoder.h>\n#include <ESPStepperMotorServer_StepperConfiguration.h>\n\n// TODO: get IO pin configration rules implemented and helper to provide information about which pins to use and which not\n// https://randomnerdtutorials.com/esp32-pinout-reference-gpios/\n// Input only pins\n// GPIOs 34 to 39 are GPIs – input only pins. These pins don’t have internal pull-ups or pull-down resistors. They can’t be used as outputs, so use these pins only as inputs:\n// GPIO 34, GPIO 35, GPIO 36, GPIO 39\n// GPIO 12 boot fail if pulled high, better not use as active low\n\n#define DEFAULT_SERVER_PORT 80\n#define DEFAULT_WIFI_MODE 1\n\nclass ESPStepperMotorServer_PositionSwitch;\n//\n// the ESPStepperMotorServer_Configuration class\nclass ESPStepperMotorServer_Configuration\n{\n  friend class ESPStepperMotorServer;\n  friend class ESPStepperMotorServer_RestAPI;\n\npublic:\n  ESPStepperMotorServer_Configuration(const char *configFilePath, bool isSPIFFSactive);\n  String getCurrentConfigurationAsJSONString(bool prettyPrint = true, bool includePasswords = false);\n  unsigned int calculateRequiredJsonDocumentSizeForCurrentConfiguration();\n  void printCurrentConfigurationAsJsonToSerial();\n  bool saveCurrentConfiguationToSpiffs(String filename = \"\");\n  bool loadConfiguationFromSpiffs(String filename = \"\");\n  void serializeServerConfiguration(JsonDocument &doc, bool includePasswords = false);\n\n  byte addStepperConfiguration(ESPStepperMotorServer_StepperConfiguration *stepperConfig);\n  byte addSwitch(ESPStepperMotorServer_PositionSwitch *positionSwitch);\n  byte addRotaryEncoder(ESPStepperMotorServer_RotaryEncoder *encoder);\n  void setStepperConfiguration(ESPStepperMotorServer_StepperConfiguration *stepperConfig, byte id);\n  void setSwitch(ESPStepperMotorServer_PositionSwitch *positionSwitch, byte id);\n  void setRotaryEncoder(ESPStepperMotorServer_RotaryEncoder *encoder, byte id);\n  void removeStepperConfiguration(byte id);\n  void removeSwitch(byte id);\n    \n  void removeRotaryEncoder(byte id);\n  ESPStepperMotorServer_StepperConfiguration *getStepperConfiguration(unsigned char id);\n  ESPStepperMotorServer_PositionSwitch *getSwitch(byte id);\n  ESPStepperMotorServer_PositionSwitch *getFirstConfiguredLimitSwitchForStepper(unsigned char id);\n  ESPStepperMotorServer_RotaryEncoder *getRotaryEncoder(unsigned char id);\n  ESP_FlexyStepper **getConfiguredFlexySteppers();\n  // a cache containing all IO pins that are used by switches. The indexes matches the indexes in the configuredSwitches (=switch ID)\n  // -1 is used to indicate an emtpy array slot\n  signed char allSwitchIoPins[ESPServerMaxSwitches];\n  int serverPort = DEFAULT_SERVER_PORT;\n  int wifiMode = 1;\n  const char *apName = \"ESPStepperMotorServer\";\n  const char *apPassword = \"Aa123456\";\n  const char *wifiSsid = \"undefined\";\n  const char *wifiPassword = \"undefined\";\n  int motionControllerCpuCore = 0;\n\n  IPAddress staticIP;\n  IPAddress gatewayIP;\n  IPAddress subnetMask;\n  IPAddress dns1IP;\n  IPAddress dns2IP;\n\n  //this \"cache\" should not be private since we need to use it in the ISRs and any getter to retrieve it would slow down processing\n  ESPStepperMotorServer_PositionSwitch *configuredEmergencySwitches[ESPServerMaxSwitches] = {NULL};\n\nprivate:\n  //\n  // private member variables\n  //\n  bool isCurrentConfigurationSaved = false;\n  const char *_configFilePath;\n  bool _isSPIFFSactive = false;\n\n  /**** the follwoing variables represent the in-memory configuration settings *******/\n  // an array to hold all configured stepper configurations\n  ESPStepperMotorServer_StepperConfiguration *configuredSteppers[ESPServerMaxSteppers] = {NULL};\n\n  // this is a shortcut/cache for all configured flexy stepper instances, yet it will not have the same indexes as the configuredSteppers,\n  // but solely an array that is filled from the beginnnig without emtpy slots.\n  // it is used to have a quick access to configured flexy steppers in time critical functions\n  void updateConfiguredFlexyStepperCache(void);\n  ESP_FlexyStepper *configuredFlexySteppers[ESPServerMaxSteppers] = {NULL};\n  // an array to hold all configured switches\n  ESPStepperMotorServer_PositionSwitch *allConfiguredSwitches[ESPServerMaxSwitches] = {NULL};\n  // update the caches for emergency and limit switches\n  void updateSwitchCaches();\n  ESPStepperMotorServer_PositionSwitch *configuredLimitSwitches[ESPServerMaxSwitches] = {NULL};\n  // an array to hold all configured rotary encoders\n  ESPStepperMotorServer_RotaryEncoder *configuredRotaryEncoders[ESPServerMaxRotaryEncoders] = {NULL};\n\n  /////////////////////////////////////////////////////\n  // CONSTANTS FOR JSON CONFIGURATION PROPERTY NAMES //\n  /////////////////////////////////////////////////////\n  // GENERAL SERVER CONFIGURATION //\n  const char *JSON_SECTION_NAME_SERVER_CONFIGURATION = \"serverConfiguration\";\n  const char *JSON_PROPERTY_NAME_PORT_NUMBER = \"port\";\n  const char *JSON_PROPERTY_NAME_WIFI_MODE = \"wififMode\"; //allowed values are 0 (wifi off = ESPServerWifiModeDisabled),1 (AP mode = ESPServerWifiModeAccessPoint) and 2 (client mode = ESPServerWifiModeClient)\n  const char *JSON_PROPERTY_NAME_WIFI_SSID = \"wifiSsid\";\n  const char *JSON_PROPERTY_NAME_WIFI_PASSWORD = \"wifiPassword\";\n  const char *JSON_PROPERTY_NAME_WIFI_AP_NAME = \"apName\";\n  const char *JSON_PROPERTY_NAME_WIFI_AP_PASSWORD = \"apPassword\";\n  const char *JSON_PROPERTY_NAME_CPUCORE_FOR_MOTIONCONTROLLER_SERVICE = \"motionControllerCpuCore\";\n\n  //for static IP settings\n  const char *JSON_PROPERTY_NAME_WIFI_STATIC_IP_ADDRESS = \"staticIP\";\n  const char *JSON_PROPERTY_NAME_WIFI_STATIC_IP_GATEWAY = \"gatewayIP\";\n  const char *JSON_PROPERTY_NAME_WIFI_STATIC_IP_SUBNETMASK = \"subnetMask\";\n  const char *JSON_PROPERTY_NAME_WIFI_STATIC_IP_DNS1 = \"dns1IP\";\n  const char *JSON_PROPERTY_NAME_WIFI_STATIC_IP_DNS2 = \"dns2IP\";\n\n\n  // STEPPER SPECIFIC CONFIGURATION //\n  const char *JSON_SECTION_NAME_STEPPER_CONFIGURATIONS = \"stepperConfigurations\";\n\n  // SWITCH SPECIFIC CONFIGURATION //\n  const char *JSON_SECTION_NAME_SWITCH_CONFIGURATIONS = \"switchConfigurations\";\n  const char *JSON_SECTION_NAME_SWITCH_CONFIGURATION_MACROACTIONS = \"macroActions\";\n\n  // ROTARY ENCODER SPECIFIC CONFIGURATION //\n  const char *JSON_SECTION_NAME_ROTARY_ENCODER_CONFIGURATIONS = \"rotaryEncoderConfigurations\";\n};\n#endif\n"
  },
  {
    "path": "src/ESPStepperMotorServer_Logger.cpp",
    "content": "//      *******************************************************************\n//      *                                                                 *\n//      *  ESP8266 and ESP32 Stepper Motor Server  - Logging class        *\n//      *            Copyright (c) Paul Kerspe, 2019                      *\n//      *                                                                 *\n//      *******************************************************************\n//\n// MIT License\n//\n// Copyright (c) 2019 Paul Kerspe\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 furnished\n// to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// 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 THE\n// SOFTWARE.\n//\n\n#include <ESPStepperMotorServer_Logger.h>\n\nbyte ESPStepperMotorServer_Logger::_logLevel = ESPServerLogLevel_INFO;\nbool ESPStepperMotorServer_Logger::_isDebugLevelSet = false;\n\nconst char *LEVEL_STRING_ALL = \"ALL\";\nconst char *LEVEL_STRING_DEBUG = \"DEBUG\";\nconst char *LEVEL_STRING_INFO = \"INFO\";\nconst char *LEVEL_STRING_WARNING = \"WARNING\";\n\nESPStepperMotorServer_Logger::ESPStepperMotorServer_Logger(String loggerName)\n{\n    this->_loggerName = loggerName;\n}\n\nESPStepperMotorServer_Logger::ESPStepperMotorServer_Logger()\n{\n    this->_loggerName = \"root\";\n}\n\nvoid ESPStepperMotorServer_Logger::printBinaryWithLeadingZeros(char *result, byte var)\n{\n    int charIndex = 0;\n    for (byte test = 0x80; test; test >>= 1)\n    {\n        result[charIndex] = (var & test) ? '1' : '0';\n        charIndex++;\n        // Serial.write(var & test ? '1' : '0');\n    }\n    result[charIndex] = '\\0';\n}\n\nvoid ESPStepperMotorServer_Logger::setLogLevel(byte logLevel)\n{\n    ESPStepperMotorServer_Logger::_isDebugLevelSet = false;\n    const char *msgTemplate = \"Setting log level to %s\\n\";\n    switch (logLevel)\n    {\n    case ESPServerLogLevel_ALL:\n        ESPStepperMotorServer_Logger::logInfof(msgTemplate, LEVEL_STRING_ALL);\n        ESPStepperMotorServer_Logger::_isDebugLevelSet = true;\n        break;\n    case ESPServerLogLevel_DEBUG:\n        ESPStepperMotorServer_Logger::logInfof(msgTemplate, LEVEL_STRING_DEBUG);\n        ESPStepperMotorServer_Logger::_isDebugLevelSet = true;\n        break;\n    case ESPServerLogLevel_INFO:\n        ESPStepperMotorServer_Logger::logInfof(msgTemplate, LEVEL_STRING_INFO);\n        break;\n    case ESPServerLogLevel_WARNING:\n        ESPStepperMotorServer_Logger::logInfof(msgTemplate, LEVEL_STRING_WARNING);\n        break;\n    default:\n        ESPStepperMotorServer_Logger::logWarning(\"Invalid log level given, log level will be set to info\");\n        ESPStepperMotorServer_Logger::_logLevel = ESPServerLogLevel_INFO;\n        return;\n    }\n    ESPStepperMotorServer_Logger::_logLevel = logLevel;\n}\n\nbyte ESPStepperMotorServer_Logger::getLogLevel()\n{\n    return ESPStepperMotorServer_Logger::_logLevel;\n}\n\nvoid ESPStepperMotorServer_Logger::logf(const char *level, const char *format, va_list args)\n{\n    char buf[1000];\n    vsnprintf(buf, sizeof(buf), format, args);\n    ESPStepperMotorServer_Logger::log(level, buf, false, false);\n}\n\nvoid ESPStepperMotorServer_Logger::log(const char *level, const char *msg, boolean newLine, boolean ommitLogLevel)\n{\n    if (!ommitLogLevel)\n    {\n        Serial.printf(\"[%s] \", level);\n    }\n    if (newLine == true)\n    {\n        Serial.println(msg);\n    }\n    else\n    {\n        Serial.print(msg);\n    }\n}\n\nbool ESPStepperMotorServer_Logger::isDebugEnabled()\n{\n    return ESPStepperMotorServer_Logger::_isDebugLevelSet;\n}\n\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\nvoid ESPStepperMotorServer_Logger::logDebug(const char *msg, boolean newLine, boolean ommitLogLevel)\n{\n    if (ESPStepperMotorServer_Logger::_isDebugLevelSet)\n    {\n        ESPStepperMotorServer_Logger::log(LEVEL_STRING_DEBUG, msg, newLine, ommitLogLevel);\n    }\n}\n\nvoid ESPStepperMotorServer_Logger::logDebugf(const char *format, ...)\n{\n    if (ESPStepperMotorServer_Logger::_isDebugLevelSet)\n    {\n        va_list _argumentList;\n        va_start(_argumentList, format);\n        ESPStepperMotorServer_Logger::logf(LEVEL_STRING_DEBUG, format, _argumentList);\n        va_end(_argumentList);\n    }\n}\n\nvoid ESPStepperMotorServer_Logger::logDebug(String msg, boolean newLine, boolean ommitLogLevel)\n{\n    if (ESPStepperMotorServer_Logger::_isDebugLevelSet)\n    {\n        ESPStepperMotorServer_Logger::logDebug(msg.c_str(), newLine, ommitLogLevel);\n    }\n}\n#endif\n\nvoid ESPStepperMotorServer_Logger::logInfo(const char *msg, boolean newLine, boolean ommitLogLevel)\n{\n    if (getLogLevel() >= ESPServerLogLevel_INFO)\n    {\n        ESPStepperMotorServer_Logger::log(LEVEL_STRING_INFO, msg, newLine, ommitLogLevel);\n    }\n}\nvoid ESPStepperMotorServer_Logger::logInfof(const char *format, ...)\n{\n    if (getLogLevel() >= ESPServerLogLevel_INFO)\n    {\n        va_list args;\n        va_start(args, format);\n        ESPStepperMotorServer_Logger::logf(LEVEL_STRING_INFO, format, args);\n        va_end(args);\n    }\n}\n\nvoid ESPStepperMotorServer_Logger::logWarning(const char *msg, boolean newLine, boolean ommitLogLevel)\n{\n    ESPStepperMotorServer_Logger::log(LEVEL_STRING_WARNING, msg, newLine, ommitLogLevel);\n}\n\nvoid ESPStepperMotorServer_Logger::logWarningf(const char *format, ...)\n{\n    va_list args;\n    va_start(args, format);\n    ESPStepperMotorServer_Logger::logf(LEVEL_STRING_WARNING, format, args);\n    va_end(args);\n}"
  },
  {
    "path": "src/ESPStepperMotorServer_Logger.h",
    "content": "//      ******************************************************************\n//      *                                                                *\n//      *       Header file for ESPStepperMotorServer_Logger.cpp         *\n//      *                                                                *\n//      *               Copyright (c) Paul Kerspe, 2019                  *\n//      *                                                                *\n//      ******************************************************************\n\n// MIT License\n//\n// Copyright (c) 2019 Paul Kerspe\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 furnished\n// to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// 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 THE\n// SOFTWARE.\n\n#ifndef ESPStepperMotorServer_Logger_h\n#define ESPStepperMotorServer_Logger_h\n\n#include <Arduino.h>\n\n#define ESPServerLogLevel_ALL 4\n#define ESPServerLogLevel_DEBUG 3\n#define ESPServerLogLevel_INFO 2\n#define ESPServerLogLevel_WARNING 1\n\n//\n// the ESPStepperMotorServer_Logger class\nclass ESPStepperMotorServer_Logger\n{\n  public:\n    ESPStepperMotorServer_Logger();\n    ESPStepperMotorServer_Logger(String logName);\n    static void setLogLevel(byte);\n    static byte getLogLevel(void);\n    static void logDebug(const char *msg, boolean newLine = true, boolean ommitLogLevel = false);\n    static void logDebugf(const char *format, ...);\n    static void logDebug(String msg, boolean newLine = true, boolean ommitLogLevel = false);\n    static void logInfo(const char *msg, boolean newLine = true, boolean ommitLogLevel = false);\n    static void logInfof(const char *format, ...);\n    static void logWarning(const char *msg, boolean newLine = true, boolean ommitLogLevel = false);\n    static void logWarningf(const char *format, ...);\n    static bool isDebugEnabled();\n\n  private:\n    static void log(const char *level, const char *msg, boolean newLine, boolean ommitLogLevel);\n    static void printBinaryWithLeadingZeros(char *result, byte var);\n    static void logf(const char *level, const char *format, va_list args);\n    static byte _logLevel;\n    static bool _isDebugLevelSet;\n    String _loggerName;\n};\n\n#endif\n"
  },
  {
    "path": "src/ESPStepperMotorServer_MacroAction.cpp",
    "content": "#include <ESPStepperMotorServer_MacroAction.h>\n#include \"ESPStepperMotorServer.h\"\n\nESPStepperMotorServer_MacroAction::ESPStepperMotorServer_MacroAction(MacroActionType actionType, int val1, long val2) {\n    this->actionType = actionType;\n    this->val1 = val1;\n    this->val2 = val2;\n}\n\nbool ESPStepperMotorServer_MacroAction::execute(ESPStepperMotorServer *serverRef) {\n    Serial.println(\"Execute called for MacroAction\");\n    switch (this->actionType) {\n    case moveBy: {\n        ESPStepperMotorServer_StepperConfiguration *stepper = serverRef->getCurrentServerConfiguration()->getStepperConfiguration(this->val1);\n        if (stepper && stepper->getFlexyStepper()) {\n            stepper->getFlexyStepper()->moveRelativeInSteps(this->val2);\n        }\n        break;\n    }\n    case MacroActionType::moveTo: {\n        ESPStepperMotorServer_StepperConfiguration *stepper = serverRef->getCurrentServerConfiguration()->getStepperConfiguration(this->val1);\n        if (stepper && stepper->getFlexyStepper()) {\n            stepper->getFlexyStepper()->setTargetPositionInSteps(this->val2);\n        }\n        break;\n    }\n    case MacroActionType::releaseEmergencyStop: {\n        serverRef->revokeEmergencyStop();\n        break;\n    }\n    case MacroActionType::triggerEmergencyStop: {\n        serverRef->performEmergencyStop();\n        break;\n    }\n    case MacroActionType::setAcceleration: {\n        ESPStepperMotorServer_StepperConfiguration *stepper = serverRef->getCurrentServerConfiguration()->getStepperConfiguration(this->val1);\n        if (stepper && stepper->getFlexyStepper()) {\n            stepper->getFlexyStepper()->setAccelerationInStepsPerSecondPerSecond((float)this->val2);\n        }\n        break;\n    }\n    case MacroActionType::setDeceleration: {\n        ESPStepperMotorServer_StepperConfiguration *stepper = serverRef->getCurrentServerConfiguration()->getStepperConfiguration(this->val1);\n        if (stepper && stepper->getFlexyStepper()) {\n            stepper->getFlexyStepper()->setDecelerationInStepsPerSecondPerSecond((float)this->val2);\n        }\n        break;\n    }\n    case MacroActionType::setHome: {\n        ESPStepperMotorServer_StepperConfiguration *stepper = serverRef->getCurrentServerConfiguration()->getStepperConfiguration(this->val1);\n        if (stepper && stepper->getFlexyStepper()) {\n            stepper->getFlexyStepper()->setCurrentPositionAsHomeAndStop();\n        }\n        break;\n    }\n    case MacroActionType::setLimitA: {\n        ESPStepperMotorServer_StepperConfiguration *stepper = serverRef->getCurrentServerConfiguration()->getStepperConfiguration(this->val1);\n        if (stepper && stepper->getFlexyStepper()) {\n            stepper->getFlexyStepper()->setLimitSwitchActive(ESP_FlexyStepper::LIMIT_SWITCH_BEGIN);\n        }\n        break;\n    }\n    case MacroActionType::setLimitB: {\n        ESPStepperMotorServer_StepperConfiguration *stepper = serverRef->getCurrentServerConfiguration()->getStepperConfiguration(this->val1);\n        if (stepper && stepper->getFlexyStepper()) {\n            stepper->getFlexyStepper()->setLimitSwitchActive(ESP_FlexyStepper::LIMIT_SWITCH_END);\n        }\n        break;\n    }\n    case MacroActionType::setSpeed: {\n        ESPStepperMotorServer_StepperConfiguration *stepper = serverRef->getCurrentServerConfiguration()->getStepperConfiguration(this->val1);\n        if (stepper && stepper->getFlexyStepper()) {\n            stepper->getFlexyStepper()->setSpeedInStepsPerSecond(this->val2);\n        }\n        break;\n    }\n    case MacroActionType::setOutputHigh:\n        digitalWrite(this->val1, HIGH);\n        break;\n    case MacroActionType::setOutputLow:\n        digitalWrite(this->val1, LOW);\n        break;\n    default:\n        break;\n    }\n    return true;\n}\n\nvoid ESPStepperMotorServer_MacroAction::addSerializedInstanceToJsonArray(JsonArray jsonArray) {\n    JsonObject nestedMacroAction = jsonArray.createNestedObject();\n    nestedMacroAction[\"type\"] = this->actionType;\n    nestedMacroAction[\"val1\"] = this->val1;\n    nestedMacroAction[\"val2\"] = this->val2;\n}\n\nESPStepperMotorServer_MacroAction *ESPStepperMotorServer_MacroAction::fromJsonObject(JsonObject macroActionJson) {\n    int val1 = macroActionJson[\"val1\"];\n    long val2 = (long)macroActionJson[\"val2\"];\n    MacroActionType type = macroActionJson[\"type\"];\n    return new ESPStepperMotorServer_MacroAction(type, val1, val2\n    );\n}\n\nMacroActionType ESPStepperMotorServer_MacroAction::getType(void) {\n    return this->actionType;\n}\n\nint ESPStepperMotorServer_MacroAction::getVal1(void) {\n    return this->val1;\n}\n\nlong ESPStepperMotorServer_MacroAction::getVal2(void) {\n    return this->val2;\n}\n"
  },
  {
    "path": "src/ESPStepperMotorServer_MacroAction.h",
    "content": "#ifndef ESPStepperMotorServer_MacroAction_h\n#define ESPStepperMotorServer_MacroAction_h\n\n#include <ArduinoJson.h>\n#include <ESP_FlexyStepper.h>\n\nclass ESPStepperMotorServer;\n\nenum MacroActionType {\n    moveTo, moveBy, setSpeed, setAcceleration, setDeceleration, setHome, setLimitA, setLimitB, setOutputHigh, setOutputLow, triggerEmergencyStop, releaseEmergencyStop\n};\n\nclass ESPStepperMotorServer_MacroAction\n{\npublic:\n    ESPStepperMotorServer_MacroAction(MacroActionType actionType, int val1, long val2 = 0);\n    bool execute(ESPStepperMotorServer *serverRef);\n    void addSerializedInstanceToJsonArray(JsonArray jsonArray);\n    static ESPStepperMotorServer_MacroAction * fromJsonObject(JsonObject macroActionJson);\n    MacroActionType getType(void);\n    int getVal1(void);\n    long getVal2(void);\nprivate:\n    MacroActionType actionType;\n    int val1 = 0;\n    long val2 = 0;\n};\n#endif\n"
  },
  {
    "path": "src/ESPStepperMotorServer_MotionController.cpp",
    "content": "\n//      *********************************************************\n//      *                                                       *\n//      *     ESP32 Stepper Motor Server -  Motion Controller   *\n//      *                                                       *\n//      *            Copyright (c) Paul Kerspe, 2019            *\n//      *                                                       *\n//      **********************************************************\n\n// MIT License\n//\n// Copyright (c) 2019 Paul Kerspe\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 furnished\n// to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// 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 THE\n// SOFTWARE.\n\n#include <ESPStepperMotorServer_MotionController.h>\n\n//\n// constructor for the motion controller module\n// creates a freeRTOS Task that runs in the background and triggers the motion updates for the stepper driver\n//\nESPStepperMotorServer_MotionController::ESPStepperMotorServer_MotionController(ESPStepperMotorServer *serverRef)\n{\n  this->serverRef = serverRef;\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n  ESPStepperMotorServer_Logger::logDebug(\"Motor Controller created\");\n#endif\n}\n\nvoid ESPStepperMotorServer_MotionController::start()\n{\n  if (this->xHandle == NULL) //prevent multiple starts\n  {\n    disableCore0WDT();\n    xTaskCreate(\n        ESPStepperMotorServer_MotionController::processMotionUpdates, /* Task function. */\n        \"MotionControl\",                                              /* String with name of task. */\n        10000,                                                        /* Stack size in bytes. */\n        this,                                                         /* Parameter passed as input of the task */\n        2,                                                            /* Priority of the task. */\n        &this->xHandle);                                              /* Task handle. */\n    //esp_task_wdt_delete(this->xHandle);\n    ESPStepperMotorServer_Logger::logInfo(\"Motion Controller task started\");\n  }\n}\n\nvoid ESPStepperMotorServer_MotionController::processMotionUpdates(void *parameter)\n{\n  ESPStepperMotorServer_MotionController *ref = static_cast<ESPStepperMotorServer_MotionController *>(parameter);\n  ESPStepperMotorServer_Configuration *configuration = ref->serverRef->getCurrentServerConfiguration();\n  ESP_FlexyStepper **configuredFlexySteppers = configuration->getConfiguredFlexySteppers();\n  bool emergencySwitchFlag = false;\n  bool allMovementsCompleted = true;\n#ifndef ESPStepperMotorServer_COMPILE_NO_WEB\n  int updateCounter = 0;\n#endif\n  while (true)\n  {\n    allMovementsCompleted = true;\n    //update positions of all steppers / trigger stepping if needed\n    for (byte i = 0; i < ESPServerMaxSteppers; i++)\n    {\n      if (configuredFlexySteppers[i])\n      {\n        if (!configuredFlexySteppers[i]->processMovement())\n        {\n          allMovementsCompleted = false;\n        }\n      }\n      else\n      {\n        break;\n      }\n    }\n\n    if (allMovementsCompleted && ref->serverRef->_isRebootScheduled)\n    {\n      //going for reboot since all motion is stopped and reboot has been requested\n      Serial.println(\"Rebooting server now\");\n      ESP.restart();\n    }\n\n    //check for emergency switch\n    if (ref->serverRef->emergencySwitchIsActive && !emergencySwitchFlag)\n    {\n      emergencySwitchFlag = true;\n      ESPStepperMotorServer_Logger::logInfo(\"Emergency Switch triggered\");\n    }\n    else if (!ref->serverRef->emergencySwitchIsActive && emergencySwitchFlag)\n    {\n      emergencySwitchFlag = false;\n    }\n\n#ifndef ESPStepperMotorServer_COMPILE_NO_WEB\n    //check if we should send updated position information via websocket\n    if (ref->serverRef->isWebserverEnabled)\n    {\n      updateCounter++;\n      //we only send sproadically to reduce load and processing times\n      if (updateCounter % 200000 == 0 && ref->serverRef->webSocketServer->count() > 0)\n      {\n        String positionsString = String(\"{\");\n        char segmentBuffer[500];\n        bool isFirstSegment = true;\n        for (byte n = 0; n < ESPServerMaxSteppers; n++)\n        {\n          if (configuredFlexySteppers[n])\n          {\n            if (!isFirstSegment)\n            {\n              positionsString += \",\";\n            }\n            sprintf(segmentBuffer, \"\\\"s%ipos\\\":%ld, \\\"s%ivel\\\":%.3f\", n, configuredFlexySteppers[n]->getCurrentPositionInSteps(), n, configuredFlexySteppers[n]->getCurrentVelocityInStepsPerSecond());\n            //maybe register as friendly class and access property directly and save some processing time\n            positionsString += segmentBuffer;\n            isFirstSegment = false;\n          }\n        }\n        positionsString += \"}\";\n\n        ref->serverRef->sendSocketMessageToAllClients(positionsString.c_str(), positionsString.length());\n        updateCounter = 0;\n      }\n    }\n#endif\n  }\n}\n\nvoid ESPStepperMotorServer_MotionController::stop()\n{\n  vTaskDelete(this->xHandle);\n  this->xHandle = NULL;\n  ESPStepperMotorServer_Logger::logInfo(\"Motion Controller stopped\");\n}\n\n// -------------------------------------- End --------------------------------------\n"
  },
  {
    "path": "src/ESPStepperMotorServer_MotionController.h",
    "content": "//      ******************************************************************\n//      *                                                                *\n//      *   Header file for ESPStepperMotorServer_MotionController.cpp   *\n//      *                                                                *\n//      *               Copyright (c) Paul Kerspe, 2019                  *\n//      *                                                                *\n//      ******************************************************************\n\n// MIT License\n//\n// Copyright (c) 2019 Paul Kerspe\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 furnished\n// to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// 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 THE\n// SOFTWARE.\n\n#ifndef ESPStepperMotorServer_MotionController_h\n#define ESPStepperMotorServer_MotionController_h\n\n#include <Arduino.h>\n#include <ESPStepperMotorServer.h>\n#include <ESPStepperMotorServer_Logger.h>\n#include <ESP_FlexyStepper.h>\n\nclass ESPStepperMotorServer;\n\nclass ESPStepperMotorServer_MotionController\n{\npublic:\n  ESPStepperMotorServer_MotionController(ESPStepperMotorServer *serverRef);\n  static void processMotionUpdates(void *parameter);\n  void start();\n  void stop();\n\nprivate:\n  TaskHandle_t xHandle = NULL;\n  ESPStepperMotorServer *serverRef;\n};\n\n#endif"
  },
  {
    "path": "src/ESPStepperMotorServer_PositionSwitch.cpp",
    "content": "//      *******************************************************************\n//      *                                                                 *\n//      * ESP8266 and ESP32 Stepper Motor Server  - Position Switch class *\n//      *            Copyright (c) Paul Kerspe, 2019                      *\n//      *                                                                 *\n//      *******************************************************************\n//\n// MIT License\n//\n// Copyright (c) 2019 Paul Kerspe\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 furnished\n// to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// 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 THE\n// SOFTWARE.\n//\n#include <ESPStepperMotorServer_PositionSwitch.h>\n\nint _stepperIndex; // can be -1 for emergency stop switches only\nbyte _ioPinNumber = 255;\nbyte _switchType; //bit mask for active state and the general type of switch\nString _positionName = \"\";\nlong _switchPosition = -1;\nESPStepperMotorServer_Logger _logger = ESPStepperMotorServer_Logger((String)\"ESPStepperMotorServer_PositionSwitch\");\n\nESPStepperMotorServer_PositionSwitch::ESPStepperMotorServer_PositionSwitch()\n{\n}\n\nESPStepperMotorServer_PositionSwitch::~ESPStepperMotorServer_PositionSwitch()\n{\n    this->clearMacroActions();\n}\n\nESPStepperMotorServer_PositionSwitch::ESPStepperMotorServer_PositionSwitch(byte ioPin, int stepperIndex, byte switchType, String name, long switchPosition)\n{\n    this->_ioPinNumber = ioPin;\n    this->_stepperIndex = stepperIndex;\n    this->_switchType = switchType; //this is a bit mask representing the active state (bit 1 and 2) and the general type (homing/limit/position or emergency stop switch) in one byte\n    this->_positionName = name;\n    this->_switchPosition = switchPosition;\n}\n\nvoid ESPStepperMotorServer_PositionSwitch::setId(byte id)\n{\n    this->_switchIndex = id;\n}\n\n/**\n * the unique ID of this switch\n * NOTE: This ID also matches the array index of the configuration in the allConfiguredSwitches array in the Configuration class\n */\nbyte ESPStepperMotorServer_PositionSwitch::getId()\n{\n    return this->_switchIndex;\n}\n\nint ESPStepperMotorServer_PositionSwitch::getStepperIndex(void)\n{\n    return this->_stepperIndex;\n}\n\nbyte ESPStepperMotorServer_PositionSwitch::getIoPinNumber(void)\n{\n    return this->_ioPinNumber;\n}\n\nbyte ESPStepperMotorServer_PositionSwitch::getSwitchType(void)\n{\n    return this->_switchType;\n}\n\nString ESPStepperMotorServer_PositionSwitch::getPositionName(void)\n{\n    return this->_positionName;\n}\nvoid ESPStepperMotorServer_PositionSwitch::setPositionName(String name)\n{\n    this->_positionName = name;\n}\n\nlong ESPStepperMotorServer_PositionSwitch::getSwitchPosition(void)\n{\n    return this->_switchPosition;\n}\nvoid ESPStepperMotorServer_PositionSwitch::setSwitchPosition(long position)\n{\n    this->_switchPosition = position;\n}\n\nbool ESPStepperMotorServer_PositionSwitch::isActiveHigh()\n{\n    return this->isTypeBitSet(SWITCHTYPE_STATE_ACTIVE_HIGH_BIT);\n}\n\nbool ESPStepperMotorServer_PositionSwitch::isEmergencySwitch()\n{\n    return this->isTypeBitSet(SWITCHTYPE_EMERGENCY_STOP_SWITCH_BIT);\n}\n\nbool ESPStepperMotorServer_PositionSwitch::isLimitSwitch()\n{\n    return (this->isTypeBitSet(SWITCHTYPE_LIMITSWITCH_POS_BEGIN_BIT) || this->isTypeBitSet(SWITCHTYPE_LIMITSWITCH_POS_END_BIT) || this->isTypeBitSet(SWITCHTYPE_LIMITSWITCH_COMBINED_BEGIN_END_BIT));\n}\n\nbool ESPStepperMotorServer_PositionSwitch::isTypeBitSet(byte bitToCheck)\n{\n    return this->_switchType & (1 << (bitToCheck - 1));\n}\n\nvoid ESPStepperMotorServer_PositionSwitch::addMacroAction(ESPStepperMotorServer_MacroAction *macroAction) {\n    this->_macroActions.push_back(macroAction);\n}\n\nstd::vector<ESPStepperMotorServer_MacroAction*> ESPStepperMotorServer_PositionSwitch::getMacroActions() {\n    return this->_macroActions;\n}\n\nvoid ESPStepperMotorServer_PositionSwitch::clearMacroActions() {\n    for(ESPStepperMotorServer_MacroAction *macroAction : this->_macroActions) {\n        delete(macroAction);\n    }\n    this->_macroActions.clear();\n}\n\nbool ESPStepperMotorServer_PositionSwitch::hasMacroActions(){\n    return (this->_macroActions.size() > 0);\n}\n\nint ESPStepperMotorServer_PositionSwitch::serializeMacroActionsToJsonArray(JsonArray macroActionsJsonArray){\n\n    for(ESPStepperMotorServer_MacroAction *macroAction : this->_macroActions) {\n        macroAction->addSerializedInstanceToJsonArray(macroActionsJsonArray);\n    }\n    return this->_macroActions.size();\n}\n"
  },
  {
    "path": "src/ESPStepperMotorServer_PositionSwitch.h",
    "content": "\n#ifndef ESPStepperMotorServer_PositionSwitch_h\n#define ESPStepperMotorServer_PositionSwitch_h\n\n#include <Arduino.h>\n#include <ArduinoJson.h>\n#include <ESPStepperMotorServer_Logger.h>\n#include <ESPStepperMotorServer_MacroAction.h>\n#include <vector>\n\n#define SWITCHTYPE_STATE_ACTIVE_HIGH_BIT 1\n#define SWITCHTYPE_STATE_ACTIVE_LOW_BIT 2\n#define SWITCHTYPE_LIMITSWITCH_POS_BEGIN_BIT 3\n#define SWITCHTYPE_LIMITSWITCH_POS_END_BIT 4\n#define SWITCHTYPE_POSITION_SWITCH_BIT 5\n#define SWITCHTYPE_EMERGENCY_STOP_SWITCH_BIT 6\n#define SWITCHTYPE_LIMITSWITCH_COMBINED_BEGIN_END_BIT 7\n\n//size calculated using https://arduinojson.org/v6/assistant/\n#define RESERVED_JSON_SIZE_ESPStepperMotorServer_PositionSwitch 170\n\nclass ESPStepperMotorServer_MacroAction;\n\nclass ESPStepperMotorServer_PositionSwitch\n{\n    friend class ESPStepperMotorServer;\npublic:\n    ESPStepperMotorServer_PositionSwitch();\n    ~ESPStepperMotorServer_PositionSwitch();\n    ESPStepperMotorServer_PositionSwitch(byte ioPin, int stepperIndex, byte switchType, String name = \"\", long switchPosition = 0);\n\n    /**\n     * setter to set the id of this switch.\n     * Only use this if you know what you are doing\n     */\n    void setId(byte id);\n\n    /**\n      * get the id of the switch\n      */\n    byte getId();\n\n    int getStepperIndex(void);\n\n    byte getIoPinNumber(void);\n    \n    /**\n     * return the type of this switch if set.\n     * It indicates whether the switch as limit, position or emergency switch\n     * See constants \n     *  SWITCHTYPE_LIMITSWITCH_POS_BEGIN_BIT\n     *  SWITCHTYPE_LIMITSWITCH_POS_END_BIT\n     *  SWITCHTYPE_LIMITSWITCH_COMBINED_BEGIN_END_BIT\n     *  SWITCHTYPE_POSITION_SWITCH_BIT\n     *  SWITCHTYPE_EMERGENCY_STOP_SWITCH_BIT\n     */\n    byte getSwitchType(void);\n\n    String getPositionName(void);\n    void setPositionName(String name);\n\n    bool isActiveHigh();\n    bool isEmergencySwitch();\n    bool isLimitSwitch();\n    bool isTypeBitSet(byte bitToCheck);\n\n    long getSwitchPosition(void);\n    void setSwitchPosition(long position);\n\n    void addMacroAction(ESPStepperMotorServer_MacroAction *macroAction);\n    std::vector<ESPStepperMotorServer_MacroAction*> getMacroActions();\n    bool hasMacroActions(void);\n    void clearMacroActions(void);\n    int serializeMacroActionsToJsonArray(JsonArray macroActionsJsonArray);\n\nprivate:\n    byte _stepperIndex;\n    byte _switchIndex;\n    byte _ioPinNumber = 255;\n    byte _switchType = 0; //this is a bit mask representing the active state (bit 1 and 2) and the general type (homing/limit/position or emergency stop switch) in one byte\n    String _positionName;\n    long _switchPosition;\n    ESPStepperMotorServer_Logger _logger;\n    std::vector<ESPStepperMotorServer_MacroAction*> _macroActions;\n};\n#endif\n"
  },
  {
    "path": "src/ESPStepperMotorServer_RestAPI.cpp",
    "content": "\n//      *********************************************************\n//      *                                                       *\n//      *     ESP8266 and ESP32 Stepper Motor Server Rest API   *\n//      *                                                       *\n//      *            Copyright (c) Paul Kerspe, 2019            *\n//      *                                                       *\n//      **********************************************************\n\n// MIT License\n//\n// Copyright (c) 2019 Paul Kerspe\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 furnished\n// to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// 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 THE\n// SOFTWARE.\n\n#include \"ESPStepperMotorServer_RestAPI.h\"\n\nESPStepperMotorServer_Logger *logger;\nESPStepperMotorServer *_stepperMotorServer;\n// ---------------------------------------------------------------------------------\n//                                  Setup functions\n// ---------------------------------------------------------------------------------\n\n//\n// constructor for the rest endpoint provider\n//\nESPStepperMotorServer_RestAPI::ESPStepperMotorServer_RestAPI(ESPStepperMotorServer *stepperMotorServer)\n{\n    this->_stepperMotorServer = stepperMotorServer;\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n    ESPStepperMotorServer_Logger::logDebug(\"ESPStepperMotorServer_RestAPI instance created\");\n#endif\n}\n\n/**\n * this function is used to register all handlers for the rest API endpoints of the ESPStepperMotorServer\n */\nvoid ESPStepperMotorServer_RestAPI::registerRestEndpoints(AsyncWebServer *httpServer)\n{\n    // GET /api/status\n    // get the current stepper server status report including the following information: version string of the server, wifi information (wifi mode, IP address), spiffs information (total space and free space)\n    httpServer->on(\"/api/status\", HTTP_GET, [this](AsyncWebServerRequest *request)\n                   {\n                       this->logDebugRequestUrl(request);\n\n                       String output = String(\"\");\n                       this->_stepperMotorServer->getServerStatusAsJsonString(output); //populate string with json\n                       AsyncWebServerResponse *response = request->beginResponse(200, \"application/json\", output);\n                       request->send(response);\n                   });\n\n    // GET /api/steppers/position?id=<id>\n    httpServer->on(\"/api/steppers/position\", HTTP_GET, [this](AsyncWebServerRequest *request)\n                   {\n                       this->logDebugRequestUrl(request);\n\n                       if (request->hasParam(\"id\"))\n                       {\n                           const byte stepperIndex = request->getParam(\"id\")->value().toInt();\n                           ESPStepperMotorServer_StepperConfiguration *stepper = this->_stepperMotorServer->getCurrentServerConfiguration()->getStepperConfiguration(stepperIndex);\n                           if (stepperIndex < 0 || stepperIndex >= ESPServerMaxSteppers || stepper == NULL)\n                           {\n                               request->send(404, \"application/json\", \"{\\\"error\\\": \\\"Invalid stepper id\\\"}\");\n                               return;\n                           }\n                           String output;\n                           const int docSize = 60;\n\n                           StaticJsonDocument<docSize> doc;\n                           JsonObject root = doc.to<JsonObject>();\n\n                           root[\"mm\"] = stepper->getFlexyStepper()->getCurrentPositionInMillimeters();\n                           root[\"revs\"] = stepper->getFlexyStepper()->getCurrentPositionInRevolutions();\n                           root[\"steps\"] = stepper->getFlexyStepper()->getCurrentPositionInSteps();\n                           serializeJson(root, output);\n                           AsyncWebServerResponse *response = request->beginResponse(200, \"application/json\", output);\n                           request->send(response);\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n                           if (ESPStepperMotorServer_Logger::isDebugEnabled())\n                           {\n                               ESPStepperMotorServer_Logger::logDebugf(\"ArduinoJSON document size uses %i bytes from alocated %i bytes\", doc.memoryUsage(), docSize);\n                           }\n#endif\n                       }\n                       else\n                       {\n                           request->send(400, \"application/json\", \"{\\\"error\\\": \\\"Missing id paramter\\\"}\");\n                           return;\n                       }\n                   });\n\n    // POST /api/steppers/returnhome\n    // endpoint trigger movement for stepper until home is reached (indicated by kill switch)\n    // see documentation of handler function for details\n    httpServer->on(\"/api/steppers/returnhome\", HTTP_POST, [this](AsyncWebServerRequest *request)\n                   {\n                       this->logDebugRequestUrl(request);\n                       this->handleHomingRequest(request);\n                   });\n\n    // POST /api/steppers/moveby\n    // endpoint to set a new RELATIVE target position for the stepper motor in either mm, revs or steps\n    // post parameters: id, unit, value\n    // optional parameters: speed, accel, decel\n    httpServer->on(\"/api/steppers/moveby\", HTTP_POST, [this](AsyncWebServerRequest *request)\n                   {\n                       this->logDebugRequestUrl(request);\n\n                       if (request->hasParam(\"id\"))\n                       {\n                           int stepperIndex = request->getParam(\"id\")->value().toInt();\n                           ESPStepperMotorServer_StepperConfiguration *stepper = this->_stepperMotorServer->getCurrentServerConfiguration()->getStepperConfiguration(stepperIndex);\n                           if (stepper == NULL)\n                           {\n                               request->send(404, \"application/json\", \"{\\\"error\\\": \\\"No stepper configuration found for given id\\\"}\");\n                               return;\n                           }\n                           if (request->hasParam(\"speed\"))\n                           {\n                               float speed = request->getParam(\"speed\")->value().toFloat();\n                               if (speed > 0)\n                               {\n                                   stepper->getFlexyStepper()->setSpeedInStepsPerSecond(speed);\n                               }\n                           }\n                           if (request->hasParam(\"accel\"))\n                           {\n                               float accel = request->getParam(\"accel\")->value().toFloat();\n                               if (accel > 0)\n                               {\n                                   stepper->getFlexyStepper()->setAccelerationInStepsPerSecondPerSecond(accel);\n                                   //in case deceleration is not explicitly given, we just use the same value\n                                   stepper->getFlexyStepper()->setDecelerationInStepsPerSecondPerSecond(accel);\n                               }\n                           }\n                           if (request->hasParam(\"decel\"))\n                           {\n                               float decel = request->getParam(\"decel\")->value().toFloat();\n                               if (decel > 0)\n                               {\n                                   stepper->getFlexyStepper()->setDecelerationInStepsPerSecondPerSecond(decel);\n                               }\n                           }\n\n                           if (request->hasParam(\"value\") && request->hasParam(\"unit\"))\n                           {\n                               String unit = request->getParam(\"unit\")->value();\n                               float distance = request->getParam(\"value\")->value().toFloat();\n                               if (unit == \"mm\")\n                               {\n                                   stepper->getFlexyStepper()->setTargetPositionRelativeInMillimeters(distance);\n                               }\n                               else if (unit == \"revs\")\n                               {\n                                   stepper->getFlexyStepper()->setTargetPositionRelativeInRevolutions(distance);\n                               }\n                               else if (unit == \"steps\")\n                               {\n                                   stepper->getFlexyStepper()->setTargetPositionRelativeInSteps(distance);\n                               }\n                               else\n                               {\n                                   request->send(400, \"application/json\", \"{\\\"error\\\": \\\"Unit must be one of: revs, steps, mm\\\"}\");\n                                   return;\n                               }\n                               request->send(204);\n                               return;\n                           }\n                       }\n                       request->send(400, \"application/json\", \"{\\\"error\\\": \\\"Missing id paramter\\\"}\");\n                   });\n\n    // POST /api/steppers/position\n    // endpoint to set a new absolute target position for the stepper motor in either mm, revs or steps\n    // post parameters: id, unit, value\n    // optional parameters: speed, accel, decel\n    httpServer->on(\"/api/steppers/position\", HTTP_POST, [this](AsyncWebServerRequest *request)\n                   {\n                       this->logDebugRequestUrl(request);\n\n                       if (request->hasParam(\"id\", true))\n                       {\n                           int stepperIndex = request->getParam(\"id\", true)->value().toInt();\n                           ESPStepperMotorServer_StepperConfiguration *stepper = this->_stepperMotorServer->getCurrentServerConfiguration()->getStepperConfiguration(stepperIndex);\n                           if (stepper == NULL)\n                           {\n                               request->send(404, \"application/json\", \"{\\\"error\\\": \\\"No stepper configuration found for given id\\\"}\");\n                               return;\n                           }\n\n                           if (request->hasParam(\"speed\"))\n                           {\n                               float speed = request->getParam(\"speed\")->value().toFloat();\n                               if (speed > 0)\n                               {\n                                   stepper->getFlexyStepper()->setSpeedInStepsPerSecond(speed);\n                               }\n                           }\n                           if (request->hasParam(\"accel\"))\n                           {\n                               float accel = request->getParam(\"accel\")->value().toFloat();\n                               if (accel > 0)\n                               {\n                                   stepper->getFlexyStepper()->setAccelerationInStepsPerSecondPerSecond(accel);\n                                   //in case deceleration is not explicitly given, we just use the same value\n                                   stepper->getFlexyStepper()->setDecelerationInStepsPerSecondPerSecond(accel);\n                               }\n                           }\n                           if (request->hasParam(\"decel\"))\n                           {\n                               float decel = request->getParam(\"decel\")->value().toFloat();\n                               if (decel > 0)\n                               {\n                                   stepper->getFlexyStepper()->setDecelerationInStepsPerSecondPerSecond(decel);\n                               }\n                           }\n\n                           if (request->hasParam(\"value\", true) && request->hasParam(\"unit\", true))\n                           {\n                               String unit = request->getParam(\"unit\", true)->value();\n                               float position = request->getParam(\"value\", true)->value().toFloat();\n                               if (unit == \"mm\")\n                               {\n                                   stepper->getFlexyStepper()->setTargetPositionInMillimeters(position);\n                               }\n                               else if (unit == \"revs\")\n                               {\n                                   stepper->getFlexyStepper()->setTargetPositionInRevolutions(position);\n                               }\n                               else if (unit == \"steps\")\n                               {\n                                   stepper->getFlexyStepper()->setTargetPositionInSteps(position);\n                               }\n                               else\n                               {\n                                   request->send(400);\n                                   return;\n                               }\n\n                               request->send(204);\n                               return;\n                           }\n                       }\n                       request->send(400);\n                   });\n\n    // GET /api/steppers/stop?id=<id>\n    // endpoint to send a stop signal to the selected stepper\n    httpServer->on(\"/api/steppers/stop\", HTTP_GET, [this](AsyncWebServerRequest *request)\n                   {\n                       this->logDebugRequestUrl(request);\n\n                       if (request->hasParam(\"id\", false))\n                       {\n                           int stepperIndex = request->getParam(\"id\", false)->value().toInt();\n                           ESPStepperMotorServer_StepperConfiguration *stepper = this->_stepperMotorServer->getCurrentServerConfiguration()->getStepperConfiguration(stepperIndex);\n                           if (stepper == NULL)\n                           {\n                               request->send(404);\n                               return;\n                           }\n                           else\n                           {\n                               stepper->getFlexyStepper()->setTargetPositionToStop();\n                               request->send(204);\n                               return;\n                           }\n                       }\n                       else\n                       {\n                           request->send(400, \"application/json\", \"{\\\"error\\\": \\\"Missing id paramter\\\"}\");\n                           return;\n                       }\n                   });\n\n    // GET /api/emergencystop/trigger\n    // endpoint to send a emergencystop signal for all steppers\n    httpServer->on(\"/api/emergencystop/trigger\", HTTP_GET, [this](AsyncWebServerRequest *request)\n                   {\n                       this->logDebugRequestUrl(request);\n                       this->_stepperMotorServer->performEmergencyStop();\n                       request->send(204);\n                       return;\n                   });\n\n    // GET /api/emergencystop/revoke\n    // endpoint to revoke the emergencystop signal for all steppers\n    httpServer->on(\"/api/emergencystop/revoke\", HTTP_GET, [this](AsyncWebServerRequest *request)\n                   {\n                       this->logDebugRequestUrl(request);\n                       this->_stepperMotorServer->revokeEmergencyStop();\n                       request->send(204);\n                       return;\n                   });\n\n    // GET /api/steppers\n    // GET /api/steppers?id=<id>\n    // endpoint to list all configured steppers or a specific one if \"id\" query parameter is given\n    httpServer->on(\"/api/steppers\", HTTP_GET, [this](AsyncWebServerRequest *request)\n                   {\n                       this->logDebugRequestUrl(request);\n\n                       String output;\n\n                       if (request->hasParam(\"id\"))\n                       {\n                           int stepperIndex = request->getParam(\"id\")->value().toInt();\n                           if (stepperIndex < 0 || stepperIndex >= ESPServerMaxSteppers)\n                           {\n                               request->send(404);\n                               return;\n                           }\n\n                           StaticJsonDocument<400> doc;\n                           JsonObject root = doc.to<JsonObject>();\n                           JsonObject stepperDetails = root.createNestedObject(\"stepper\");\n                           this->populateStepperDetailsToJsonObject(stepperDetails, this->_stepperMotorServer->getCurrentServerConfiguration()->getStepperConfiguration(stepperIndex), stepperIndex);\n                           serializeJson(root, output);\n                       }\n                       else\n                       {\n                           const int docSize = 420 * ESPServerMaxSteppers;\n                           StaticJsonDocument<docSize> doc;\n                           JsonObject root = doc.to<JsonObject>();\n                           JsonArray steppers = root.createNestedArray(\"steppers\");\n                           for (int i = 0; i < ESPServerMaxSteppers; i++)\n                           {\n                               JsonObject stepperDetails = steppers.createNestedObject();\n                               this->populateStepperDetailsToJsonObject(stepperDetails, this->_stepperMotorServer->getCurrentServerConfiguration()->getStepperConfiguration(i), i);\n                           }\n                           serializeJson(root, output);\n                           ESPStepperMotorServer_Logger::logDebugf(\"ArduinoJSON document size uses %i bytes from alocated %i bytes\\n\", doc.memoryUsage(), docSize);\n                       }\n\n                       AsyncWebServerResponse *response = request->beginResponse(200, \"application/json\", output);\n                       request->send(response);\n                   });\n\n    // DELETE /api/steppers?id=<id>\n    // delete an existing stepper configuration entry\n    httpServer->on(\"/api/steppers\", HTTP_DELETE, [this](AsyncWebServerRequest *request)\n                   {\n                       this->logDebugRequestUrl(request);\n                       this->handleDeleteStepperRequest(request, true);\n                   });\n\n    // POST /api/steppers\n    // add a new stepper configuration entry\n    httpServer->on(\n        \"/api/steppers\", HTTP_POST, [](AsyncWebServerRequest *request) {}, NULL, [this](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total)\n        {\n            this->logDebugRequestUrl(request);\n            this->handlePostStepperRequest(request, data, len, index, total, -1);\n        });\n\n    // PUT /api/steppers?id=<id>\n    // upate an existing stepper configuration entry\n    httpServer->on(\n        \"/api/steppers\", HTTP_PUT, [](AsyncWebServerRequest *request) {}, NULL, [this](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total)\n        {\n            this->logDebugRequestUrl(request);\n            int stepperIndex = request->getParam(\"id\")->value().toInt();\n            this->handlePostStepperRequest(request, data, len, index, total, stepperIndex);\n        });\n\n    // GET /api/switches/status\n    // GET /api/switches/status?id=<id>\n    // get the current switch status (active, inactive) of either one specific switch or all switches (returned as a bit mask in MSB order)\n    httpServer->on(\"/api/switches/status\", HTTP_GET, [this](AsyncWebServerRequest *request)\n                   {\n                       this->logDebugRequestUrl(request);\n                       if (request->hasParam(\"id\"))\n                       {\n                           int switchIndex = request->getParam(\"id\")->value().toInt();\n                           if (this->_stepperMotorServer->getCurrentServerConfiguration()->getSwitch(switchIndex) == NULL)\n                           {\n                               request->send(404);\n                               return;\n                           }\n                           request->send(200, \"application/json\", (String) \"{ \\\"status\\\": \\\"\" + this->_stepperMotorServer->getPositionSwitchStatus(switchIndex) + (String) \"\\\"}\");\n                       }\n                       else\n                       {\n                           String output = \"{ \\\"status\\\": \\\"\";\n                           for (int i = ESPServerSwitchStatusRegisterCount - 1; i >= 0; i--)\n                           {\n                               this->_stepperMotorServer->getFormattedPositionSwitchStatusRegister(i, output);\n                           }\n                           request->send(200, \"application/json\", output + \"\\\"}\");\n                       }\n                   });\n\n    // GET /api/switches\n    // GET /api/switches?id=<id>\n    // endpoint to list all position switch configurations or a specific configuration if the \"id\" query parameter is given\n    httpServer->on(\"/api/switches\", HTTP_GET, [this](AsyncWebServerRequest *request)\n                   {\n                       this->logDebugRequestUrl(request);\n                       String output;\n                       const int switchObjectSize = JSON_OBJECT_SIZE(8) + 80; //80 is for Strings names\n\n                       if (request->hasParam(\"id\"))\n                       {\n                           int switchIndex = request->getParam(\"id\")->value().toInt();\n                           if (this->_stepperMotorServer->getCurrentServerConfiguration()->getSwitch(switchIndex) == NULL)\n                           {\n                               request->send(404, \"application/json\", \"{\\\"error\\\": \\\"No switch found for the given id\\\"}\");\n                               return;\n                           }\n\n                           StaticJsonDocument<switchObjectSize> doc;\n                           JsonObject root = doc.to<JsonObject>();\n                           this->populateSwitchDetailsToJsonObject(root, this->_stepperMotorServer->getCurrentServerConfiguration()->getSwitch(switchIndex), switchIndex);\n                           serializeJson(root, output);\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n                           if (ESPStepperMotorServer_Logger::isDebugEnabled())\n                           {\n                               ESPStepperMotorServer_Logger::logDebugf(\"ArduinoJSON document size uses %i bytes from alocated %i bytes\\n\", doc.memoryUsage(), switchObjectSize);\n                           }\n#endif\n                       }\n                       else\n                       {\n                           const int docSize = switchObjectSize * ESPServerMaxSwitches;\n                           StaticJsonDocument<docSize> doc;\n                           JsonObject root = doc.to<JsonObject>();\n                           JsonArray switches = root.createNestedArray(\"switches\");\n\n                           //TODO instead of doing a loop, implement function getAllConfiguredSwitches()\n                           for (int i = 0; i < ESPServerMaxSwitches; i++)\n                           {\n                               if (this->_stepperMotorServer->getCurrentServerConfiguration()->getSwitch(i))\n                               {\n                                   JsonObject switchDetails = switches.createNestedObject();\n                                   this->populateSwitchDetailsToJsonObject(switchDetails, this->_stepperMotorServer->getCurrentServerConfiguration()->getSwitch(i), i);\n                               }\n                           }\n                           serializeJson(root, output);\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n                           if (ESPStepperMotorServer_Logger::isDebugEnabled())\n                           {\n                               ESPStepperMotorServer_Logger::logDebugf(\"ArduinoJSON document size uses %i bytes from alocated %i bytes\\n\", doc.memoryUsage(), docSize);\n                           }\n#endif\n                       }\n\n                       AsyncWebServerResponse *response = request->beginResponse(200, \"application/json\", output);\n                       request->send(response);\n                   });\n\n    // POST /api/switches\n    // endpoint to add a new switch configuration\n    httpServer->on(\n        \"/api/switches\", HTTP_POST, [](AsyncWebServerRequest *request) {}, NULL, [this](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total)\n        {\n            this->logDebugRequestUrl(request);\n            this->handlePostSwitchRequest(request, data, len, index, total);\n        });\n\n    // PUT /api/switches?id=<id>\n    // endpoint to update an existing switch configuration\n    httpServer->on(\n        \"/api/switches\", HTTP_PUT, [](AsyncWebServerRequest *request) {}, NULL, [this](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total)\n        {\n            this->logDebugRequestUrl(request);\n            int switchIndex = request->getParam(\"id\")->value().toInt();\n            this->handlePostSwitchRequest(request, data, len, index, total, switchIndex);\n        });\n\n    // DELETE /api/switches?id=<id>\n    // delete a specific switch configuration\n    httpServer->on(\"/api/switches\", HTTP_DELETE, [this](AsyncWebServerRequest *request)\n                   {\n                       this->logDebugRequestUrl(request);\n                       this->handleDeleteSwitchRequest(request, true);\n                   });\n\n    // GET /api/encoders\n    // GET /api/encoders?id=<id>\n    // endpoint to list all rotary encoder configurations or a specific configuration if the \"id\" query parameter is given\n    httpServer->on(\"/api/encoders\", HTTP_GET, [this](AsyncWebServerRequest *request)\n                   {\n                       this->logDebugRequestUrl(request);\n                       String output;\n                       const int rotaryEncoderObjectSize = JSON_OBJECT_SIZE(8) + 80; //80 is for Strings names\n\n                       if (request->hasParam(\"id\"))\n                       {\n                           int rotaryEncoderIndex = request->getParam(\"id\")->value().toInt();\n                           if (this->_stepperMotorServer->getCurrentServerConfiguration()->getRotaryEncoder(rotaryEncoderIndex) == NULL)\n                           {\n                               request->send(404);\n                               return;\n                           }\n\n                           StaticJsonDocument<rotaryEncoderObjectSize> doc;\n                           JsonObject root = doc.to<JsonObject>();\n                           this->populateRotaryEncoderDetailsToJsonObject(root, this->_stepperMotorServer->getCurrentServerConfiguration()->getRotaryEncoder(rotaryEncoderIndex), rotaryEncoderIndex);\n                           serializeJson(root, output);\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n                           if (ESPStepperMotorServer_Logger::isDebugEnabled())\n                           {\n                               ESPStepperMotorServer_Logger::logDebugf(\"ArduinoJSON document size uses %i bytes from alocated %i bytes\\n\", doc.memoryUsage(), rotaryEncoderObjectSize);\n                           }\n#endif\n                       }\n                       else\n                       {\n                           const int docSize = rotaryEncoderObjectSize * ESPServerMaxRotaryEncoders;\n                           StaticJsonDocument<docSize> doc;\n                           JsonObject root = doc.to<JsonObject>();\n                           JsonArray encoders = root.createNestedArray(\"rotaryEncoders\");\n                           for (int i = 0; i < ESPServerMaxRotaryEncoders; i++)\n                           {\n                               if (this->_stepperMotorServer->getCurrentServerConfiguration()->getRotaryEncoder(i) != NULL)\n                               {\n                                   JsonObject encoderDetails = encoders.createNestedObject();\n                                   this->populateRotaryEncoderDetailsToJsonObject(encoderDetails, this->_stepperMotorServer->getCurrentServerConfiguration()->getRotaryEncoder(i), i);\n                               }\n                           }\n                           serializeJson(root, output);\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n                           if (ESPStepperMotorServer_Logger::isDebugEnabled())\n                           {\n                               ESPStepperMotorServer_Logger::logDebugf(\"ArduinoJSON document size uses %i bytes from alocated %i bytes\\n\", doc.memoryUsage(), docSize);\n                           }\n#endif\n                       }\n\n                       AsyncWebServerResponse *response = request->beginResponse(200, \"application/json\", output);\n                       request->send(response);\n                   });\n\n    // POST /api/encoders\n    // endpoint to add a new rotary encoder configuration\n    httpServer->on(\n        \"/api/encoders\", HTTP_POST, [](AsyncWebServerRequest *request) {}, NULL, [this](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total)\n        {\n            this->logDebugRequestUrl(request);\n            this->handlePostRotaryEncoderRequest(request, data, len, index, total);\n        });\n\n    // PUT /api/encoders?id=<id>\n    // endpoint to update an existing rotary encoder configuration (will effectively delete the old configuraiton and write a new one at the same position)\n    httpServer->on(\n        \"/api/encoders\", HTTP_PUT, [](AsyncWebServerRequest *request) {}, NULL, [this](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total)\n        {\n            this->logDebugRequestUrl(request);\n            int deleteRC = this->handleDeleteRotaryEncoderRequest(request, false);\n            if (deleteRC == 204)\n            {\n                int encoderIndex = request->getParam(\"id\")->value().toInt();\n                this->handlePostRotaryEncoderRequest(request, data, len, index, total, encoderIndex);\n            }\n            else\n            {\n                request->send(deleteRC, \"application/json\", \"{\\\"error\\\": \\\"Failed to update rotary encoder\\\"}\");\n            }\n        });\n\n    // DELETE /api/encoders?id=<id>\n    // delete a specific rotary encoder configuration\n    httpServer->on(\"/api/encoders\", HTTP_DELETE, [this](AsyncWebServerRequest *request)\n                   {\n                       this->logDebugRequestUrl(request);\n                       this->handleDeleteRotaryEncoderRequest(request, true);\n                   });\n\n    // GET /api/config/save\n    // endpoint to save the current IN MEMORY configuration with all settings to SPIFFS and therefore persist it to survive a reboot\n    httpServer->on(\"/api/config/save\", HTTP_GET, [this](AsyncWebServerRequest *request)\n                   {\n                       this->logDebugRequestUrl(request);\n                       ESPStepperMotorServer_Configuration *config = this->_stepperMotorServer->getCurrentServerConfiguration();\n                       bool saved = config->saveCurrentConfiguationToSpiffs();\n                       if (saved)\n                       {\n                           request->send(204);\n                       }\n                       else\n                       {\n                           request->send(500, \"application/json\", \"{\\\"error\\\": \\\"failed to save configuraiton to SPIFFS\\\"}\");\n                       }\n                   });\n\n    // GET /api/config\n    // endpoint to list the current IN MEMORY configuration with all settings (passwords will be hidden though)\n    httpServer->on(\"/api/config\", HTTP_GET, [this](AsyncWebServerRequest *request)\n                   {\n                       this->logDebugRequestUrl(request);\n                       ESPStepperMotorServer_Configuration *config = this->_stepperMotorServer->getCurrentServerConfiguration();\n                       request->send(200, \"application/json\", config->getCurrentConfigurationAsJSONString(true, false));\n                   });\n\n    // GET /api/outputs\n    // GET /api/outputs?id=<id>\n    // GET /api/outputs/status?id=<id>\n    // PUT /api/outputs/status?id=<id>\n    // POST /api/outputs\n    // PUT /api/outputs?id=<id>\n    // DELETE /api/outputs?id=<id>\n}\n\n/**\n * handler for the REST endpoint to perform a homing run.\n * This will call the goToLimitAndSetAsHome() function in the flexyStepper instance.\n * It will require that a home/limit switch is configured for this stepper, otherwise stepper will \"never\" (an absolute limit of 2000000000 steps is configured by default to somewhat limit the movement) come to a halt \n * Required POST parameters: \n *      id (id of the stepper motor to pefrom the homing command for)\n *      speed: the speed in steps per second to perform the homing command with\n * Optional POST parameters: \n *      switchId:  the id of the configured switch to use as limit switch, if parameter is omited the position switch from the configurtration of the stepper motor will be used, if none is configured, operation will fail)\n *      accel: the acceleration for the homing procdeure in steps/sec^2, if ommitted the previously defined acceleration in the flexy stepper instance will be used\n *      maxSteps: this parameter defines the maximum number of steps to perform before cancelling the homing procedure. This is kind of a safeguard to prevent endless spinning of the stepper motor. Defaults to 2000000000 steps\n */\nvoid ESPStepperMotorServer_RestAPI::handleHomingRequest(AsyncWebServerRequest *request)\n{\n    this->logDebugRequestUrl(request);\n\n    if (!request->hasParam(\"id\"))\n    {\n        request->send(400, \"application/json\", \"{\\\"error\\\": \\\"Missing stepper id parameter\\\"}\");\n        return;\n    }\n\n    int stepperIndex = request->getParam(\"id\")->value().toInt();\n    ESPStepperMotorServer_StepperConfiguration *stepperConfiguration = this->_stepperMotorServer->getCurrentServerConfiguration()->getStepperConfiguration(stepperIndex);\n    if (stepperConfiguration == NULL)\n    {\n        request->send(404, \"application/json\", \"{\\\"error\\\": \\\"No stepper configuration found for given stepper id\\\"}\");\n        return;\n    }\n\n    float speedInStepsPerSecond = 0;\n    if (request->hasParam(\"speed\"))\n    {\n        speedInStepsPerSecond = request->getParam(\"speed\")->value().toFloat();\n        if (speedInStepsPerSecond <= 0)\n        {\n            request->send(400, \"application/json\", \"{\\\"error\\\": \\\"Value for homing speed (in steps/second) must be larger than 0\\\"}\");\n            return;\n        }\n    }\n    else\n    {\n        request->send(400, \"application/json\", \"{\\\"error\\\": \\\"Missing parameter for speed (in steps/second)\\\"}\");\n        return;\n    }\n\n    float accelInStepPerSecondSquare = 0;\n    if (request->hasParam(\"accel\"))\n    {\n        accelInStepPerSecondSquare = request->getParam(\"accel\")->value().toFloat();\n        if (accelInStepPerSecondSquare <= 0)\n        {\n            request->send(400, \"application/json\", \"{\\\"error\\\": \\\"Acceleration value must be larger than 0\\\"}\");\n            return;\n        }\n    }\n\n    unsigned int maxSteps = 2000000000;\n    if (request->hasParam(\"maxSteps\"))\n    {\n        maxSteps = request->getParam(\"maxSteps\")->value().toInt();\n        if (maxSteps < 1)\n        {\n            request->send(400, \"application/json\", \"{\\\"error\\\": \\\"Max number of steps during homing must be larger than 0\\\"}\");\n            return;\n        }\n    }\n\n    ESPStepperMotorServer_PositionSwitch *switchConfig;\n    signed char directionTowardHome = 1;\n\n    signed char forcedDirectionTowardHome = 0;\n    if (request->hasParam(\"direction\"))\n    {\n        forcedDirectionTowardHome = request->getParam(\"direction\")->value().toInt();\n        if (forcedDirectionTowardHome != 1 && forcedDirectionTowardHome != -1)\n        {\n            request->send(400, \"application/json\", \"{\\\"error\\\": \\\"Invalid direction value given. Allowed values are 1 and -1\\\"}\");\n            return;\n        }\n    }\n\n    byte gpioPinForSwitch = 0;\n    //TOOD: implement proper ISR handling for custom switch parameter if not linked to existing stepper\n    if (request->hasParam(\"switchId\"))\n    {\n        int switchIndex = request->getParam(\"switchId\")->value().toInt();\n        switchConfig = this->_stepperMotorServer->getCurrentServerConfiguration()->getSwitch(switchIndex);\n        if (switchConfig == NULL)\n        {\n            request->send(400, \"application/json\", \"{\\\"error\\\": \\\"No switch configuration found for given switch id\\\"}\");\n            return;\n        }\n        gpioPinForSwitch = switchConfig->getIoPinNumber();\n        //TODO: this detection is not exactely precise, since the switch could also be a combined Begin / End switch\n        if (switchConfig->isTypeBitSet(SWITCHTYPE_LIMITSWITCH_POS_BEGIN_BIT))\n        {\n            directionTowardHome = -1;\n        }\n    }\n    else\n    {\n        //Try to find pin by looking for configured limit switches of type homing switch for selected stepper motor config\n        switchConfig = this->_stepperMotorServer->getCurrentServerConfiguration()->getFirstConfiguredLimitSwitchForStepper(stepperIndex);\n        if (switchConfig == NULL)\n        {\n            request->send(400, \"application/json\", \"{\\\"error\\\": \\\"No existing limit switch configuration found the stepper with the given id\\\"}\");\n            return;\n        }\n        gpioPinForSwitch = switchConfig->getIoPinNumber();\n        // determine direction section for the case no explicit switch ID was provided\n        if (switchConfig->isTypeBitSet(SWITCHTYPE_LIMITSWITCH_POS_BEGIN_BIT))\n        {\n            directionTowardHome = -1;\n        }\n    }\n\n    ESPStepperMotorServer_Logger::logDebugf(\"Received homing request for stepper with id %i and limit switch on GPIO %i. Homing speed to be set to %.2f steps per second. Max step limit set to %i\\n\", stepperIndex, gpioPinForSwitch, speedInStepsPerSecond, maxSteps);\n\n    ESP_FlexyStepper *stepper = stepperConfiguration->getFlexyStepper();\n    if (speedInStepsPerSecond > 0)\n    {\n        stepper->setSpeedInStepsPerSecond(speedInStepsPerSecond);\n    }\n    if (accelInStepPerSecondSquare > 0)\n    {\n        stepper->setAccelerationInStepsPerSecondPerSecond(accelInStepPerSecondSquare);\n    }\n\n    if (forcedDirectionTowardHome != 0)\n    {\n        stepper->setDirectionToHome(forcedDirectionTowardHome);\n    }\n    else\n    {\n        stepper->setDirectionToHome(directionTowardHome);\n    }\n    stepper->goToLimitAndSetAsHome(NULL, maxSteps);\n    request->send(200, \"application/json\", \"{\\\"status\\\": \\\"homing procedure started\\\"}\");\n    return;\n}\n\nvoid ESPStepperMotorServer_RestAPI::populateSwitchDetailsToJsonObject(JsonObject &switchDetails, ESPStepperMotorServer_PositionSwitch *positionSwitch, int index)\n{\n    switchDetails[\"id\"] = index;\n    switchDetails[\"ioPin\"] = positionSwitch->getIoPinNumber();\n    switchDetails[\"name\"] = positionSwitch->getPositionName();\n    switchDetails[\"stepperId\"] = positionSwitch->getStepperIndex();\n    switchDetails[\"type\"] = positionSwitch->getSwitchType();\n    switchDetails[\"isActiveHighType\"] = positionSwitch->isActiveHigh();\n    switchDetails[\"switchPosition\"] = positionSwitch->getSwitchPosition();\n    if (positionSwitch->hasMacroActions())\n    {\n        JsonArray macroActions = switchDetails.createNestedArray(this->_stepperMotorServer->getCurrentServerConfiguration()->JSON_SECTION_NAME_SWITCH_CONFIGURATION_MACROACTIONS);\n        positionSwitch->serializeMacroActionsToJsonArray(macroActions);\n    }\n}\n\nvoid ESPStepperMotorServer_RestAPI::populateRotaryEncoderDetailsToJsonObject(JsonObject &rotaryEncoderDetails, ESPStepperMotorServer_RotaryEncoder *rotaryEncoder, int index)\n{\n    rotaryEncoderDetails[\"id\"] = index;\n    rotaryEncoderDetails[\"ioPinA\"] = rotaryEncoder->getPinAIOPin();\n    rotaryEncoderDetails[\"ioPinB\"] = rotaryEncoder->getPinBIOPin();\n    rotaryEncoderDetails[\"name\"] = rotaryEncoder->getDisplayName();\n    rotaryEncoderDetails[\"stepMultiplier\"] = rotaryEncoder->getStepMultiplier();\n    rotaryEncoderDetails[\"stepperId\"] = rotaryEncoder->getStepperIndex();\n}\n\nvoid ESPStepperMotorServer_RestAPI::populateStepperDetailsToJsonObject(JsonObject &stepperDetails, ESPStepperMotorServer_StepperConfiguration *stepper, int index)\n{\n    stepperDetails[\"id\"] = index;\n\n    stepperDetails[\"configured\"] = (stepper == NULL) ? \"false\" : \"true\";\n    if (stepper != NULL)\n    {\n        stepperDetails[\"name\"] = stepper->getDisplayName();\n        stepperDetails[\"stepPin\"] = stepper->getStepIoPin();\n        stepperDetails[\"dirPin\"] = stepper->getDirectionIoPin();\n\n        stepperDetails[\"brakePin\"] = stepper->getBrakeIoPin();\n        stepperDetails[\"brakePinActiveState\"] = stepper->getBrakePinActiveState();\n        stepperDetails[\"brakeEngageDelayMs\"] = stepper->getBrakeEngageDelayMs();\n        stepperDetails[\"brakeReleaseDelayMs\"] = stepper->getBrakeReleaseDelayMs();\n\n        stepperDetails[\"stepsPerMM\"] = stepper->getStepsPerMM();\n        stepperDetails[\"stepsPerRev\"] = stepper->getStepsPerRev();\n        stepperDetails[\"microsteppingDivisor\"] = stepper->getMicrostepsPerStep();\n\n        JsonObject position = stepperDetails.createNestedObject(\"position\");\n        position[\"mm\"] = stepper->getFlexyStepper()->getCurrentPositionInMillimeters();\n        position[\"revs\"] = stepper->getFlexyStepper()->getCurrentPositionInRevolutions();\n        position[\"steps\"] = stepper->getFlexyStepper()->getCurrentPositionInSteps();\n\n        JsonObject stepperStatus = stepperDetails.createNestedObject(\"velocity\");\n        stepperStatus[\"rev_s\"] = stepper->getFlexyStepper()->getCurrentVelocityInRevolutionsPerSecond();\n        stepperStatus[\"mm_s\"] = stepper->getFlexyStepper()->getCurrentVelocityInMillimetersPerSecond();\n        stepperStatus[\"steps_s\"] = stepper->getFlexyStepper()->getCurrentVelocityInStepsPerSecond();\n\n        stepperDetails[\"stopped\"] = stepper->getFlexyStepper()->motionComplete();\n    }\n}\n\nvoid ESPStepperMotorServer_RestAPI::logDebugRequestUrl(AsyncWebServerRequest *request)\n{\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n    if (ESPStepperMotorServer_Logger::isDebugEnabled())\n    {\n        int params = request->params();\n        ESPStepperMotorServer_Logger::logDebug((String)request->methodToString() + \" called\" + request->url() + ((params > 0) ? \" with parameters: \" : \"\"), (params == 0));\n        for (int i = 0; i < params; i++)\n        {\n            const AsyncWebParameter *p = request->getParam(i);\n            if (!p->isFile() && !p->isPost())\n            {\n                ESPStepperMotorServer_Logger::logDebug(p->name() + \"=\" + p->value() + ((i < params - 1) ? \", \" : \"\"), (i == params - 1), true);\n            }\n        }\n    }\n#endif\n}\n\n// request handlers\nvoid ESPStepperMotorServer_RestAPI::handlePostStepperRequest(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total, int stepperIndex)\n{\n    StaticJsonDocument<500> doc;\n    DeserializationError error = deserializeJson(doc, (const char *)data);\n    if (error)\n    {\n        request->send(400, \"application/json\", \"{\\\"error\\\": \\\"Invalid JSON request, deserialization failed\\\"}\");\n        ESPStepperMotorServer_Logger::logWarningf(\"Error while trying to deserialize JSON request: %s\", error.c_str());\n    }\n    else\n    {\n        if (doc.containsKey(\"name\") && doc.containsKey(\"stepPin\") && doc.containsKey(\"dirPin\") && doc.containsKey(\"stepsPerMM\") && doc.containsKey(\"stepsPerRev\") && doc.containsKey(\"microsteppingDivisor\"))\n        {\n            const char *name = doc[\"name\"];\n            int stepPin = doc[\"stepPin\"];\n            int dirPin = doc[\"dirPin\"];\n            int stepsPerMM = doc[\"stepsPerMM\"];\n            int stepsPerRev = doc[\"stepsPerRev\"];\n            int microsteppingDivisor = doc[\"microsteppingDivisor\"];\n\n            int brakePin = doc[\"brakePin\"];\n            int brakePinActiveState = doc[\"brakePinActiveState\"];\n            int brakeEngageDelayMs = doc[\"brakeEngageDelayMs\"];\n            int brakeReleaseDelayMs = doc[\"brakeReleaseDelayMs\"];\n\n            if (stepPin >= 0 && stepPin <= ESPStepperHighestAllowedIoPin && dirPin >= 0 && dirPin <= ESPStepperHighestAllowedIoPin && dirPin != stepPin)\n            {\n                ESPStepperMotorServer_StepperConfiguration *stepper = this->_stepperMotorServer->getCurrentServerConfiguration()->getStepperConfiguration(stepperIndex);\n                //check if pins are already in use by a stepper or switch configuration (that is not the current stepper to be updated)\n                if (this->_stepperMotorServer->isIoPinUsed(stepPin) && (stepper == NULL || stepper->getStepIoPin() != stepPin))\n                {\n                    request->send(400, \"application/json\", \"{\\\"error\\\": \\\"The given STEP IO pin is already used by another stepper or a switch configuration\\\"}\");\n                }\n                else if (this->_stepperMotorServer->isIoPinUsed(dirPin) && (stepper == NULL || stepper->getDirectionIoPin() != dirPin))\n                {\n                    request->send(400, \"application/json\", \"{\\\"error\\\": \\\"The given DIRECTION IO pin is already used by another stepper or a switch configuration\\\"}\");\n                }\n                else if (brakePin >= 0 && brakePin != ESPStepperMotorServer_StepperConfiguration::ESPServerStepperUnsetIoPinNumber && this->_stepperMotorServer->isIoPinUsed(brakePin) && (stepper == NULL || stepper->getBrakeIoPin() != brakePin))\n                {\n                    request->send(400, \"application/json\", \"{\\\"error\\\": \\\"The given BRAKE IO pin is already used by another stepper or a switch configuration\\\"}\");\n                }\n                else\n                {\n                    int newId = -1;\n                    ESPStepperMotorServer_StepperConfiguration *stepperToAdd = new ESPStepperMotorServer_StepperConfiguration(stepPin, dirPin);\n                    stepperToAdd->setDisplayName(name);\n                    stepperToAdd->setStepsPerMM(stepsPerMM);\n                    stepperToAdd->setStepsPerRev(stepsPerRev);\n                    stepperToAdd->setMicrostepsPerStep(microsteppingDivisor);\n                    if (brakePin != ESPStepperMotorServer_StepperConfiguration::ESPServerStepperUnsetIoPinNumber)\n                    {\n                        stepperToAdd->setBrakeIoPin(brakePin, brakePinActiveState);\n                    }\n                    stepperToAdd->setBrakeEngageDelayMs(brakeEngageDelayMs);\n                    stepperToAdd->setBrakeReleaseDelayMs(brakeReleaseDelayMs);\n\n                    if (stepperIndex == -1)\n                    {\n                        newId = this->_stepperMotorServer->addOrUpdateStepper(stepperToAdd);\n                    }\n                    else\n                    {\n                        //\"update\" existing stepper config which basically means we store it at a specific index\n                        newId = stepperIndex;\n                        this->_stepperMotorServer->addOrUpdateStepper(stepperToAdd, stepperIndex);\n                    }\n                    AsyncResponseStream *response = request->beginResponseStream(\"application/json\");\n                    response->setCode(200);\n                    response->printf(\"{\\\"id\\\": %i}\", newId);\n                    request->send(response);\n                }\n            }\n            else\n            {\n                request->send(400, \"application/json\", \"{\\\"error\\\": \\\"Invalid IO pin number given or step and dir pin are the same\\\"}\");\n            }\n        }\n        else\n        {\n            request->send(400, \"application/json\", \"{\\\"error\\\": \\\"Invalid request, missing one ore more required parameters: name, stepPin, dirPin, stepsPerMM, stepsPerRev, microsteppingDivisor\\\"}\");\n        }\n    }\n}\n\nint ESPStepperMotorServer_RestAPI::handleDeleteStepperRequest(AsyncWebServerRequest *request, boolean sendResponse)\n{\n    if (request->hasParam(\"id\"))\n    {\n        int stepperIndex = request->getParam(\"id\")->value().toInt();\n        if (stepperIndex < 0 || stepperIndex >= ESPServerMaxSteppers || this->_stepperMotorServer->getCurrentServerConfiguration()->getStepperConfiguration(stepperIndex) != NULL)\n        {\n            this->_stepperMotorServer->removeStepper(stepperIndex);\n            if (sendResponse)\n            {\n                request->send(204);\n            }\n            return 204;\n        }\n    }\n    if (sendResponse)\n    {\n        request->send(404, \"application/json\", \"{\\\"error\\\": \\\"Invalid stepper id\\\"}\");\n    }\n    return 404;\n}\n\nvoid ESPStepperMotorServer_RestAPI::handlePostRotaryEncoderRequest(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total, int encoderIndex)\n{\n    StaticJsonDocument<300> doc;\n    DeserializationError error = deserializeJson(doc, (const char *)data);\n    if (error)\n    {\n        request->send(400, \"application/json\", \"{\\\"error\\\": \\\"Invalid JSON request, deserialization failed\\\"}\");\n        ESPStepperMotorServer_Logger::logWarningf(\"Error while trying to deserialize JSON request: %s\", error.c_str());\n    }\n    else\n    {\n        if (doc.containsKey(\"stepMultiplier\") && doc.containsKey(\"pinA\") && doc.containsKey(\"pinB\") && doc.containsKey(\"displayName\") && doc.containsKey(\"stepperId\"))\n        {\n            const char *displayName = doc[\"displayName\"];\n            int stepMultiplier = doc[\"stepMultiplier\"];\n            byte stepperIndex = doc[\"stepperId\"];\n            byte pinA = doc[\"pinA\"];\n            byte pinB = doc[\"pinB\"];\n\n            if (pinA >= 0 && pinA <= ESPStepperHighestAllowedIoPin && pinB >= 0 && pinB <= ESPStepperHighestAllowedIoPin && pinA != pinB)\n            {\n                ESPStepperMotorServer_RotaryEncoder *encoderToUpdate = this->_stepperMotorServer->getCurrentServerConfiguration()->getRotaryEncoder(encoderIndex);\n\n                //check if pins are already in use by a stepper, switch or another encoder configuration\n                if (this->_stepperMotorServer->isIoPinUsed(pinA))\n                {\n                    //check if it pin NOT used by same encoder to update\n                    if (encoderToUpdate == NULL || encoderToUpdate->getPinAIOPin() != pinA)\n                    {\n                        AsyncResponseStream *response = request->beginResponseStream(\"application/json\");\n                        response->setCode(400);\n                        response->printf(\"{\\\"error\\\": \\\"The given Pin-A IO pin %i is already used by another stepper, encoder or switch configuration\\\"}\", pinA);\n                        request->send(response);\n                        return;\n                    }\n                }\n                if (this->_stepperMotorServer->isIoPinUsed(pinB))\n                {\n                    //check if it pin NOT used by same encoder to update\n                    if (encoderToUpdate == NULL || encoderToUpdate->getPinBIOPin() != pinB)\n                    {\n                        AsyncResponseStream *response = request->beginResponseStream(\"application/json\");\n                        response->setCode(400);\n                        response->printf(\"{\\\"error\\\": \\\"The given Pin-B IO pin %i is already used by another stepper, encoder or switch configuration\\\"}\", pinB);\n                        request->send(response);\n                        return;\n                    }\n                }\n\n                ESPStepperMotorServer_RotaryEncoder *encoderToAdd = new ESPStepperMotorServer_RotaryEncoder(pinA, pinB, displayName, stepMultiplier, stepperIndex);\n                if (encoderIndex == -1)\n                {\n                    encoderIndex = this->_stepperMotorServer->addOrUpdateRotaryEncoder(encoderToAdd);\n                }\n                else\n                {\n                    //\"update\" existing stepper config which basically means we store it at a specific index\n                    encoderIndex = this->_stepperMotorServer->addOrUpdateRotaryEncoder(encoderToAdd, encoderIndex);\n                }\n                AsyncResponseStream *response = request->beginResponseStream(\"application/json\");\n                response->setCode(200);\n                response->printf(\"{\\\"id\\\": %i}\", encoderIndex);\n                request->send(response);\n                return;\n            }\n            else\n            {\n                request->send(400, \"application/json\", \"{\\\"error\\\": \\\"Invalid IO pin number given or Pin A and Pin B are the same\\\"}\");\n                return;\n            }\n        }\n        else\n        {\n            request->send(400, \"application/json\", \"{\\\"error\\\": \\\"Invalid request, missing one ore more required parameters: stepMultiplier, pinA, pinB, displayName, stepperId\\\"}\");\n            return;\n        }\n    }\n}\n\nint ESPStepperMotorServer_RestAPI::handleDeleteRotaryEncoderRequest(AsyncWebServerRequest *request, boolean sendResponse)\n{\n    if (request->hasParam(\"id\"))\n    {\n        int encoderIndex = request->getParam(\"id\")->value().toInt();\n        if (this->_stepperMotorServer->getCurrentServerConfiguration()->getRotaryEncoder(encoderIndex) != NULL)\n        {\n            this->_stepperMotorServer->removeRotaryEncoder(encoderIndex);\n            if (sendResponse)\n            {\n                request->send(204);\n            }\n            return 204;\n        }\n    }\n    if (sendResponse)\n    {\n        request->send(404, \"application/json\", \"{\\\"error\\\": \\\"Invalid rotary encoder id\\\"}\");\n    }\n    return 404;\n}\n\nint ESPStepperMotorServer_RestAPI::handleDeleteSwitchRequest(AsyncWebServerRequest *request, boolean sendResponse)\n{\n    if (request->hasParam(\"id\"))\n    {\n        int switchIndex = request->getParam(\"id\")->value().toInt();\n        if (this->_stepperMotorServer->getCurrentServerConfiguration()->getSwitch(switchIndex))\n        {\n            this->_stepperMotorServer->removePositionSwitch(switchIndex);\n            if (sendResponse)\n            {\n                request->send(204);\n            }\n            return 204;\n        }\n    }\n    if (sendResponse)\n    {\n        request->send(404, \"application/json\", \"{\\\"error\\\": \\\"Invalid position switch id\\\"}\");\n    }\n    return 404;\n}\n\nvoid ESPStepperMotorServer_RestAPI::handlePostSwitchRequest(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total, int switchIndex)\n{\n    StaticJsonDocument<350> doc;\n    DeserializationError error = deserializeJson(doc, (const char *)data);\n    if (error)\n    {\n        request->send(400, \"application/json\", \"{\\\"error\\\": \\\"Invalid JSON request, deserialization failed\\\"}\");\n        ESPStepperMotorServer_Logger::logWarningf(\"Error while trying to deserialize JSON request: %s\", error.c_str());\n    }\n    else\n    {\n        if (doc.containsKey(\"stepperId\") && doc.containsKey(\"ioPinNumber\") && doc.containsKey(\"positionName\") && doc.containsKey(\"switchPosition\") && doc.containsKey(\"switchType\"))\n        {\n            int stepperConfigIndex = doc[\"stepperId\"];\n            byte ioPinNumber = doc[\"ioPinNumber\"];\n            const char *name = doc[\"positionName\"];\n            long switchPosition = doc[\"switchPosition\"];\n            byte switchType = doc[\"switchType\"];\n\n            //stepperConfigIndex can be -1 for emergency switch only\n            if (stepperConfigIndex == -1 && !(switchType & (1 << (SWITCHTYPE_EMERGENCY_STOP_SWITCH_BIT - 1))))\n            {\n                request->send(400, \"application/json\", \"{\\\"error\\\": \\\"Invalid Stepper ID. Only emergency stop switches are allowed to have -1 as stepper configuration reference\\\"}\");\n                return;\n            }\n\n            if (ioPinNumber >= 0 && ioPinNumber <= ESPStepperHighestAllowedIoPin) //check valid pin range value\n            {\n                ESPStepperMotorServer_PositionSwitch *switchToUpdate = this->_stepperMotorServer->getCurrentServerConfiguration()->getSwitch(switchIndex);\n                //check if pins are already in use by a stepper, switch or another encoder configuration\n                if (this->_stepperMotorServer->isIoPinUsed(ioPinNumber))\n                {\n                    //check if pin is NOT used by same switch to update\n                    if (switchToUpdate == NULL || switchToUpdate->getIoPinNumber() != ioPinNumber)\n                    {\n                        request->send(400, \"application/json\", \"{\\\"error\\\": \\\"The given IO pin is already used by another element\\\"}\");\n                        return;\n                    }\n                }\n\n                if (stepperConfigIndex > -1 && this->_stepperMotorServer->getCurrentServerConfiguration()->getStepperConfiguration(stepperConfigIndex) == NULL)\n                {\n                    request->send(404, \"application/json\", \"{\\\"error\\\": \\\"The given stepper id is invalid\\\"}\");\n                    return;\n                }\n\n                ESPStepperMotorServer_PositionSwitch *posSwitchToAdd = new ESPStepperMotorServer_PositionSwitch(ioPinNumber, stepperConfigIndex, switchType, name, switchPosition);\n\n                if (doc[this->_stepperMotorServer->getCurrentServerConfiguration()->JSON_SECTION_NAME_SWITCH_CONFIGURATION_MACROACTIONS])\n                {\n                    JsonArray macroActionsJsonArray = doc[this->_stepperMotorServer->getCurrentServerConfiguration()->JSON_SECTION_NAME_SWITCH_CONFIGURATION_MACROACTIONS].as<JsonArray>();\n                    if (macroActionsJsonArray)\n                    {\n                        for (JsonVariant macroActionJson : macroActionsJsonArray)\n                        {\n                            posSwitchToAdd->addMacroAction(ESPStepperMotorServer_MacroAction::fromJsonObject(macroActionJson));\n                        }\n                    }\n                }\n\n                switchIndex = this->_stepperMotorServer->addOrUpdatePositionSwitch(posSwitchToAdd, switchIndex);\n                AsyncResponseStream *response = request->beginResponseStream(\"application/json\");\n                response->setCode(200);\n                response->printf(\"{\\\"id\\\": %i}\", switchIndex);\n                request->send(response);\n            }\n            else\n            {\n                request->send(400, \"application/json\", \"{\\\"error\\\": \\\"Invalid IO pin number given\\\"}\");\n            }\n        }\n        else\n        {\n            request->send(400, \"application/json\", \"{\\\"error\\\": \\\"Invalid request, missing one ore more required parameters: stepperId, ioPinNumber, positionName, switchPosition, switchType\\\"}\");\n        }\n    }\n}\n\n// -------------------------------------- End --------------------------------------\n"
  },
  {
    "path": "src/ESPStepperMotorServer_RestAPI.h",
    "content": "//      ******************************************************************\n//      *                                                                *\n//      *       Header file for ESPStepperMotorServer_RestAPI.cpp        *\n//      *                                                                *\n//      *               Copyright (c) Paul Kerspe, 2019                  *\n//      *                                                                *\n//      ******************************************************************\n\n// MIT License\n//\n// Copyright (c) 2019 Paul Kerspe\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 furnished\n// to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// 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 THE\n// SOFTWARE.\n\n#ifndef ESPStepperMotorServer_RestAPI_h\n#define ESPStepperMotorServer_RestAPI_h\n\n#include <Arduino.h>\n#include <AsyncJson.h>\n#include <ESPAsyncWebServer.h>\n#include <ArduinoJson.h>\n#include <ESPStepperMotorServer_PositionSwitch.h>\n#include <ESPStepperMotorServer_Logger.h>\n#include <ESPStepperMotorServer.h>\n#include <ESP_FlexyStepper.h>\n\n//just declare class here for compiler, since we have a circular dependency\nclass ESPStepperMotorServer;\n\nclass ESPStepperMotorServer_RestAPI\n{\npublic:\n  ESPStepperMotorServer_RestAPI(ESPStepperMotorServer *stepperMotorServer);\n  /**\n     * register all rest endpoint handlers with the given ESP AsyncWebServer instance reference\n     */\n  void registerRestEndpoints(AsyncWebServer *server);\n\nprivate:\n  String version;\n  ESPStepperMotorServer_Logger *logger;\n  ESPStepperMotorServer *_stepperMotorServer;\n  void populateStepperDetailsToJsonObject(JsonObject &detailsObjecToPopulate, ESPStepperMotorServer_StepperConfiguration *stepper, int index);\n  void populateSwitchDetailsToJsonObject(JsonObject &detailsObjecToPopulate, ESPStepperMotorServer_PositionSwitch *positionSwitch, int index);\n  void populateRotaryEncoderDetailsToJsonObject(JsonObject &detailsObjecToPopulate, ESPStepperMotorServer_RotaryEncoder *rotaryEncoder, int index);\n  \n  void logDebugRequestUrl(AsyncWebServerRequest *request);\n\n  //movement related endpoints\n  void handleHomingRequest(AsyncWebServerRequest *request);\n  //for other endpoints see ESPStepperMotorServer_RestAPI.cpp in function registerRestEndpoints\n\n  // SWITCH CONFIGURATION ENDPOINT HANDLER\n  void handlePostSwitchRequest(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total, int switchIndex = -1);\n  int handleDeleteSwitchRequest(AsyncWebServerRequest *request, boolean sendReponse);\n  // STEPPER CONFIGURATION ENDPOINT HANDLER\n  void handlePostStepperRequest(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total, int switchIndex = -1);\n  int handleDeleteStepperRequest(AsyncWebServerRequest *request, boolean sendReponse);\n  // ROTARY ENCODER CONFIGURATION ENDPOINT HANDLER\n  void handlePostRotaryEncoderRequest(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total, int encoderIndex = -1);\n  int handleDeleteRotaryEncoderRequest(AsyncWebServerRequest *request, boolean sendReponse);\n};\n\n#endif\n"
  },
  {
    "path": "src/ESPStepperMotorServer_RotaryEncoder.cpp",
    "content": "//      ******************************************************************\n//     //                                                               *\n//     //   Header file for ESPStepperMotorServer_RotaryEncoder.cpp     *\n//     //                                                               *\n//     //              Copyright (c) Paul Kerspe, 2019                  *\n//     //                                                               *\n//      ******************************************************************\n//\n// This is a class to support rotary encoders a input controllers for the ESPStepperMotorServer\n//\n// It is based on the work Ben Buxton's Rotary Encodr Library, which was licensed under the following conditions:\n// Copyright 2011 Ben Buxton.Licenced under the GNU GPL Version 3. Contact : bb @cactii.net\n// Licenced under the GNU GPL Version 3. Contact: bb@cactii.net\n// https://github.com/buxtronix/arduino/tree/master/libraries/Rotary\n//\n// BEGIN OF BENS COMMENT\n// A typical mechanical rotary encoder emits a two bit gray code\n// on 3 output pins. Every step in the output (often accompanied\n// by a physical 'click') generates a specific sequence of output\n// codes on the pins.\n// There are 3 pins used for the rotary encoding - one common and\n// two 'bit' pins.\n// The following is the typical sequence of code on the output when\n// moving from one step to the next:\n//  Position   Bit1   Bit2\n//  ----------------------\n//    Step1     0      0\n//     1/4      1      0\n//     1/2      1      1\n//     3/4      0      1\n//    Step2     0      0\n//\n// From this table, we can see that when moving from one 'click' to\n// the next, there are 4 changes in the output code.\n//\n// - From an initial 0 - 0, Bit1 goes high, Bit0 stays low.\n// - Then both bits are high, halfway through the step.\n// - Then Bit1 goes low, but Bit2 stays high.\n// - Finally at the end of the step, both bits return to 0.\n//\n// Detecting the direction is easy - the table simply goes in the other\n// direction (read up instead of down).\n//\n// To decode this, we use a simple state machine. Every time the output\n// code changes, it follows state, until finally a full steps worth of\n// code is received (in the correct order). At the final 0-0, it returns\n// a value indicating a step in one direction or the other.\n//\n// If an invalid state happens (for example we go from '0-1' straight\n// to '1-0'), the state machine resets to the start until 0-0 and the\n// next valid codes occur.\n//\n// The biggest advantage of using a state machine over other algorithms\n// is that this has inherent debounce built in. Other algorithms emit spurious\n// output with switch bounce, but this one will simply flip between\n// sub-states until the bounce settles, then continue along the state\n// machine.\n// A side effect of debounce is that fast rotations can cause steps to\n// be skipped. By not requiring debounce, fast rotations can be accurately\n// measured.\n// Another advantage is the ability to properly handle bad state, such\n// as due to EMI, etc.\n// It is also a lot simpler than others - a static state table and less\n// than 10 lines of logic.\n// END OF BENS COMMENT\n//\n// I included the sources here to reduce the complexity of setting up the required libraries that need to be installed on top of the ESPSMS Library\n// by the user in the Arduino UI, since it can not currently deal with automatic dependency management like platformIO.\n// Ben's code is beautiful in its simplicy and therefore does not create a noticable overhead in the code size of the ESPStepperMotorServer.\n//\n//\n// ESPStepperMotorServer is licensed under the following conditions:\n//\n// MIT License\n//\n// Copyright (c) 2019 Paul Kerspe\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 furnished\n// to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// 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 THE\n// SOFTWARE.\n\n#include \"Arduino.h\"\n#include \"ESPStepperMotorServer_Logger.h\"\n#include \"ESPStepperMotorServer_RotaryEncoder.h\"\n#include \"ESPStepperMotorServer.h\"\n\n// The below state table has, for each state (row), the new state\n// to set based on the next encoder output. From left to right in,\n// the table, the encoder outputs are 00, 01, 10, 11, and the value\n// in that position is the new state to set.\n#define R_START 0x0\n\n// NOTE regarind HALF STEP support in the original Rotray Encoder library by BenBuxton:\n// Half Step support has been removed to reduce complexity\n\n// Use the full-step state table (emits a code at 00 only)\n#define R_CW_FINAL 0x1\n#define R_CW_BEGIN 0x2\n#define R_CW_NEXT 0x3\n#define R_CCW_BEGIN 0x4\n#define R_CCW_FINAL 0x5\n#define R_CCW_NEXT 0x6\n\nconst unsigned char ttable[7][4] = {\n    // R_START\n    {R_START, R_CW_BEGIN, R_CCW_BEGIN, R_START},\n    // R_CW_FINAL\n    {R_CW_NEXT, R_START, R_CW_FINAL, R_START | DIR_CW},\n    // R_CW_BEGIN\n    {R_CW_NEXT, R_CW_BEGIN, R_START, R_START},\n    // R_CW_NEXT\n    {R_CW_NEXT, R_CW_BEGIN, R_CW_FINAL, R_START},\n    // R_CCW_BEGIN\n    {R_CCW_NEXT, R_START, R_CCW_BEGIN, R_START},\n    // R_CCW_FINAL\n    {R_CCW_NEXT, R_CCW_FINAL, R_START, R_START | DIR_CCW},\n    // R_CCW_NEXT\n    {R_CCW_NEXT, R_CCW_FINAL, R_CCW_BEGIN, R_START},\n};\n\nESPStepperMotorServer_RotaryEncoder::ESPStepperMotorServer_RotaryEncoder(char pinA, char pinB, String displayName, int stepMultiplier, byte stepperIndex)\n{\n    // Assign variables.\n    this->_pinA = pinA;\n    this->_pinB = pinB;\n    this->_displayName = displayName;\n    this->_stepMultiplier = stepMultiplier;\n    this->_stepperIndex = stepperIndex;\n    this->_encoderIndex = -1;\n    // Initialise state.\n    this->_state = R_START;\n}\n\nunsigned char ESPStepperMotorServer_RotaryEncoder::process()\n{\n    // Grab state of input pins.\n    unsigned char pinstate = (digitalRead(this->_pinB) << 1) | digitalRead(this->_pinA);\n    // Determine new state from the pins and state table.\n    this->_state = ttable[this->_state & 0xf][pinstate];\n    // Return emit bits, ie the generated event.\n    return this->_state & 0x30;\n}\n\nvoid ESPStepperMotorServer_RotaryEncoder::setId(byte id)\n{\n    this->_encoderIndex = id;\n}\n\nbyte ESPStepperMotorServer_RotaryEncoder::getId()\n{\n    return this->_encoderIndex;\n}\n\nunsigned char ESPStepperMotorServer_RotaryEncoder::getPinAIOPin()\n{\n    return this->_pinA;\n}\n\nunsigned char ESPStepperMotorServer_RotaryEncoder::getPinBIOPin()\n{\n    return this->_pinB;\n}\n\nvoid ESPStepperMotorServer_RotaryEncoder::setStepperIndex(byte stepperMotorIndex)\n{\n    if (stepperMotorIndex > -1 && stepperMotorIndex <= ESPStepperHighestAllowedIoPin)\n    {\n        this->_stepperIndex = stepperMotorIndex;\n    }\n    else\n    {\n        ESPStepperMotorServer_Logger::logWarning(\"ESPStepperMotorServer_RotaryEncoder::setStepperIndex: Invalid stepper motor index value given, must be within he allowed range of 0 >= value <= ESPStepperHighestAllowedIoPin\");\n    }\n}\n\nbyte ESPStepperMotorServer_RotaryEncoder::getStepperIndex()\n{\n    return this->_stepperIndex;\n}\n\nconst String ESPStepperMotorServer_RotaryEncoder::getDisplayName()\n{\n    return this->_displayName;\n}\n\nvoid ESPStepperMotorServer_RotaryEncoder::setStepMultiplier(unsigned int stepMultiplier)\n{\n    this->_stepMultiplier = stepMultiplier;\n}\n\nunsigned int ESPStepperMotorServer_RotaryEncoder::getStepMultiplier()\n{\n    return this->_stepMultiplier;\n}\n"
  },
  {
    "path": "src/ESPStepperMotorServer_RotaryEncoder.h",
    "content": "//      ******************************************************************\n//      *                                                                *\n//      *    Header file for ESPStepperMotorServer_RotaryEncoder.cpp     *\n//      *                                                                *\n//      *               Copyright (c) Paul Kerspe, 2019                  *\n//      *                                                                *\n//      ******************************************************************\n//\n// This is a class to support rotary encoders a input controllers for the ESPStepperMotorServer\n//\n// It is based on the work Ben Buxton's Rotary Encodr Library, which was licensed under the following conditions:\n// Copyright 2011 Ben Buxton.Licenced under the GNU GPL Version 3. Contact : bb @cactii.net\n// Licenced under the GNU GPL Version 3. Contact: bb@cactii.net\n// https://github.com/buxtronix/arduino/tree/master/libraries/Rotary\n//\n// I included the sources here to reduce the complexity of setting up the required libraries that need to be installed on top of the ESPSMS Library\n// by the user in the Arduino UI, since it can not currently deal with automatic dependency management like platformIO.\n// Ben's code is beautiful in its simplicy and therefore does not create a noticable overhead in the code size of the ESPStepperMotorServer.\n//\n//\n// ESPStepperMotorServer is licensed under the following conditions:\n//\n// MIT License\n//\n// Copyright (c) 2019 Paul Kerspe\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 furnished\n// to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// 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 THE\n// SOFTWARE.\n\n#ifndef ESPStepperMotorServer_RotaryEncoder_h\n#define ESPStepperMotorServer_RotaryEncoder_h\n\n#include \"Arduino.h\"\n\n// Enable this to emit codes twice per step.\n//#define HALF_STEP\n\n// Enable weak pullups\n#define ENABLE_PULLUPS\n\n// Values returned by 'process'\n// No complete step yet.\n#define DIR_NONE 0x0\n// Clockwise step.\n#define DIR_CW 0x10\n// Anti-clockwise step.\n#define DIR_CCW 0x20\n\n//size calculated using https://arduinojson.org/v6/assistant/\n#define RESERVED_JSON_SIZE_ESPStepperMotorServer_RotaryEncoder 170\n\nclass ESPStepperMotorServer_RotaryEncoder\n{\n   friend class ESPStepperMotorServer;\n\npublic:\n   /**\n     * Constructor for the rotary encoder entity.\n     * The arguments define the PGIO Pins to be used to connect the Rotary encoder to\n     * The displayName defines the human readable name for thi encoder in the User Interface and logs\n     */\n   ESPStepperMotorServer_RotaryEncoder(char pinA, char pinB, String displayName, int stepMultiplier, byte stepperIndex);\n\n   /**\n   * setter to set the id of this encoder.\n   * Only use this if you know what you are doing\n   */\n   void setId(byte id);\n   /**\n    * get the id of the rotary encoder\n    */\n   byte getId();\n   /**\n     * process the input states of the io pins to determine the current rotary encoder step status\n     */\n   unsigned char process();\n   /**\n     * return the configured GPIO pin number that is connected to pin A of the rotary encoder\n     */\n   unsigned char getPinAIOPin();\n   /**\n     * return the configured GPIO pin number that is connected to pin B of the rotary encoder\n     */\n   unsigned char getPinBIOPin();\n   /**\n     * get the configured display name of the rotary encoder    \n     */\n   const String getDisplayName();\n   /**\n     * set the stepper motor id that should be linked to this rotary encoder\n     */\n   void setStepperIndex(byte stepperMotorIndex);\n   /**\n     * get the configured id of the stepper motor that is linked to this rotary encoder\n     */\n   byte getStepperIndex();\n   /**\n   * set a multiplication factor used to calculate the ammount of pulses send to the stepper motor for one step of the rotary encoder.\n   * Default is factor of 1, so if one step in the rotatry encoder will be convertd into on puls to the steppr motor driver.\n   * If microstpping is configured in the stepper driver board, one pulse will be one microstep, so it might be needed to st this multiplier accordingly to the microstepp setting of the stepper drivr board.     * e.g. if you configured 32 microsteps in your stepper driver board and you want the stepper motor to perform one full step per rotary encoder step, you need to set this mulitplier to 32\n   */\n   void setStepMultiplier(unsigned int stepMultiplier);\n   /**\n   * get the configured step multiplier value for this rotary encoder \n   */\n   unsigned int getStepMultiplier(void);\n\nprivate:\n   unsigned char _state;\n   unsigned char _pinA;\n   unsigned char _pinB;\n   unsigned char _encoderIndex;\n   byte _stepperIndex;\n   String _displayName;\n   // step multiplier is used to define how many pulses should be sen to the stepper for one step from the rotary encoder\n   unsigned int _stepMultiplier;\n};\n\n#endif\n"
  },
  {
    "path": "src/ESPStepperMotorServer_StepperConfiguration.cpp",
    "content": "//      *********************************************************************\n//      *                                                                   *\n//      *  ESP8266 and ESP32 Stepper Motor Server  - Stepper Config class   *\n//      * a wrapper class to decouple the FlexyStepper class a bit better   *\n//      *            Copyright (c) Paul Kerspe, 2019                        *\n//      *                                                                   *\n//      *********************************************************************\n//\n// MIT License\n//\n// Copyright (c) 2019 Paul Kerspe\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 furnished\n// to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// 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 THE\n// SOFTWARE.\n//\n\n#include \"ESPStepperMotorServer_StepperConfiguration.h\"\n\nESPStepperMotorServer_StepperConfiguration::ESPStepperMotorServer_StepperConfiguration(const ESPStepperMotorServer_StepperConfiguration &espStepperConfiguration)\n{\n    this->_flexyStepper = new ESP_FlexyStepper;\n    *this->_flexyStepper = *espStepperConfiguration._flexyStepper;\n\n    this->_stepIoPin = espStepperConfiguration._stepIoPin;\n    this->_directionIoPin = espStepperConfiguration._directionIoPin;\n    this->_stepsPerMM = espStepperConfiguration._stepsPerMM;\n    this->_stepsPerRev = espStepperConfiguration._stepsPerRev;\n    this->_microsteppingDivisor = espStepperConfiguration._microsteppingDivisor;\n    this->_displayName = espStepperConfiguration._displayName;\n    this->_rpmLimit = espStepperConfiguration._rpmLimit;\n\n    this->_flexyStepper->connectToPins(this->_stepIoPin, this->_directionIoPin);\n}\n\nESPStepperMotorServer_StepperConfiguration::~ESPStepperMotorServer_StepperConfiguration()\n{\n    delete this->_flexyStepper;\n}\n\n//\n// constructor for the stepper wrapper class\n//\nESPStepperMotorServer_StepperConfiguration::ESPStepperMotorServer_StepperConfiguration(byte stepIoPin, byte directionIoPin)\n{\n    this->_stepIoPin = stepIoPin;\n    this->_directionIoPin = directionIoPin;\n    this->_flexyStepper = new ESP_FlexyStepper();\n    this->_flexyStepper->connectToPins(this->_stepIoPin, this->_directionIoPin);\n}\n\nESPStepperMotorServer_StepperConfiguration::ESPStepperMotorServer_StepperConfiguration(byte stepIoPin, byte directionIoPin, String displayName, unsigned int stepsPerRev, unsigned int stepsPerMM, unsigned int microsteppingDivisor, unsigned int rpmLimit)\n{\n    this->_stepIoPin = stepIoPin;\n    this->_directionIoPin = directionIoPin;\n    this->_microsteppingDivisor = microsteppingDivisor;\n    this->_displayName = displayName;\n    this->_rpmLimit = rpmLimit;\n\n    this->_flexyStepper = new ESP_FlexyStepper();\n    this->_flexyStepper->connectToPins(this->_stepIoPin, this->_directionIoPin);\n\n    //we store the value in flexistepper and locally, since flexystepper does not provider getters\n    this->_flexyStepper->setStepsPerMillimeter(stepsPerMM * this->_microsteppingDivisor);\n    this->_stepsPerMM = stepsPerMM;\n\n    //we store the value in flexistepper and locally, since flexystepper does not provider getters\n    this->_flexyStepper->setStepsPerRevolution(stepsPerRev * this->_microsteppingDivisor);\n    this->_stepsPerRev = stepsPerRev;\n}\n\n// ---------------------------------------------------------------------------------\n//                                  Getters / Setters\n// ---------------------------------------------------------------------------------\nESP_FlexyStepper *ESPStepperMotorServer_StepperConfiguration::getFlexyStepper()\n{\n    return this->_flexyStepper;\n}\n\nvoid ESPStepperMotorServer_StepperConfiguration::setId(byte id)\n{\n    this->_stepperIndex = id;\n}\n\nbyte ESPStepperMotorServer_StepperConfiguration::getId()\n{\n    return this->_stepperIndex;\n}\n\nString ESPStepperMotorServer_StepperConfiguration::getDisplayName()\n{\n    return this->_displayName;\n}\nvoid ESPStepperMotorServer_StepperConfiguration::setDisplayName(String displayName)\n{\n    if (displayName.length() > ESPSMS_Stepper_DisplayName_MaxLength)\n    {\n        char logString[160];\n        sprintf(logString, \"ESPStepperMotorServer_StepperConfiguration::setDisplayName: The display name for stepper with id %i is to long. Max length is %i characters. Name will be trimmed\", this->getId(), ESPSMS_Stepper_DisplayName_MaxLength);\n        ESPStepperMotorServer_Logger::logWarning(logString);\n        this->_displayName = displayName.substring(0, ESPSMS_Stepper_DisplayName_MaxLength);\n    }\n    else\n    {\n        this->_displayName = displayName;\n    }\n}\n\nbyte ESPStepperMotorServer_StepperConfiguration::getStepIoPin()\n{\n    return this->_stepIoPin;\n}\n\nbyte ESPStepperMotorServer_StepperConfiguration::getDirectionIoPin()\n{\n    return this->_directionIoPin;\n}\n\n// brake control settings\n\nbyte ESPStepperMotorServer_StepperConfiguration::getBrakeIoPin()\n{\n    return this->_brakeIoPin;\n}\n\nlong ESPStepperMotorServer_StepperConfiguration::getBrakeEngageDelayMs()\n{\n    return this->_brakeEngageDelayMs;\n}\n\nlong ESPStepperMotorServer_StepperConfiguration::getBrakeReleaseDelayMs()\n{\n    return this->_brakeReleaseDelayMs;\n}\n\nbyte ESPStepperMotorServer_StepperConfiguration::getBrakePinActiveState()\n{\n    return this->_brakePinActiveState;\n}\n\nvoid ESPStepperMotorServer_StepperConfiguration::setBrakeIoPin(byte brakeIoPin, byte brakePinActiveState)\n{\n    this->_brakeIoPin = brakeIoPin;\n    this->_brakePinActiveState = brakePinActiveState;\n    this->_flexyStepper->setBrakePin(brakeIoPin, brakePinActiveState);\n}\n\nvoid ESPStepperMotorServer_StepperConfiguration::setBrakeEngageDelayMs(long delay)\n{\n    this->_brakeEngageDelayMs = delay;\n    this->_flexyStepper->setBrakeEngageDelayMs(delay);\n}\n\nvoid ESPStepperMotorServer_StepperConfiguration::setBrakeReleaseDelayMs(long delay)\n{\n    this->_brakeReleaseDelayMs = delay;\n    this->_flexyStepper->setBrakeReleaseDelayMs(delay);\n}\n\nvoid ESPStepperMotorServer_StepperConfiguration::setBrakePinActiveState(byte activeState)\n{\n    this->_brakePinActiveState = activeState;\n    this->_flexyStepper->setBrakePin(this->_brakeIoPin, this->_brakePinActiveState);\n}\n\n// motion configurateion settings\nvoid ESPStepperMotorServer_StepperConfiguration::setStepsPerRev(unsigned int stepsPerRev)\n{\n    this->_flexyStepper->setStepsPerRevolution(stepsPerRev * this->_microsteppingDivisor);\n    this->_stepsPerRev = stepsPerRev;\n}\n\nunsigned int ESPStepperMotorServer_StepperConfiguration::getStepsPerRev()\n{\n    return this->_stepsPerRev;\n}\n\nvoid ESPStepperMotorServer_StepperConfiguration::setStepsPerMM(unsigned int stepsPerMM)\n{\n    this->_flexyStepper->setStepsPerMillimeter(stepsPerMM * this->_microsteppingDivisor);\n    this->_stepsPerMM = stepsPerMM;\n}\n\nunsigned int ESPStepperMotorServer_StepperConfiguration::getStepsPerMM()\n{\n    return this->_stepsPerMM;\n}\n\nvoid ESPStepperMotorServer_StepperConfiguration::setMicrostepsPerStep(unsigned int microstepsPerStep)\n{\n    //check for power of two value, since others are not allowed in micro step sizes\n    if (microstepsPerStep && !(microstepsPerStep & (microstepsPerStep - 1)))\n    {\n        this->_microsteppingDivisor = microstepsPerStep;\n        //update flexy stepper as well in regards to steps/rev and steps/mm\n        this->_flexyStepper->setStepsPerMillimeter(this->_stepsPerMM * this->_microsteppingDivisor);\n        this->_flexyStepper->setStepsPerRevolution(this->_stepsPerRev * this->_microsteppingDivisor);\n    }\n    else\n    {\n        ESPStepperMotorServer_Logger::logWarningf(\"Invalid microstepping value given: %i. Only values which are power of two are allowed\", microstepsPerStep);\n    }\n}\n\nunsigned int ESPStepperMotorServer_StepperConfiguration::getMicrostepsPerStep()\n{\n    return this->_microsteppingDivisor;\n}\n\nvoid ESPStepperMotorServer_StepperConfiguration::setRpmLimit(unsigned int rpmLimit)\n{\n    if (rpmLimit > ESPSMS_MAX_UPPER_RPM_LMIT)\n    {\n        char logString[170];\n        sprintf(logString, \"ESPStepperMotorServer_StepperConfiguration::setRpmLimit: The given rpm limit value %u exceeds the allowed maximum rpm limit of %i, will set to %i\", rpmLimit, ESPSMS_MAX_UPPER_RPM_LMIT, ESPSMS_MAX_UPPER_RPM_LMIT);\n        ESPStepperMotorServer_Logger::logWarning(logString);\n        this->_rpmLimit = ESPSMS_MAX_UPPER_RPM_LMIT;\n    }\n    else\n    {\n        this->_rpmLimit = rpmLimit;\n    }\n}\n\nunsigned int ESPStepperMotorServer_StepperConfiguration::getRpmLimit()\n{\n    return this->_rpmLimit;\n}\n"
  },
  {
    "path": "src/ESPStepperMotorServer_StepperConfiguration.h",
    "content": "//      *************************************************************************\n//      *                                                                       *\n//      *     Header file for ESPStepperMotorServer_StepperConfiguration.cpp    *\n//      *                                                                       *\n//      *              Copyright (c) Paul Kerspe, 2019                          *\n//      *                                                                       *\n//      *************************************************************************\n\n// MIT License\n//\n// Copyright (c) 2019 Paul Kerspe\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 furnished\n// to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// 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 THE\n// SOFTWARE.\n\n#ifndef ESPStepperMotorServer_StepperConfiguration_h\n#define ESPStepperMotorServer_StepperConfiguration_h\n\n#include <ESPStepperMotorServer_Logger.h>\n#include <ESP_FlexyStepper.h>\n\n#define ESPSMS_MICROSTEPS_OFF 1\n#define ESPSMS_MICROSTEPS_2 2\n#define ESPSMS_MICROSTEPS_4 4\n#define ESPSMS_MICROSTEPS_8 8\n#define ESPSMS_MICROSTEPS_16 16\n#define ESPSMS_MICROSTEPS_32 32\n#define ESPSMS_MICROSTEPS_128 128\n#define ESPSMS_MICROSTEPS_256 256\n\n#define ESPSMS_MAX_UPPER_RPM_LMIT 3000\n\n#define ESPSMS_Stepper_DisplayName_MaxLength 20\n\n//size calculated using https://arduinojson.org/v6/assistant/\n#define RESERVED_JSON_SIZE_ESPStepperMotorServer_StepperConfiguration 210\n\nclass ESPStepperMotorServer_StepperConfiguration\n{\n  friend class ESPStepperMotorServer;\n\npublic:\n  ESPStepperMotorServer_StepperConfiguration(const ESPStepperMotorServer_StepperConfiguration &espStepperConfiguration);\n  ~ESPStepperMotorServer_StepperConfiguration();\n  ESPStepperMotorServer_StepperConfiguration(byte stepIoPin, byte directionIoPin);\n  ESPStepperMotorServer_StepperConfiguration(byte stepIoPin, byte directionIoPin, String displayName, unsigned int stepsPerRev, unsigned int stepsPerMM, unsigned int microsteppingDivisor, unsigned int rpmLimit);\n\n  ESP_FlexyStepper *getFlexyStepper();\n\n  /**\n   * Internally used setter to set the id of this stepper motor.\n   * Only use this if you know what you are doing\n   */\n  void setId(byte id);\n  /**\n   * get the internal id of this stepper motor configuration within the stepper server\n   */\n  byte getId();\n\n  /**\n   * Set the display name of the stepper motor to be shown in the user intefaces\n   */\n  void setDisplayName(String displayName);\n  /**\n   * Get the currently configured display name for the stepper motor\n   */\n  String getDisplayName();\n\n  /**\n   * Get the currently configured IO pin that is used to send step pulses to the stepper driver\n   */\n  byte getStepIoPin();\n\n  /**\n   * Get the currently configured IO pin that is used to send the direction signal to the stepper driver\n   */\n  byte getDirectionIoPin();\n\n  /**\n   * Get the currently configured IO pin that is used to engage an optional engine/motor brake.\n   * Returns ESPServerStepperUnsetIoPinNumber (255) if none is defined\n   */\n  byte getBrakeIoPin();\n\n  /**\n   * Get the currently configured active state of the IO pin used to enabel the engine/motor brake.\n   * Returns 1 for active high (pin goes high to activate the brake), 2 for active low (pin goes low to activate the brake)\n   */\n  byte getBrakePinActiveState();\n\n  /**\n   * Get the currently configured delay in ms between the motor comes to a stop and the engine brake is being engaged. Default is 0ms.\n   */\n  long getBrakeEngageDelayMs();\n\n  /**\n   * Get the currently confgured timeout of inactivity of the motor, before the motor brake is released.\n   * Default is -1, meaning that the brake is never released as long as the engine is stopped.\n   */\n  long getBrakeReleaseDelayMs();\n\n  void setBrakeIoPin(byte, byte);\n  void setBrakePinActiveState(byte);\n  void setBrakeEngageDelayMs(long);\n  void setBrakeReleaseDelayMs(long);\n\n  /**\n   * Set the number of full steps the stepper motor itself needs to perform for a full revolution.\n   * Most stepper motors perform 1.8 degree turn per step, thus resulting in 200 full steps per revolution.\n   * Other somewhat common values are 3.6 degreee (100 steps/rev), 3.75 degree (96 steps/rev) and 7.5 degree (48 steps/rev) per full step.\n   * Geared stepper motors may have much smaller values, resutling in a much higher steps/rev value. See the datasheet of your stepper motor for the correct value.\n   * The default value is 200 steps/rev since this is the most common value.\n   */\n  void setStepsPerRev(unsigned int);\n  /**\n   * Get the currently configured steps/rev value for this steppe motor\n   * The default value is 200 steps/rev\n   **/\n  unsigned int getStepsPerRev();\n\n  /**\n   * Set the number of full steps (not microsteps!) required to move the axis by 1mm.\n   * This depends on the lead screw pitch (if lead screws are used) or the gear ratio or whatever mechanism is used to transform revolutions of the stepper motor to linear motion\n   * The default value is 100 steps/rev since this is the most common value for T8 leadscrews in regards to pitch.\n   */\n  void setStepsPerMM(unsigned int);\n\n  /**\n   * Get the currently configured steps/mm value for this steppe motor\n   * The default value is 100 steps/mm since standard T8 lead screws have a pitch of 2mm per rev, this, combined with the standard 200steps/rev of stepper motors, leads to 100 steps/mm\n   **/\n  unsigned int getStepsPerMM();\n\n  /**\n   * Set the number of microsteps you configured in the stepper driver (usually one with DIP switches on the driver board) for this stepper motor.\n   * Common values are 1 (no micro stepping), 2 (half step), 4, 8, 16, 32, 64, 128 and sometimes 256 microsteps per step.\n   * This setting is needed to calculate the proper amount of pulses that need to be send to the stepper driver.\n   * If this value does not match the configured micro step setting on your driver board, \n   * the number of pulses need to travel a certain distance in mm or to perform a certain amount of revolutions with the stepper motor, will not be correct.\n   * The default value is 1 (ESPSMS_MICROSTEPS_OFF).\n   * Allowed values: ESPSMS_MICROSTEPS_OFF (full stepping), ESPSMS_MICROSTEPS_2 (half stepping), ESPSMS_MICROSTEPS_4, ESPSMS_MICROSTEPS_8, ESPSMS_MICROSTEPS_16, ESPSMS_MICROSTEPS_32, ESPSMS_MICROSTEPS_64, ESPSMS_MICROSTEPS_128, ESPSMS_MICROSTEPS_256\n   */\n  void setMicrostepsPerStep(unsigned int);\n  /**\n   * Get the currently configured number of microsteps per step for this stepper motor.\n   * The default value is 1 (ESPSMS_MICROSTEPS_OFF)\n   **/\n  unsigned int getMicrostepsPerStep();\n\n  /**\n   * Set the maximum revolutions per minute for this stepper.\n   * This limit will only be used to limit the allowed values in the rest api enpoints / user interfaces and to calculate the maximum step pulse frequncy needed.\n   * If the step pulse frequnency is higher than the one your motor can handle, you might lose steps or the motor might stall.\n   * See your stepper motors datasheet for the torque curve an chose the limit that fits your needs (some datasheets specify the PPS (=Pulses per second) rather then revs/minutem, so make sure you get the right unit when settings this value).\n   * The default setting is 1200 revs/minute, which might be already to high for some steppers (especially geared ones or steppers driven with a low voltage)\n   **/\n  void setRpmLimit(unsigned int);\n  /**\n   * Get the currently configured Revolutions per Minute limit for this stepper. \n   * Default is 1200 revs/minute.\n   */\n  unsigned int getRpmLimit();\n\n  const static byte ESPServerStepperUnsetIoPinNumber = 255;\n\nprivate:\n  //\n  // private member variables\n  //\n  ESP_FlexyStepper *_flexyStepper;\n  String _displayName;\n  byte _stepperIndex = 0;\n  byte _stepIoPin = ESPServerStepperUnsetIoPinNumber;\n  byte _directionIoPin = ESPServerStepperUnsetIoPinNumber;\n  byte _brakeIoPin = ESPServerStepperUnsetIoPinNumber;\n  byte _brakePinActiveState = 1; // 1 = active high, 2 = active low\n  long _brakeEngageDelayMs = 0;\n  long _brakeReleaseDelayMs = -1;\n  unsigned int _stepsPerRev = 200;\n  unsigned int _stepsPerMM = 100;\n  unsigned int _microsteppingDivisor = ESPSMS_MICROSTEPS_OFF;\n  unsigned int _rpmLimit = 1200;\n};\n// ------------------------------------ End ---------------------------------\n#endif\n"
  },
  {
    "path": "src/ESPStepperMotorServer_WebInterface.cpp",
    "content": "\n//      *********************************************************\n//      *                                                       *\n//      *           ESP32 Stepper Motor Web Interface           *\n//      *                                                       *\n//      *            Copyright (c) Paul Kerspe, 2019            *\n//      *                                                       *\n//      **********************************************************\n\n// MIT License\n//\n// Copyright (c) 2019 Paul Kerspe\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 furnished\n// to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// 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 THE\n// SOFTWARE.\n\n#include <ESPStepperMotorServer_WebInterface.h>\n\nHTTPClient http;\n// ---------------------------------------------------------------------------------\n//                                  Setup functions\n// ---------------------------------------------------------------------------------\n\n//\n// constructor for the web user interface module\n//\nESPStepperMotorServer_WebInterface::ESPStepperMotorServer_WebInterface(ESPStepperMotorServer *serverRef)\n{\n    this->_serverRef = serverRef;\n    this->_httpServer = NULL;\n}\n\n/**\n * check if the UI files exist in the SPIFFS and then register all endpoints for the web UI in the http server\n */\nvoid ESPStepperMotorServer_WebInterface::registerWebInterfaceUrls(AsyncWebServer *httpServer)\n{\n    this->_httpServer = httpServer;\n\n    //OTA update form\n    this->_httpServer->on(\"/update\", HTTP_GET, [this](AsyncWebServerRequest *request) {\n        if (LittleFS.exists(this->webUiFirmwareUpdate))\n        {\n            AsyncWebServerResponse *response = request->beginResponse(LittleFS, this->webUiFirmwareUpdate, \"text/html\");\n            response->addHeader(\"Content-Encoding\", \"gzip\");\n            request->send(response);\n        }\n        else\n        {\n            request->send(200, \"text/html\", \"<html><body><h1>Firmware update</h1><form method='POST' action='#' enctype='multipart/form-data' id='upload_form'><p>Firmware File: <input type='file' accept='.bin' name='update'></p><p><input type='submit' value='Update'></p></form></body></html>\");\n        }\n    });\n\n    //OTA Update handler\n    this->_httpServer->on(\n        \"/update\", HTTP_POST, [this](AsyncWebServerRequest *request) {\n            // the request handler is triggered after the upload has finished... \n            AsyncWebServerResponse *response = request->beginResponse(200, \"text/plain\", (Update.hasError())?\"UPDATE FAILED\":\"SUCCESS. Rebooting server now\");\n            response->addHeader(\"Connection\", \"close\");\n            response->addHeader(\"Access-Control-Allow-Origin\", \"*\");\n            request->send(response);\n            if (!Update.hasError()) {\n                delay(100);\n                this->_serverRef->requestReboot(\"Firmware update completed\");\n            } },\n\n        //Upload handler to process chunks of data\n        [](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {\n            if (!filename.endsWith(\".bin\"))\n            {\n                Serial.println(\"Invalid firmware file provided, must have .bin-extension\");\n                request->send(400, \"text/plain\", \"Invalid fimrware file given\");\n                request->client()->close();\n            }\n            else\n            {\n                if (!index)\n                { // if index == 0 then this is the first frame of data\n                    Serial.printf(\"UploadStart: %s\\n\", filename.c_str());\n                    Serial.setDebugOutput(true);\n\n                    // calculate sketch space required for the update\n                    uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;\n                    if (!Update.begin(maxSketchSpace))\n                    { //start with max available size\n                        Update.printError(Serial);\n                    }\n                }\n\n                //Write chunked data to the free sketch space\n                if (Update.write(data, len) != len)\n                {\n                    Update.printError(Serial);\n                }\n\n                if (final)\n                { // if the final flag is set then this is the last frame of data\n                    if (Update.end(true))\n                    { //true to set the size to the current progress\n                        Serial.printf(\"Update Success: %u B\\nRebooting...\\n\", index + len);\n                    }\n                    else\n                    {\n                        Update.printError(Serial);\n                    }\n                    Serial.setDebugOutput(false);\n                }\n            }\n        });\n\n    if (this->_serverRef->isSPIFFSMounted() && checkIfGuiExistsInSpiffs())\n    {\n        this->_httpServer->on(\"/\", HTTP_GET, [this](AsyncWebServerRequest *request) {\n            request->send(LittleFS, this->webUiIndexFile);\n        });\n        this->_httpServer->on(this->webUiIndexFile, HTTP_GET, [this](AsyncWebServerRequest *request) {\n            request->send(LittleFS, this->webUiIndexFile);\n        });\n\n        this->_httpServer->on(this->webUiFaviconFile, HTTP_GET, [this](AsyncWebServerRequest *request) {\n            AsyncWebServerResponse *response = request->beginResponse(LittleFS, this->webUiFaviconFile, \"image/x-icon\");\n            //response->addHeader(\"Content-Encoding\", \"gzip\");\n            request->send(response);\n        });\n        /* REMOVED FOR NOW DUE TO SECURITY ISSUES. CAN STILL USE /api/config endpoint to download config (in memory config though!)\n        this->_httpServer->on(this->_serverRef->defaultConfigurationFilename, HTTP_GET, [this](AsyncWebServerRequest *request) {\n          //FIME: currently this streams the json config including the WiFi Credentials, which might be a security risk.\n          //TODO: replace wifi passwords in config with placeholder (maybe add config flag to API to allowed disabling the security feature in the future)\n          AsyncWebServerResponse *response = request->beginResponse(LittleFS, this->_serverRef->defaultConfigurationFilename, \"application/json\", true);\n          request->send(response);\n        });\n        */\n\n        this->_httpServer->on(\"/js/app.js\", HTTP_GET, [this](AsyncWebServerRequest *request) {\n            request->redirect(this->webUiJsFile);\n        });\n        this->_httpServer->on(this->webUiJsFile, HTTP_GET, [this](AsyncWebServerRequest *request) {\n            AsyncWebServerResponse *response = request->beginResponse(LittleFS, this->webUiJsFile, \"text/javascript\");\n            response->addHeader(\"Content-Encoding\", \"gzip\");\n            request->send(response);\n        });\n\n        //little test page to show contents of SPIFFS and check if it is initialized at all for trouble shooting\n        this->_httpServer->on(\"/selftest\", HTTP_GET, [this](AsyncWebServerRequest *request) {\n            AsyncResponseStream *response = request->beginResponseStream(\"text/html\");\n            response->print(\"<!DOCTYPE html><html lang=\\\"en\\\"><head><title>ESP-StepperMotorServer Test Page</title><link rel=\\\"stylesheet\\\" href=\\\"https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css\\\" crossorigin=\\\"anonymous\\\"></head><body>\");\n            response->print(\"<div class=\\\"container\\\"><h1>ESP-StepperMotorServer self test</h1><p>Testing environment:</p><ul>\");\n            response->printf(\"<li>Server Version: <strong>%s</strong>\", this->_serverRef->version);\n            response->printf(\"<li>LittleFS Initialized: %s</li>\", (this->_serverRef->isSPIFFSMounted()) ? \"<span class=\\\"badge badge-success\\\">true</span>\" : \"<span class=\\\"badge badge-danger\\\">false</span>\");\n            if (!this->_serverRef->isSPIFFSMounted())\n            {\n                response->print(\"ERROR: LittleFS not initialized & mounted, in case you are intending to use the WEB UI, you need to make sure the LittleFS Filesystem has been properly initialized on your ESP32\");\n            }\n            else\n            {\n                response->printf(\"<li>WEB UI installed completely: %s</li>\", (this->checkIfGuiExistsInSpiffs()) ? \"<span class=\\\"badge badge-success\\\">true</span>\" : \"<span class=\\\"badge badge-danger\\\">false</span>\");\n\n                File root = LittleFS.open(\"/\");\n                if (!root)\n                {\n                    response->print(\"ERROR: Failed to open root folder on LittleFS for reading\");\n                }\n                if (root.isDirectory())\n                {\n                    response->print(\"<li>Listing files in root folder of LittleFS:<ul>\");\n                    File file = root.openNextFile();\n                    while (file)\n                    {\n                        response->printf(\"<li>File: %s (%i) %ld</li>\", file.name(), file.size(), file.getLastWrite());\n                        file = root.openNextFile();\n                    }\n                    response->printf(\"</ul></li>\");\n                    root.close();\n                }\n            }\n            response->printf(\"</ul>\");\n            String output = String(\"\");\n            this->_serverRef->getServerStatusAsJsonString(output); //populate string with json\n            response->print(output);\n            response->print(\"</div></body></html>\");\n            //send the response last\n            request->send(response);\n        });\n\n        // register image paths with caching header present\n        this->_httpServer->on(this->webUiLogoFile, HTTP_GET, [this](AsyncWebServerRequest *request) {\n            AsyncWebServerResponse *response = request->beginResponse(LittleFS, this->webUiLogoFile, \"image/svg+xml\");\n            response->addHeader(\"Cache-Control\", \"max-age=36000, public\");\n            request->send(response);\n        });\n        this->_httpServer->on(this->webUiStepperGraphic, HTTP_GET, [this](AsyncWebServerRequest *request) {\n            AsyncWebServerResponse *response = request->beginResponse(LittleFS, this->webUiStepperGraphic, \"image/svg+xml\");\n            response->addHeader(\"Cache-Control\", \"max-age=36000, public\");\n            request->send(response);\n        });\n        this->_httpServer->on(this->webUiEncoderGraphic, HTTP_GET, [this](AsyncWebServerRequest *request) {\n            AsyncWebServerResponse *response = request->beginResponse(LittleFS, this->webUiEncoderGraphic, \"image/svg+xml\");\n            response->addHeader(\"Cache-Control\", \"max-age=36000, public\");\n            request->send(response);\n        });\n        this->_httpServer->on(this->webUiEmergencySwitchGraphic, HTTP_GET, [this](AsyncWebServerRequest *request) {\n            AsyncWebServerResponse *response = request->beginResponse(LittleFS, this->webUiEmergencySwitchGraphic, \"image/svg+xml\");\n            response->addHeader(\"Cache-Control\", \"max-age=36000, public\");\n            request->send(response);\n        });\n        this->_httpServer->on(this->webUiSwitchGraphic, HTTP_GET, [this](AsyncWebServerRequest *request) {\n            AsyncWebServerResponse *response = request->beginResponse(LittleFS, this->webUiSwitchGraphic, \"image/svg+xml\");\n            response->addHeader(\"Cache-Control\", \"max-age=36000, public\");\n            request->send(response);\n        });\n        this->_httpServer->onNotFound([this](AsyncWebServerRequest *request) {\n            request->send(404, \"text/html\", \"<html><body><h1>ESP-StepperMotor-Server</h1><p>The requested file could not be found</body></html>\");\n        });\n    }\n    else\n    {\n        ESPStepperMotorServer_Logger::logInfo(\"No web UI could be registered\");\n    }\n}\n\n/**\n * Checks if all required UI files exist in the SPIFFS.\n * Will try to download the current version of the files from the git hub repo if they could not be found\n */\nbool ESPStepperMotorServer_WebInterface::checkIfGuiExistsInSpiffs()\n{\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n    ESPStepperMotorServer_Logger::logDebug(\"Checking if web UI is installed in SPIFFS\");\n#endif\n\n    if (!this->_serverRef->isSPIFFSMounted())\n    {\n        ESPStepperMotorServer_Logger::logWarning(\"LittleFS is not mounted, UI files not found\");\n        return false;\n    }\n    else\n    {\n        bool uiComplete = true;\n        const char *notPresent = \"The file %s could not be found on LittleFS\\n\";\n        const char *files[] = {\n            this->webUiIndexFile,\n            this->webUiJsFile,\n            this->webUiLogoFile,\n            this->webUiFaviconFile,\n            this->webUiEncoderGraphic,\n            this->webUiEmergencySwitchGraphic,\n            this->webUiStepperGraphic,\n            this->webUiSwitchGraphic,\n            this->webUiFirmwareUpdate};\n\n        for (int i = 0; i < 9; i++) //ALWAYS UPDATE THIS COUNTER IF NEW FILES ARE ADDED TO UI\n        {\n            if (!LittleFS.exists(files[i]))\n            {\n                ESPStepperMotorServer_Logger::logInfof(notPresent, files[i]);\n                if (this->_serverRef->getCurrentServerConfiguration()->wifiMode == ESPServerWifiModeClient && WiFi.isConnected())\n                {\n                    char downloadUrl[200];\n                    strcpy(downloadUrl, webUiRepositoryBasePath);\n                    strcat(downloadUrl, files[i]);\n                    if (!this->downloadFileToSpiffs(downloadUrl, files[i]))\n                    {\n                        uiComplete = false;\n                    }\n                }\n                else\n                {\n                    uiComplete = false;\n                }\n            }\n        }\n\n        if (uiComplete == false && this->_serverRef->getCurrentServerConfiguration()->wifiMode == ESPServerWifiModeAccessPoint)\n        {\n            ESPStepperMotorServer_Logger::logWarning(\"The UI does not seem to be installed completely on SPIFFS. Automatic download failed since the server is in Access Point mode and not connected to the internet\");\n            ESPStepperMotorServer_Logger::logWarning(\"Start the server in wifi client (STA) mode to enable automatic download of the web interface files to SPIFFS\");\n        }\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n        else if (ESPStepperMotorServer_Logger::isDebugEnabled())\n        {\n            if (uiComplete == true)\n            {\n                ESPStepperMotorServer_Logger::logDebug(\"Check completed successfully\");\n            }\n            else\n            {\n                ESPStepperMotorServer_Logger::logDebug(\"Check failed, one or more UI files are missing and could not be downloaded automatically\");\n            }\n        }\n#endif\n        return uiComplete;\n    }\n}\n\n// Perform an HTTP GET request to a remote page to download a file to LittleFS\nbool ESPStepperMotorServer_WebInterface::downloadFileToSpiffs(const char *url, const char *targetPath)\n{\n    if (!this->_serverRef->isSPIFFSMounted())\n    {\n        ESPStepperMotorServer_Logger::logWarningf(\"downloading of %s was canceled since LittleFS is not mounted\\n\");\n        return false;\n    }\n    else\n    {\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n        ESPStepperMotorServer_Logger::logDebugf(\"downloading %s from %s\\n\", targetPath, url);\n#endif\n\n        if (http.begin(url))\n        {\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n            int httpCode = http.GET();\n            ESPStepperMotorServer_Logger::logDebugf(\"server responded with %i\\n\", httpCode);\n#endif\n\n            //////////////////\n            // get length of document (is -1 when Server sends no Content-Length header)\n            int len = http.getSize();\n            uint8_t buff[128] = {0};\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n            ESPStepperMotorServer_Logger::logDebugf(\"starting download stream for file size %i\\n\", len);\n#endif\n\n            WiFiClient *stream = &http.getStream();\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n            ESPStepperMotorServer_Logger::logDebug(\"opening file for writing\");\n#endif\n            File f = LittleFS.open(targetPath, \"w+\");\n\n            // read all data from server\n            while (http.connected() && (len > 0 || len == -1))\n            {\n                // get available data size\n                size_t size = stream->available();\n#ifndef ESPStepperMotorServer_COMPILE_NO_DEBUG\n                ESPStepperMotorServer_Logger::logDebugf(\"%i bytes available to read from stream\\n\", size);\n#endif\n\n                if (size)\n                {\n                    // read up to 128 byte\n                    int c = stream->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size));\n                    // write it to file\n                    for (int i = 0; i < c; i++)\n                    {\n                        f.write(buff[i]);\n                    }\n                    if (len > 0)\n                    {\n                        len -= c;\n                    }\n                }\n                delay(1);\n            }\n            f.close();\n            ESPStepperMotorServer_Logger::logInfof(\"Download of %s completed\\n\", targetPath);\n            http.end(); //Close connection\n        }\n\n        return LittleFS.exists(targetPath);\n    }\n}\n\n// -------------------------------------- End --------------------------------------\n"
  },
  {
    "path": "src/ESPStepperMotorServer_WebInterface.h",
    "content": "//      ******************************************************************\n//      *                                                                *\n//      *  Header file for ESPStepperMotorServer_WebInterface.cpp        *\n//      *                                                                *\n//      *               Copyright (c) Paul Kerspe, 2019                  *\n//      *                                                                *\n//      ******************************************************************\n\n// MIT License\n//\n// Copyright (c) 2019 Paul Kerspe\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 furnished\n// to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// 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 THE\n// SOFTWARE.\n\n#ifndef ESPStepperMotorServer_WebInterface_h\n#define ESPStepperMotorServer_WebInterface_h\n\n#include <Arduino.h>\n#include <HTTPClient.h>\n#include <ESPAsyncWebServer.h>\n#include <ESPStepperMotorServer_Logger.h>\n#include <ESPStepperMotorServer.h>\n#include <Update.h>\n#include <LittleFS.h>\n\n//need this forward declaration here due to circular dependency (use in constructor)\nclass ESPStepperMotorServer;\n\nclass ESPStepperMotorServer_WebInterface\n{\npublic:\n  ESPStepperMotorServer_WebInterface(ESPStepperMotorServer *serverRef);\n  void registerWebInterfaceUrls(AsyncWebServer *httpServer);\n\nprivate:\n  bool downloadFileToSpiffs(const char *url, const char *targetPath);\n  bool checkIfGuiExistsInSpiffs();\n\n  const char *webUiFirmwareUpdate = \"/upload.html.gz\";\n  const char *webUiIndexFile = \"/index.html\";\n  const char *webUiJsFile = \"/js/app.js.gz\";\n  const char *webUiLogoFile = \"/img/logo.svg\";\n  const char *webUiEncoderGraphic = \"/img/rotaryEncoderWheel.svg\";\n  const char *webUiEmergencySwitchGraphic = \"/img/emergencyStopSwitch.svg\";\n  const char *webUiStepperGraphic = \"/img/stepper.svg\";\n  const char *webUiSwitchGraphic = \"/img/switch.svg\";\n  const char *webUiFaviconFile = \"/favicon.ico\";\n  const char *webUiRepositoryBasePath = \"https://raw.githubusercontent.com/pkerspe/ESP-StepperMotor-Server-UI/master/data\";\n  ESPStepperMotorServer *_serverRef;\n  AsyncWebServer *_httpServer;\n};\n\n#endif\n"
  },
  {
    "path": "src/main.cpp",
    "content": "//      ******************************************************************\n//      *     Simple example for starting the stepper motor server       *\n//      *            Paul Kerspe                31.5.2020                *\n//      ******************************************************************\n//\n// This is the simplest example of how to start the ESP Stepper Motor Server with the Webinterface to perform all setup steps via the Web UI\n//\n// This library requires that your stepper motor be connected to the ESP32\n// using an external driver that has a \"Step and Direction\" interface.\n//\n// For all driver boards, it is VERY important that you set the motor\n// current before running the example. This is typically done by adjusting\n// a potentiometer on the board or using dip switches.\n// Read the driver board's documentation to learn how to configure the driver\n//\n// all you need to do, to get started with this example, is fill in your wifi credentials in lines 26/27, then compile and upload to your ESP32.\n// In order to use the Web Interface of the server, you need to upload the contents of the \"data\" folder in this example to the LittleFS of your ESP32\n//\n// for a detailed manual on how to use this library please visit: https://github.com/pkerspe/ESP-StepperMotor-Server/blob/master/README.md\n// ***********************************************************************\n#include <Arduino.h>\n#include <ESPStepperMotorServer.h>\n\nESPStepperMotorServer *stepperMotorServer;\n\nconst char *wifiName= \"<your wifi ssid>\"; // enter the SSID of the wifi network to connect to\nconst char *wifiSecret = \"<your wifi password>\"; // enter the password of the the existing wifi network here\n\nvoid setup() \n{\n  // start the serial interface with 115200 baud\n  // IMPORTANT: the following line is important, since the server relies on the serial console for log output \n  // Do not remove this line! (you can modify the baud rate to your needs though, but keep in mind, that slower baud rates might cause timing issues especially if you set the log level to DEBUG)\n  Serial.begin(115200);\n  // now create a new ESPStepperMotorServer instance (this must be done AFTER the Serial interface has been started)\n  // In this example We create the server instance with all modules activated and log level set to INFO (which is the default, you can also use ESPServerLogLevel_DEBUG to set it to debug instead)\n  stepperMotorServer = new ESPStepperMotorServer(ESPServerRestApiEnabled | ESPServerWebserverEnabled | ESPServerSerialEnabled, ESPServerLogLevel_INFO);\n  // connect to an existing WiFi network. Make sure you set the vairables wifiName and wifiSecret to match you SSID and wifi pasword (see above before the setup function)\n  stepperMotorServer->setWifiCredentials(wifiName, wifiSecret);\n  stepperMotorServer->setWifiMode(ESPServerWifiModeClient); //start the server as a wifi client (DHCP client of an existing wifi network)\n\n  // NOTE: if you want to start the server in a stand alone mode that opens a wifi access point, then comment out the above two lines and uncomment the following line\n  // stepperMotorServer->setWifiMode(ESPServerWifiModeAccessPoint);\n  // you can define the AP name and the password using the following two lines, otherwise the defaults will be used (Name: ESP-StepperMotor-Server, password: Aa123456)\n  // stepperMotorServer->setAccessPointName(\"<ap-name>\");\n  // stepperMotorServer->setAccessPointPassword(\"<ap password must be longer than 8 characters>\");\n\n  //start the server\n  stepperMotorServer->start();\n  // the server will now connect to the wifi and start the webserver, rest API and serial command line interface.\n  // check the serial console for more details like the URL of the WebInterface\n}\n\nvoid loop() \n{\n  //put your code here\n}\n"
  }
]