diff --git a/.github/workflows/Platform_build.yml b/.github/workflows/Platform_build.yml index fc448a7b..7c10bc47 100644 --- a/.github/workflows/Platform_build.yml +++ b/.github/workflows/Platform_build.yml @@ -18,7 +18,7 @@ jobs: name: Global setup runs-on: ubuntu-latest container: - image: sle118/squeezelite-esp32-idfv43 + image: sle118/squeezelite-esp32-idfv435 outputs: build_number: ${{ steps.buildnumber.outputs.build_number }} ui_build: ${{ steps.build_flags.outputs.ui_build }} @@ -99,7 +99,7 @@ jobs: build: runs-on: ubuntu-latest container: - image: sle118/squeezelite-esp32-idfv43 + image: sle118/squeezelite-esp32-idfv435 needs: [bootstrap] strategy: matrix: @@ -127,7 +127,7 @@ jobs: . /opt/esp/python_env/idf4.3_py3.8_env/bin/activate git config --global --add safe.directory /__w/squeezelite-esp32/squeezelite-esp32 git status - build_tools.py environment --build ${{ needs.bootstrap.outputs.build_number }} --env_file "$GITHUB_ENV" --node "${{matrix.node}}" --depth ${{matrix.depth}} --major 2 --docker sle118/squeezelite-esp32-idfv43 + build_tools.py environment --build ${{ needs.bootstrap.outputs.build_number }} --env_file "$GITHUB_ENV" --node "${{matrix.node}}" --depth ${{matrix.depth}} --major 2 --docker sle118/squeezelite-esp32-idfv435 - uses: actions/download-artifact@master name: Restore common objects @@ -137,11 +137,6 @@ jobs: if: ${{ needs.bootstrap.outputs.mock == 0 }} run: | . ${IDF_PYTHON_ENV_PATH}/bin/activate - chmod +x ./components/spotify/cspot/bell/external/nanopb/generator/protoc - chmod +x ./components/spotify/cspot/bell/external/nanopb/generator/protoc-gen-nanopb - chmod +x ./components/spotify/cspot/bell/external/nanopb/generator/*.py - chmod +x ./components/spotify/cspot/bell/external/nanopb/generator/*.py2 - chmod +x ./components/spotify/cspot/bell/external/nanopb/generator/proto/*.py echo "Copying target sdkconfig" cp build-scripts/${TARGET_BUILD_NAME}-sdkconfig.defaults sdkconfig echo "Building project" @@ -230,4 +225,4 @@ jobs: if: ${{ always() && !cancelled() && needs.bootstrap.outputs.release_flag == 1 && needs.bootstrap.outputs.mock == 0 }} uses: ./.github/workflows/web_deploy.yml secrets: - WEB_INSTALLER: ${{ secrets.WEB_INSTALLER }} \ No newline at end of file + WEB_INSTALLER: ${{ secrets.WEB_INSTALLER }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index fd209220..b32a91fb 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -19,6 +19,7 @@ on: branches: [ master-cmake ] schedule: - cron: '19 12 * * 4' + workflow_dispatch: jobs: analyze: @@ -39,7 +40,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -50,7 +51,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v2 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -62,6 +63,13 @@ jobs: #- run: | # make bootstrap # make release - + # Exclude specific artifacts from analysis + - name: Exclude Artifacts + run: | + # Exclude components/wifi-manager/webapp/dist/js/index* from analysis + echo 'components/wifi-manager/webapp/dist/js/index*' >> .codeql-exclude-paths + echo 'components/wifi-manager/webapp/dist/js/index*' >> .codeql-exclude-paths.txt + echo 'components/wifi-manager/webapp/dist/index*' >> .codeql-exclude-paths + echo 'components/wifi-manager/webapp/dist/index*' >> .codeql-exclude-paths.txt - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 diff --git a/CMakeLists.txt b/CMakeLists.txt index cef5add4..cc4177c1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,13 @@ cmake_minimum_required(VERSION 3.5) -set(EXTRA_COMPONENT_DIRS components/platform_console/app_recovery components/platform_console/app_squeezelite ) include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +set(PROJECT_VER $ENV{PROJECT_VER}) add_definitions(-DMODEL_NAME=SqueezeESP32) +if(NOT DEFINED DEPTH) + set(DEPTH "16") +endif() + # State machine hierarchy enabled and logging enabled add_definitions(-DSTATE_MACHINE_LOGGER=1) add_definitions(-DHIERARCHICAL_STATES=1) @@ -18,14 +23,113 @@ add_definitions(-DHIERARCHICAL_STATES=1) #add_definitions(-DNETWORK_MANAGER_LOG_LEVEL=ESP_LOG_DEBUG) #add_definitions(-DNETWORK_HTTP_SERVER_LOG_LEVEL=ESP_LOG_DEBUG) -if(NOT DEFINED DEPTH) - set(DEPTH "16") -endif() -message(STATUS "Building RECOVERY") +# utility to build sizes +function(build_size target_name) + set(target_elf ${target_name}.elf) + set(target_map ${target_name}.map) + set(idf_size ${python} ${IDF_PATH}/tools/idf_size.py) + + if(DEFINED OUTPUT_JSON AND OUTPUT_JSON) + list(APPEND idf_size "--json") + endif() + + add_custom_target(size-${target_name} ALL + DEPENDS ${target_elf} + COMMAND ${idf_size} ${target_map} -o "size-${target_name}" + ) + + add_custom_target(size-files-${target_name} ALL + DEPENDS ${target_elf} + COMMAND ${idf_size} --files ${target_map} + ) + + add_custom_target(size-components-${target_name} ALL + DEPENDS ${target_elf} + COMMAND ${idf_size} --archives ${target_map} + ) +endfunction() + +# manually add the 2 versions for application: recovery and squeezelite +set(EXTRA_COMPONENT_DIRS components/platform_console/app_recovery components/platform_console/app_squeezelite ) + project(recovery) -set_property(TARGET recovery.elf PROPERTY RECOVERY_PREFIX app_recovery ) -include(squeezelite.cmake) -set(PROJECT_VER $ENV{PROJECT_VER}) + +# we need own "esp_app_desc" to take precedence +add_custom_command( + TARGET recovery.elf + PRE_LINK + COMMAND xtensa-esp32-elf-objcopy --weaken-symbol esp_app_desc ${BUILD_DIR}/esp-idf/app_update/libapp_update.a + VERBATIM +) + +# when building recovery, add app_recovery to the link +get_target_property(BCA recovery.elf LINK_LIBRARIES) +list(REMOVE_ITEM BCA "idf::app_squeezelite" "idf::app_recovery" "-Wl,--Map=${BUILD_DIR}/recovery.map") +set_target_properties(recovery.elf PROPERTIES LINK_LIBRARIES "${BCA};idf::app_recovery;-Wl,--Map=${BUILD_DIR}/recovery.map") + +# create files with size for recovery +# build_size(recovery) + + +# build squeezelite, add app_squeezelite to the link +add_executable(squeezelite.elf "CMakeLists.txt") +add_dependencies(squeezelite.elf recovery.elf) +set_target_properties(squeezelite.elf PROPERTIES LINK_LIBRARIES "${BCA};idf::app_squeezelite;-Wl,--Map=${BUILD_DIR}/squeezelite.map") +add_custom_command( + TARGET squeezelite.elf + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E echo "Generating ${BUILD_DIR}/squeezelite.bin" + COMMAND ${ESPTOOLPY} elf2image ${ESPTOOLPY_FLASH_OPTIONS} ${ESPTOOLPY_ELF2IMAGE_OPTIONS} -o "squeezelite.bin" "squeezelite.elf" + VERBATIM +) + +# create files with size for squeezelite +# build_size(squeezelite) + +# make it part of cleanup +set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + APPEND PROPERTY + ADDITIONAL_MAKE_CLEAN_FILES "${BUILD_DIR}/squeezelite.elf" "${BUILD_DIR}/squeezelite.map" +) + +# adding OTA_0 partition +partition_table_get_partition_info(otaapp_offset "--partition-type app --partition-subtype ota_0" "offset") +idf_component_get_property(main_args esptool_py FLASH_ARGS) +idf_component_get_property(sub_args esptool_py FLASH_SUB_ARGS) +esptool_py_flash_target(squeezelite-flash "${main_args}" "${sub_args}") +esptool_py_flash_target_image(squeezelite-flash squeezelite "${otaapp_offset}" "${BUILD_DIR}/squeezelite.bin") +esptool_py_flash_target_image(flash squeezelite "${otaapp_offset}" "${BUILD_DIR}/squeezelite.bin") + +# and JTAG scripts +add_custom_target(_jtag_scripts ALL + BYPRODUCTS "flash_dbg_project_args" + POST_BUILD + COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_SOURCE_DIR}/generate_debug_scripts.cmake" +) + +if(CMAKE_HOST_UNIX) + # Add custom target to set executable permissions before build for cspot component + add_custom_target(set_cspot_permissions + COMMAND ${CMAKE_COMMAND} -E echo "************************************************************************************************" + COMMAND ${CMAKE_COMMAND} -E echo "**** Setting permissions for required files" + COMMAND ${CMAKE_COMMAND} -E echo "**** ${CMAKE_SOURCE_DIR}/components/spotify/cspot/bell/external/nanopb/generator/protoc-gen-nanopb" + COMMAND ${CMAKE_COMMAND} -E echo "**** ${CMAKE_SOURCE_DIR}/components/spotify/cspot/bell/external/nanopb/generator/*.py" + COMMAND ${CMAKE_COMMAND} -E echo "**** ${CMAKE_SOURCE_DIR}/components/spotify/cspot/bell/external/nanopb/generator/*.py2" + COMMAND ${CMAKE_COMMAND} -E echo "**** ${CMAKE_SOURCE_DIR}/components/spotify/cspot/bell/external/nanopb/generator/proto/*.py" + COMMAND ${CMAKE_COMMAND} -E echo "**** ${CMAKE_SOURCE_DIR}/components/spotify/cspot/bell/external/nanopb/generator/protoc" + COMMAND chmod +x ${CMAKE_SOURCE_DIR}/components/spotify/cspot/bell/external/nanopb/generator/protoc-gen-nanopb + COMMAND chmod +x ${CMAKE_SOURCE_DIR}/components/spotify/cspot/bell/external/nanopb/generator/*.py + COMMAND chmod +x ${CMAKE_SOURCE_DIR}/components/spotify/cspot/bell/external/nanopb/generator/*.py2 + COMMAND chmod +x ${CMAKE_SOURCE_DIR}/components/spotify/cspot/bell/external/nanopb/generator/proto/*.py + COMMAND chmod +x ${CMAKE_SOURCE_DIR}/components/spotify/cspot/bell/external/nanopb/generator/protoc + COMMAND ${CMAKE_COMMAND} -E echo "************************************************************************************************" + ) + + # Add a dependency to ensure permissions are set before building cspot component + add_dependencies(__idf_spotify set_cspot_permissions) +endif() + +# ======================= DEBUG FLAGS ============================ #target_compile_definitions(__idf_esp_eth PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_INFO) diff --git a/Dockerfile b/Dockerfile index 760f71f9..fb8815c6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,29 +2,34 @@ FROM ubuntu:20.04 ARG DEBIAN_FRONTEND=noninteractive -ENV GCC_TOOLS_BASE=/opt/esp/tools/xtensa-esp32-elf/esp-2021r2-8.4.0/xtensa-esp32-elf/bin/xtensa-esp32-elf- +ENV GCC_TOOLS_BASE=/opt/esp/tools/xtensa-esp32-elf/esp-2021r2-patch3-8.4.0/xtensa-esp32-elf/bin/xtensa-esp32-elf- # To build the image for a branch or a tag of IDF, pass --build-arg IDF_CLONE_BRANCH_OR_TAG=name. # To build the image with a specific commit ID of IDF, pass --build-arg IDF_CHECKOUT_REF=commit-id. # It is possibe to combine both, e.g.: # IDF_CLONE_BRANCH_OR_TAG=release/vX.Y # IDF_CHECKOUT_REF=. -# The following commit contains the ldgen fix: eab738c79e063b3d6f4c345ea5e1d4f8caef725b -# to build an image using that commit: docker build . --build-arg IDF_CHECKOUT_REF=eab738c79e063b3d6f4c345ea5e1d4f8caef725b -t sle118/squeezelite-esp32-idfv43 -# Docker build for release 4.3.2 as of 2022/02/28 -# docker build . --build-arg IDF_CHECKOUT_REF=8bf14a9238329954c7c5062eeeda569529aedf75 -t sle118/squeezelite-esp32-idfv43 -# To run the image interactive (windows): -# docker run --rm -v %cd%:/project -w /project -it sle118/squeezelite-esp32-idfv43 -# To run the image interactive (linux): -# docker run --rm -v `pwd`:/project -w /project -it sle118/squeezelite-esp32-idfv43 +# Docker build for release 4.3.5 as of 2023/05/18 +# docker build . --build-arg IDF_CHECKOUT_REF=6d04316cbe4dc35ea7e4885e9821bd9958ac996d -t sle118/squeezelite-esp32-idfv435 +# Updating the docker image in the repository +# docker push sle118/squeezelite-esp32-idfv435 +# or to do both: +# docker build . --build-arg IDF_CHECKOUT_REF=6d04316cbe4dc35ea7e4885e9821bd9958ac996d -t sle118/squeezelite-esp32-idfv435 && docker push sle118/squeezelite-esp32-idfv435 +# +# (windows) To run the image interactive : +# docker run --rm -v %cd%:/project -w /project -it sle118/squeezelite-esp32-idfv435 +# (windows powershell) +# docker run --rm -v ${PWD}:/project -w /project -it sle118/squeezelite-esp32-idfv435 +# (linux) To run the image interactive : +# docker run --rm -v `pwd`:/project -w /project -it sle118/squeezelite-esp32-idfv435 # to build the web app inside of the interactive session # pushd components/wifi-manager/webapp/ && npm install && npm run-script build && popd # # to run the docker with netwotrk port published on the host: -# docker run --rm -p 5000:5000/tcp -v %cd%:/project -w /project -it sle118/squeezelite-esp32-idfv43 +# docker run --rm -p 5000:5000/tcp -v %cd%:/project -w /project -it sle118/squeezelite-esp32-idfv435 ARG IDF_CLONE_URL=https://github.com/espressif/esp-idf.git ARG IDF_CLONE_BRANCH_OR_TAG=master -ARG IDF_CHECKOUT_REF=8bf14a9238329954c7c5062eeeda569529aedf75 +ARG IDF_CHECKOUT_REF=6d04316cbe4dc35ea7e4885e9821bd9958ac996d ENV IDF_PATH=/opt/esp/idf ENV IDF_TOOLS_PATH=/opt/esp @@ -35,6 +40,7 @@ RUN : \ && apt-get update \ && apt-get install -y \ apt-utils \ + build-essential \ bison \ ca-certificates \ ccache \ @@ -42,22 +48,26 @@ RUN : \ curl \ flex \ git \ + git-lfs \ gperf \ lcov \ + libbsd-dev \ + libpython3.8 \ libffi-dev \ libncurses-dev \ - libpython2.7 \ libusb-1.0-0-dev \ make \ ninja-build \ - python3 \ + python3.8 \ python3-pip \ + python3-venv \ + ruby \ unzip \ wget \ xz-utils \ zip \ - npm \ - nodejs \ + npm \ + nodejs \ && apt-get autoremove -y \ && rm -rf /var/lib/apt/lists/* \ && update-alternatives --install /usr/bin/python python /usr/bin/python3 10 \ @@ -97,8 +107,8 @@ RUN : \ COPY docker/patches $IDF_PATH #set idf environment variabies -ENV PATH /opt/esp/idf/components/esptool_py/esptool:/opt/esp/idf/components/espcoredump:/opt/esp/idf/components/partition_table:/opt/esp/idf/components/app_update:/opt/esp/tools/xtensa-esp32-elf/esp-2021r2-8.4.0/xtensa-esp32-elf/bin:/opt/esp/tools/xtensa-esp32s2-elf/esp-2021r2-8.4.0/xtensa-esp32s2-elf/bin:/opt/esp/tools/xtensa-esp32s3-elf/esp-2021r2-8.4.0/xtensa-esp32s3-elf/bin:/opt/esp/tools/riscv32-esp-elf/esp-2021r2-8.4.0/riscv32-esp-elf/bin:/opt/esp/tools/esp32ulp-elf/2.28.51-esp-20191205/esp32ulp-elf-binutils/bin:/opt/esp/tools/esp32s2ulp-elf/2.28.51-esp-20191205/esp32s2ulp-elf-binutils/bin:/opt/esp/tools/cmake/3.16.4/bin:/opt/esp/tools/openocd-esp32/v0.10.0-esp32-20211111/openocd-esp32/bin:/opt/esp/python_env/idf4.3_py3.8_env/bin:/opt/esp/idf/tools:$PATH -ENV GCC_TOOLS_BASE="/opt/esp/tools/xtensa-esp32-elf/esp-2021r2-8.4.0/xtensa-esp32-elf/bin/xtensa-esp32-elf-" +ENV PATH /opt/esp/idf/components/esptool_py/esptool:/opt/esp/idf/components/espcoredump:/opt/esp/idf/components/partition_table:/opt/esp/idf/components/app_update:/opt/esp/tools/xtensa-esp32-elf/esp-2021r2-patch3-8.4.0/xtensa-esp32-elf/bin:/opt/esp/tools/xtensa-esp32s2-elf/esp-2021r2-patch3-8.4.0/xtensa-esp32s2-elf/bin:/opt/esp/tools/xtensa-esp32s3-elf/esp-2021r2-patch3-8.4.0/xtensa-esp32s3-elf/bin:/opt/esp/tools/riscv32-esp-elf/esp-2021r2-patch3-8.4.0/riscv32-esp-elf/bin:/opt/esp/tools/esp32ulp-elf/2.28.51-esp-20191205/esp32ulp-elf-binutils/bin:/opt/esp/tools/esp32s2ulp-elf/2.28.51-esp-20191205/esp32s2ulp-elf-binutils/bin:/opt/esp/tools/cmake/3.16.4/bin:/opt/esp/tools/openocd-esp32/v0.11.0-esp32-20220706/openocd-esp32/bin:/opt/esp/python_env/idf4.3_py3.8_env/bin:/opt/esp/idf/tools:$PATH +ENV GCC_TOOLS_BASE="/opt/esp/tools/xtensa-esp32-elf/esp-2021r2-patch3-8.4.0/xtensa-esp32-elf/bin/xtensa-esp32-elf-" ENV IDF_PATH="/opt/esp/idf" ENV IDF_PYTHON_ENV_PATH="/opt/esp/python_env/idf4.3_py3.8_env" ENV IDF_TOOLS_EXPORT_CMD="/opt/esp/idf/export.sh" @@ -109,7 +119,32 @@ ENV NODE_VERSION="8" ENV OPENOCD_SCRIPTS="/opt/esp/tools/openocd-esp32/v0.10.0-esp32-20211111/openocd-esp32/share/openocd/scripts" # Ccache is installed, enable it by default +# The constraint file has been downloaded and the right Python package versions installed. No need to check and +# download this at every invocation of the container. +ENV IDF_PYTHON_CHECK_CONSTRAINTS=no + +# Ccache is installed, enable it by default ENV IDF_CCACHE_ENABLE=1 + +# Install QEMU runtime dependencies +RUN : \ + && apt-get update && apt-get install -y -q \ + libglib2.0-0 \ + libpixman-1-0 \ + && rm -rf /var/lib/apt/lists/* \ + && : + +# Install QEMU +ARG QEMU_VER=esp-develop-20220919 +ARG QEMU_DIST=qemu-${QEMU_VER}.tar.bz2 +ARG QEMU_SHA256=f6565d3f0d1e463a63a7f81aec94cce62df662bd42fc7606de4b4418ed55f870 +RUN : \ + && wget --no-verbose https://github.com/espressif/qemu/releases/download/${QEMU_VER}/${QEMU_DIST} \ + && echo "${QEMU_SHA256} *${QEMU_DIST}" | sha256sum --check --strict - \ + && tar -xf ${QEMU_DIST} -C /opt \ + && rm ${QEMU_DIST} \ + && : + COPY docker/entrypoint.sh /opt/esp/entrypoint.sh COPY components/wifi-manager/webapp/package.json /opt @@ -142,6 +177,9 @@ RUN : \ && node --version \ && npm install -g \ && : +RUN : \ + && npm install -g html-webpack-plugin + ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules ENV PATH $IDF_PYTHON_ENV_PATH:$NVM_DIR/v$NODE_VERSION/bin:$PATH @@ -152,5 +190,7 @@ RUN : \ && chmod +x /usr/sbin/build_tools.py \ && : + + ENTRYPOINT [ "/opt/esp/entrypoint.sh" ] CMD [ "/bin/bash" ] diff --git a/README.md b/README.md index 3cb81836..d1547a81 100644 --- a/README.md +++ b/README.md @@ -83,13 +83,13 @@ NB: You can use the pre-build binaries Muse4MBFlash which has all the hardware I - target: `muse` - bat_config: `channel=5,scale=7.48,atten=3,cells=1` - spi_config: `"mosi=15,miso=2,clk=14` *(this one is probably optional)* -- dac_config: `model=I2S,bck=5,ws=25,do=26,di=35,i2c=16,sda=18,scl=23,mck` +- dac_config: `model=I2S,bck=5,ws=25,do=26,di=35,i2c=16,sda=18,scl=23,mck=0` - dac_controlset: `{"init":[ {"reg":0,"val":128}, {"reg":0,"val":0}, {"reg":25,"val":4}, {"reg":1,"val":80}, {"reg":2,"val":0}, {"reg":8,"val":0}, {"reg":4,"val":192}, {"reg":0,"val":18}, {"reg":1,"val":0}, {"reg":23,"val":24}, {"reg":24,"val":2}, {"reg":38,"val":9}, {"reg":39,"val":144}, {"reg":42,"val":144}, {"reg":43,"val":128}, {"reg":45,"val":128}, {"reg":27,"val":0}, {"reg":26,"val":0}, {"reg":2,"val":240}, {"reg":2,"val":0}, {"reg":29,"val":28}, {"reg":4,"val":48}, {"reg":25,"val":0}, {"reg":46,"val":33}, {"reg":47,"val":33} ]}` - actrls_config: buttons - define a "buttons" variable with: `[{"gpio":32, "pull":true, "debounce":10, "normal":{"pressed":"ACTRLS_VOLDOWN"}}, {"gpio":19, "pull":true, "debounce":40, "normal":{"pressed":"ACTRLS_VOLUP"}}, {"gpio":12, "pull":true, "debounce":40, "long_press":1000, "normal":{"pressed":"ACTRLS_TOGGLE"},"longpress":{"pressed":"ACTRLS_POWER"}}]` ### ESP32-A1S -Works with [ESP32-A1S](https://docs.ai-thinker.com/esp32-a1s) module that includes audio codec and headset output. You still need to use a demo board like [this](https://www.aliexpress.com/item/4001060963585.html) or an external amplifier if you want direct speaker connection. Note that there is a version with AC101 codec and another one with ES8388 with probably two variants - these boards are a mess (see below) +Works with [ESP32-A1S](https://docs.ai-thinker.com/esp32-a1s) module that includes audio codec and headset output. You still need to use a demo board like [this](https://aliexpress.com/item/4000130915903.html) or an external amplifier if you want direct speaker connection. Note that there is a version with AC101 codec and another one with ES8388 with probably two variants - these boards are a mess (see below) The board shown above has the following IO set - amplifier: GPIO21 @@ -174,9 +174,9 @@ Default and only "host" is 1 as others are used already by flash and spiram. The ### DAC/I2S The NVS parameter "dac_config" set the gpio used for i2s communication with your DAC. You can define the defaults at compile time but nvs parameter takes precedence except for SqueezeAMP and A1S where these are forced at runtime. Syntax is ``` -bck=,ws=,do=[,mck][,mute=[:0|1][,model=TAS57xx|TAS5713|AC101|I2S][,sda=,scl=gpio[,i2c=]] +bck=,ws=,do=[,mck=0|1|2][,mute=[:0|1][,model=TAS57xx|TAS5713|AC101|I2S][,sda=,scl=[,i2c=]] ``` -if "model" is not set or is not recognized, then default "I2S" is used. The option "mck" is used for some codecs that require a master clock (although they should not). Only GPIO0 can be used as MCLK and be aware that this cannot coexit with RMII Ethernet (see ethernet section below). I2C parameters are optional and only needed if your DAC requires an I2C control (See 'dac_controlset' below). Note that "i2c" parameters are decimal, hex notation is not allowed. +if "model" is not set or is not recognized, then default "I2S" is used. The option "mck" is used for some codecs that require a master clock (although they should not). By default GPIO0 is used as MCLK and only recent builds (none as or 2023/05/15) can use 1 or 2. Also be aware that this cannot coexit with RMII Ethernet (see ethernet section below). I2C parameters are optional and only needed if your DAC requires an I2C control (See 'dac_controlset' below). Note that "i2c" parameters are decimal, hex notation is not allowed. So far, TAS57xx, TAS5713, AC101, WM8978 and ES8388 are recognized models where the proper init sequence/volume/power controls are sent. For other codecs that might require an I2C commands, please use the parameter "dac_controlset" that allows definition of simple commands to be sent over i2c for init, power, speakder and headset on and off using a JSON syntax: ```json @@ -305,7 +305,7 @@ See [set_GPIO](#set-gpio) for how to set the green and red LEDs. In addition, th ``` [green=0..100][,red=0..100] ``` -NB: For well-known configuration, this is ignored +NB: For well-known configuration, GPIO affected to green and red LED cannot be changed but brightness option applies ### LED Strip One LED strip with up to 255 addressable LEDs can be configured to offer enhanced visualizations. The LED strip can also be controlled remotely though the LMS server (using the CLI interface). Currently only WS2812B LEDs are supported. Set the LED Strip configuration (or NVS led_vu_config) to `WS2812,length=,gpio=, where is the number of leds in the strip (1..255), and is the data pin.` diff --git a/build-scripts/I2S-4MFlash-sdkconfig.defaults b/build-scripts/I2S-4MFlash-sdkconfig.defaults index 7e6a9138..20e876ba 100644 --- a/build-scripts/I2S-4MFlash-sdkconfig.defaults +++ b/build-scripts/I2S-4MFlash-sdkconfig.defaults @@ -216,6 +216,7 @@ CONFIG_TARGET="" # I2S settings # CONFIG_I2S_NUM=0 +CONFIG_I2S_MCK_IO=-1 CONFIG_I2S_BCK_IO=-1 CONFIG_I2S_WS_IO=-1 CONFIG_I2S_DO_IO=-1 diff --git a/build-scripts/Muse-sdkconfig.defaults b/build-scripts/Muse-sdkconfig.defaults index d001b283..76bad083 100644 --- a/build-scripts/Muse-sdkconfig.defaults +++ b/build-scripts/Muse-sdkconfig.defaults @@ -217,6 +217,7 @@ CONFIG_TARGET="muse" # I2S settings # CONFIG_I2S_NUM=0 +CONFIG_I2S_MCK_IO=-1 CONFIG_I2S_BCK_IO=-1 CONFIG_I2S_WS_IO=-1 CONFIG_I2S_DO_IO=-1 diff --git a/build-scripts/SqueezeAmp-sdkconfig.defaults b/build-scripts/SqueezeAmp-sdkconfig.defaults index 2f37e09a..1b5fdd5d 100644 --- a/build-scripts/SqueezeAmp-sdkconfig.defaults +++ b/build-scripts/SqueezeAmp-sdkconfig.defaults @@ -209,6 +209,7 @@ CONFIG_TARGET="squeezeamp" # I2S settings # CONFIG_I2S_NUM=0 +CONFIG_I2S_MCK_IO=-1 CONFIG_I2S_BCK_IO=-1 CONFIG_I2S_WS_IO=-1 CONFIG_I2S_DO_IO=-1 diff --git a/components/_override/CMakeLists.txt b/components/_override/CMakeLists.txt index 96fc7555..02f7d5c3 100644 --- a/components/_override/CMakeLists.txt +++ b/components/_override/CMakeLists.txt @@ -1,21 +1,22 @@ -set(lib_dir ${build_dir}/esp-idf) +if(IDF_TARGET STREQUAL esp32) + set(lib_dir ${build_dir}/esp-idf) + set(driver esp32/i2s.c esp32/i2s_hal.c) + string(REPLACE ".c" ".c.obj" driver_obj "${driver}") -set(driver i2s.c i2s_hal.c spi_bus_lock.c) -string(REPLACE ".c" ".c.obj" driver_obj "${driver}") + idf_component_register( SRCS ${driver} + REQUIRES driver + INCLUDE_DIRS ${IDF_PATH}/components/driver + PRIV_INCLUDE_DIRS ${IDF_PATH}/components/driver/include/driver + ) -idf_component_register( SRCS ${driver} - REQUIRES driver - INCLUDE_DIRS ${IDF_PATH}/components/driver - PRIV_INCLUDE_DIRS ${IDF_PATH}/components/driver/include/driver -) - -# CMake is just a pile of crap -message("!! overriding ${driver} !!") -message("CAREFUL, LIBRARIES STRIPPING FROM DUPLICATED COMPONENTS DEPENDS ON THIS BEING REBUILD") + # CMake is just a pile of crap + message(STATUS "!! overriding ${driver} !!") + message(STATUS "CAREFUL, LIBRARIES STRIPPING FROM DUPLICATED COMPONENTS DEPENDS ON THIS BEING REBUILD") -add_custom_command( - TARGET ${COMPONENT_LIB} - PRE_LINK - COMMAND xtensa-esp32-elf-ar -d ${lib_dir}/driver/libdriver.a ${driver_obj} - VERBATIM -) \ No newline at end of file + add_custom_command( + TARGET ${COMPONENT_LIB} + PRE_LINK + COMMAND xtensa-esp32-elf-ar -d ${lib_dir}/driver/libdriver.a ${driver_obj} + VERBATIM + ) +endif() diff --git a/components/_override/i2s.c b/components/_override/esp32/i2s.c similarity index 99% rename from components/_override/i2s.c rename to components/_override/esp32/i2s.c index bb20c5e2..654a36be 100644 --- a/components/_override/i2s.c +++ b/components/_override/esp32/i2s.c @@ -38,7 +38,8 @@ #include "esp_attr.h" #include "esp_log.h" #include "esp_pm.h" -#include "esp_efuse.h" +#include "soc/chip_revision.h" +#include "hal/efuse_hal.h" #include "esp_rom_gpio.h" #include "sdkconfig.h" @@ -193,7 +194,7 @@ static float i2s_apll_get_fi2s(int bits_per_sample, int sdm0, int sdm1, int sdm2 #if CONFIG_IDF_TARGET_ESP32 /* ESP32 rev0 silicon issue for APLL range/accuracy, please see ESP32 ECO document for more information on this */ - if (esp_efuse_get_chip_ver() == 0) { + if (!ESP_CHIP_REV_ABOVE(efuse_hal_chip_revision(), 100)) { sdm0 = 0; sdm1 = 0; } diff --git a/components/_override/i2s_hal.c b/components/_override/esp32/i2s_hal.c similarity index 100% rename from components/_override/i2s_hal.c rename to components/_override/esp32/i2s_hal.c diff --git a/components/_override/spi_bus_lock.c b/components/_override/spi_bus_lock.c deleted file mode 100644 index 4dc2c9e0..00000000 --- a/components/_override/spi_bus_lock.c +++ /dev/null @@ -1,849 +0,0 @@ -// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "freertos/FreeRTOS.h" -#include "freertos/semphr.h" -#include -#include "sdkconfig.h" -#include "spi_common_internal.h" -#include "esp_intr_alloc.h" -#include "soc/soc_caps.h" -#include "stdatomic.h" -#include "esp_log.h" -#include -#include "esp_heap_caps.h" - -/* - * This lock is designed to solve the conflicts between SPI devices (used in tasks) and - * the background operations (ISR or cache access). - * - * There are N (device/task) + 1 (BG) acquiring processer candidates that may touch the bus. - * - * The core of the lock is a `status` atomic variable, which is always available. No intermediate - * status is allowed. The atomic operations (mainly `atomic_fetch_and`, `atomic_fetch_or`) - * atomically read the status, and bitwisely write status value ORed / ANDed with given masks. - * - * Definitions of the status: - * - [30] WEAK_BG_FLAG, active when the BG is the cache - * - [29:20] LOCK bits, active when corresponding device is asking for acquiring - * - [19:10] PENDING bits, active when the BG acknowledges the REQ bits, but hasn't fully handled them. - * - [ 9: 0] REQ bits, active when corresponding device is requesting for BG operations. - * - * The REQ bits together PENDING bits are called BG bits, which represent the actual BG request - * state of devices. Either one of REQ or PENDING being active indicates the device has pending BG - * requests. Reason of having two bits instead of one is in the appendix below. - * - * Acquiring processer means the current processor (task or ISR) allowed to touch the critical - * resources, or the SPI bus. - * - * States of the lock: - * - STATE_IDLE: There's no acquiring processor. No device is acquiring the bus, and no BG - * operation is in progress. - * - * - STATE_ACQ: The acquiring processor is a device task. This means one of the devices is - * acquiring the bus. - * - * - STATE_BG: The acquiring processor is the ISR, and there is no acquiring device. - * - * - STATE_BG_ACQ: The acquiring processor is the ISR, and there is an acquiring device. - * - * - * Whenever a bit is written to the status, it means the a device on a task is trying to acquire - * the lock (either for the task, or the ISR). When there is no LOCK bits or BG bits active, the - * caller immediately become the acquiring processor. Otherwise, the task has to block, and the ISR - * will not be invoked until scheduled by the current acquiring processor. - * - * The acquiring processor is responsible to assign the next acquiring processor by calling the - * scheduler, usually after it finishes some requests, and cleared the corresponding status bit. - * But there is one exception, when the last bit is cleared from the status, after which there is - * no other LOCK bits or BG bits active, the acquiring processor lost its role immediately, and - * don't need to call the scheduler to assign the next acquiring processor. - * - * The acquiring processor may also choose to assign a new acquiring device when there is no, by - * calling `spi_bus_lock_bg_rotate_acq_dev` in the ISR. But the acquiring processor, in this case, - * is still the ISR, until it calls the scheduler. - * - * - * Transition of the FSM: - * - * - STATE_IDLE: no acquiring device, nor acquiring processor, no LOCK or BG bits active - * -> STATE_BG: by `req_core` - * -> STATE_ACQ: by `acquire_core` - * - * - STATE_BG: - * * No acquiring device, the ISR is the acquiring processor, there is BG bits active, but no LOCK - * bits - * * The BG operation should be enabled while turning into this state. - * - * -> STATE_IDLE: by `bg_exit_core` after `clear_pend_core` for all BG bits - * -> STATE_BG_ACQ: by `schedule_core`, when there is new LOCK bit set (by `acquire_core`) - * - * - STATE_BG_ACQ: - * * There is acquiring device, the ISR is the acquiring processor, there may be BG bits active for - * the acquiring device. - * * The BG operation should be enabled while turning into this state. - * - * -> STATE_ACQ: by `bg_exit_core` after `clear_pend_core` for all BG bits for the acquiring - * device. - * - * Should not go to the STATE_ACQ (unblock the acquiring task) until all requests of the - * acquiring device are finished. This is to preserve the sequence of foreground (polling) and - * background operations of the device. The background operations queued before the acquiring - * should be completed first. - * - * - STATE_ACQ: - * * There is acquiring device, the task is the acquiring processor, there is no BG bits active for - * the acquiring device. - * * The acquiring task (if blocked at `spi_bus_lock_acquire_start` or `spi_bus_lock_wait_bg_done`) - * should be resumed while turning into this state. - * - * -> STATE_BG_ACQ: by `req_core` - * -> STATE_BG_ACQ (other device): by `acquire_end_core`, when there is LOCK bit for another - * device, and the new acquiring device has active BG bits. - * -> STATE_ACQ (other device): by `acquire_end_core`, when there is LOCK bit for another devices, - * but the new acquiring device has no active BG bits. - * -> STATE_BG: by `acquire_end_core` when there is no LOCK bit active, but there are active BG - * bits. - * -> STATE_IDLE: by `acquire_end_core` when there is no LOCK bit, nor BG bit active. - * - * The `req_core` used in the task is a little special. It asks for acquiring processor for the - * ISR. When it succeed for the first time, it will invoke the ISR (hence passing the acquiring - * role to the BG). Otherwise it will not block, the ISR will be automatically be invoked by other - * acquiring processor. The caller of `req_core` will never become acquiring processor by this - * function. - * - * - * Appendix: The design, that having both request bit and pending bit, is to solve the - * concurrency issue between tasks and the bg, when the task can queue several requests, - * however the request bit cannot represent the number of requests queued. - * - * Here's the workflow of task and ISR work concurrently: - * - Task: (a) Write to Queue -> (b) Write request bit - * The Task have to write request bit (b) after the data is prepared in the queue (a), - * otherwise the BG may fail to read from the queue when it sees the request bit set. - * - * - BG: (c) Read queue -> (d) Clear request bit - * Since the BG cannot know the number of requests queued, it have to repeatedly check the - * queue (c), until it find the data is empty, and then clear the request bit (d). - * - * The events are possible to happen in the order: (c) -> (a) -> (b) -> (d). This may cause a false - * clear of the request bit. And there will be data prepared in the queue, but the request bit is - * inactive. - * - * (e) move REQ bits to PEND bits, happen before (c) is introduced to solve this problem. In this - * case (d) is changed to clear the PEND bit. Even if (e) -> (c) -> (a) -> (b) -> (d), only PEND - * bit is cleared, while the REQ bit is still active. - */ - -struct spi_bus_lock_dev_t; -typedef struct spi_bus_lock_dev_t spi_bus_lock_dev_t; - -typedef struct spi_bus_lock_t spi_bus_lock_t; - - -#define MAX_DEV_NUM 10 - -// Bit 29-20: lock bits, Bit 19-10: pending bits -// Bit 9-0: request bits, Bit 30: -#define LOCK_SHIFT 20 -#define PENDING_SHIFT 10 -#define REQ_SHIFT 0 - -#define WEAK_BG_FLAG BIT(30) /**< The bus is permanently requested by background operations. - * This flag is weak, will not prevent acquiring of devices. But will help the BG to be re-enabled again after the bus is release. - */ - -// get the bit mask wher bit [high-1, low] are all 1'b1 s. -#define BIT1_MASK(high, low) ((UINT32_MAX << (high)) ^ (UINT32_MAX << (low))) - -#define LOCK_BIT(mask) ((mask) << LOCK_SHIFT) -#define REQUEST_BIT(mask) ((mask) << REQ_SHIFT) -#define PENDING_BIT(mask) ((mask) << PENDING_SHIFT) -#define DEV_MASK(id) (LOCK_BIT(1<mask & REQ_MASK) -#define DEV_PEND_MASK(dev) ((dev)->mask & PEND_MASK) -#define DEV_BG_MASK(dev) ((dev)->mask & BG_MASK) - -struct spi_bus_lock_t { - /** - * The core of the lock. These bits are status of the lock, which should be always available. - * No intermediate status is allowed. This is realized by atomic operations, mainly - * `atomic_fetch_and`, `atomic_fetch_or`, which atomically read the status, and bitwise write - * status value ORed / ANDed with given masks. - * - * The request bits together pending bits represent the actual bg request state of one device. - * Either one of them being active indicates the device has pending bg requests. - * - * Whenever a bit is written to the status, it means the a device on a task is trying to - * acquire the lock. But this will succeed only when no LOCK or BG bits active. - * - * The acquiring processor is responsible to call the scheduler to pass its role to other tasks - * or the BG, unless it clear the last bit in the status register. - */ - //// Critical resources, they are only writable by acquiring processor, and stable only when read by the acquiring processor. - atomic_uint_fast32_t status; - spi_bus_lock_dev_t* volatile acquiring_dev; ///< The acquiring device - bool volatile acq_dev_bg_active; ///< BG is the acquiring processor serving the acquiring device, used for the wait_bg to skip waiting quickly. - bool volatile in_isr; ///< ISR is touching HW - //// End of critical resources - - atomic_intptr_t dev[DEV_NUM_MAX]; ///< Child locks. - bg_ctrl_func_t bg_enable; ///< Function to enable background operations. - bg_ctrl_func_t bg_disable; ///< Function to disable background operations - void* bg_arg; ///< Argument for `bg_enable` and `bg_disable` functions. - - spi_bus_lock_dev_t* last_dev; ///< Last used device, to decide whether to refresh all registers. - int periph_cs_num; ///< Number of the CS pins the HW has. - - //debug information - int host_id; ///< Host ID, for debug information printing - uint32_t new_req; ///< Last int_req when `spi_bus_lock_bg_start` is called. Debug use. -}; - -struct spi_bus_lock_dev_t { - SemaphoreHandle_t semphr; ///< Binray semaphore to notify the device it claimed the bus - spi_bus_lock_t* parent; ///< Pointer to parent spi_bus_lock_t - uint32_t mask; ///< Bitwise OR-ed mask of the REQ, PEND, LOCK bits of this device -}; - -portMUX_TYPE s_spinlock = portMUX_INITIALIZER_UNLOCKED; - -DRAM_ATTR static const char TAG[] = "bus_lock"; - -#define LOCK_CHECK(a, str, ret_val, ...) \ - if (!(a)) { \ - ESP_LOGE(TAG,"%s(%d): "str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ - return (ret_val); \ - } - -static inline int mask_get_id(uint32_t mask); -static inline int dev_lock_get_id(spi_bus_lock_dev_t *dev_lock); - -/******************************************************************************* - * atomic operations to the status - ******************************************************************************/ -SPI_MASTER_ISR_ATTR static inline uint32_t lock_status_fetch_set(spi_bus_lock_t *lock, uint32_t set) -{ - return atomic_fetch_or(&lock->status, set); -} - -IRAM_ATTR static inline uint32_t lock_status_fetch_clear(spi_bus_lock_t *lock, uint32_t clear) -{ - return atomic_fetch_and(&lock->status, ~clear); -} - -IRAM_ATTR static inline uint32_t lock_status_fetch(spi_bus_lock_t *lock) -{ - return atomic_load(&lock->status); -} - -SPI_MASTER_ISR_ATTR static inline void lock_status_init(spi_bus_lock_t *lock) -{ - atomic_store(&lock->status, 0); -} - -// return the remaining status bits -IRAM_ATTR static inline uint32_t lock_status_clear(spi_bus_lock_t* lock, uint32_t clear) -{ - //the fetch and clear should be atomic, avoid missing the all '0' status when all bits are clear. - uint32_t state = lock_status_fetch_clear(lock, clear); - return state & (~clear); -} - -/******************************************************************************* - * Schedule service - * - * The modification to the status bits may cause rotating of the acquiring processor. It also have - * effects to `acquired_dev` (the acquiring device), `in_isr` (HW used in BG), and - * `acq_dev_bg_active` (wait_bg_end can be skipped) members of the lock structure. - * - * Most of them should be atomic, and special attention should be paid to the operation - * sequence. - ******************************************************************************/ -SPI_MASTER_ISR_ATTR static inline void resume_dev_in_isr(spi_bus_lock_dev_t *dev_lock, BaseType_t *do_yield) -{ - xSemaphoreGiveFromISR(dev_lock->semphr, do_yield); -} - -IRAM_ATTR static inline void resume_dev(const spi_bus_lock_dev_t *dev_lock) -{ - xSemaphoreGive(dev_lock->semphr); -} - -SPI_MASTER_ISR_ATTR static inline void bg_disable(spi_bus_lock_t *lock) -{ - BUS_LOCK_DEBUG_EXECUTE_CHECK(lock->bg_disable); - lock->bg_disable(lock->bg_arg); -} - -IRAM_ATTR static inline void bg_enable(spi_bus_lock_t* lock) -{ - BUS_LOCK_DEBUG_EXECUTE_CHECK(lock->bg_enable); - lock->bg_enable(lock->bg_arg); -} - -// Set the REQ bit. If we become the acquiring processor, invoke the ISR and pass that to it. -// The caller will never become the acquiring processor after this function returns. -SPI_MASTER_ATTR static inline void req_core(spi_bus_lock_dev_t *dev_handle) -{ - spi_bus_lock_t *lock = dev_handle->parent; - - // Though `acquired_dev` is critical resource, `dev_handle == lock->acquired_dev` - // is a stable statement unless `acquire_start` or `acquire_end` is called by current - // device. - if (dev_handle == lock->acquiring_dev){ - // Set the REQ bit and check BG bits if we are the acquiring processor. - // If the BG bits were not active before, invoke the BG again. - - // Avoid competitive risk against the `clear_pend_core`, `acq_dev_bg_active` should be set before - // setting REQ bit. - lock->acq_dev_bg_active = true; - uint32_t status = lock_status_fetch_set(lock, DEV_REQ_MASK(dev_handle)); - if ((status & DEV_BG_MASK(dev_handle)) == 0) { - bg_enable(lock); //acquiring processor passed to BG - } - } else { - uint32_t status = lock_status_fetch_set(lock, DEV_REQ_MASK(dev_handle)); - if (status == 0) { - bg_enable(lock); //acquiring processor passed to BG - } - } -} - -//Set the LOCK bit. Handle related stuff and return true if we become the acquiring processor. -SPI_MASTER_ISR_ATTR static inline bool acquire_core(spi_bus_lock_dev_t *dev_handle) -{ - spi_bus_lock_t* lock = dev_handle->parent; - portENTER_CRITICAL_SAFE(&s_spinlock); - uint32_t status = lock_status_fetch_set(lock, dev_handle->mask & LOCK_MASK); - portEXIT_CRITICAL_SAFE(&s_spinlock); - - // Check all bits except WEAK_BG - if ((status & (BG_MASK | LOCK_MASK)) == 0) { - //succeed at once - lock->acquiring_dev = dev_handle; - BUS_LOCK_DEBUG_EXECUTE_CHECK(!lock->acq_dev_bg_active); - if (status & WEAK_BG_FLAG) { - //Mainly to disable the cache (Weak_BG), that is not able to disable itself - bg_disable(lock); - } - return true; - } - return false; -} - -/** - * Find the next acquiring processor according to the status. Will directly change - * the acquiring device if new one found. - * - * Cases: - * - BG should still be the acquiring processor (Return false): - * 1. Acquiring device has active BG bits: out_desired_dev = new acquiring device - * 2. No acquiring device, but BG active: out_desired_dev = randomly pick one device with active BG bits - * - BG should yield to the task (Return true): - * 3. Acquiring device has no active BG bits: out_desired_dev = new acquiring device - * 4. No acquiring device while no active BG bits: out_desired_dev=NULL - * - * Acquiring device task need to be resumed only when case 3. - * - * This scheduling can happen in either task or ISR, so `in_isr` or `bg_active` not touched. - * - * @param lock - * @param status Current status - * @param out_desired_dev Desired device to work next, see above. - * - * @return False if BG should still be the acquiring processor, otherwise True (yield to task). - */ -IRAM_ATTR static inline bool -schedule_core(spi_bus_lock_t *lock, uint32_t status, spi_bus_lock_dev_t **out_desired_dev) -{ - spi_bus_lock_dev_t* desired_dev = NULL; - uint32_t lock_bits = (status & LOCK_MASK) >> LOCK_SHIFT; - uint32_t bg_bits = status & BG_MASK; - bg_bits = ((bg_bits >> REQ_SHIFT) | (bg_bits >> PENDING_SHIFT)) & REQ_MASK; - - bool bg_yield; - if (lock_bits) { - int dev_id = mask_get_id(lock_bits); - desired_dev = (spi_bus_lock_dev_t *)atomic_load(&lock->dev[dev_id]); - BUS_LOCK_DEBUG_EXECUTE_CHECK(desired_dev); - - lock->acquiring_dev = desired_dev; - bg_yield = ((bg_bits & desired_dev->mask) == 0); - lock->acq_dev_bg_active = !bg_yield; - } else { - lock->acq_dev_bg_active = false; - if (bg_bits) { - int dev_id = mask_get_id(bg_bits); - desired_dev = (spi_bus_lock_dev_t *)atomic_load(&lock->dev[dev_id]); - BUS_LOCK_DEBUG_EXECUTE_CHECK(desired_dev); - - lock->acquiring_dev = NULL; - bg_yield = false; - } else { - desired_dev = NULL; - lock->acquiring_dev = NULL; - bg_yield = true; - } - } - *out_desired_dev = desired_dev; - return bg_yield; -} - -//Clear the LOCK bit and trigger a rescheduling. -IRAM_ATTR static inline void acquire_end_core(spi_bus_lock_dev_t *dev_handle) -{ - spi_bus_lock_t* lock = dev_handle->parent; - //uint32_t status = lock_status_clear(lock, dev_handle->mask & LOCK_MASK); - spi_bus_lock_dev_t* desired_dev = NULL; - - portENTER_CRITICAL_SAFE(&s_spinlock); - uint32_t status = lock_status_clear(lock, dev_handle->mask & LOCK_MASK); - bool invoke_bg = !schedule_core(lock, status, &desired_dev); - portEXIT_CRITICAL_SAFE(&s_spinlock); - - if (invoke_bg) { - bg_enable(lock); - } else if (desired_dev) { - resume_dev(desired_dev); - } else if (status & WEAK_BG_FLAG) { - bg_enable(lock); - } -} - -// Move the REQ bits to corresponding PEND bits. Must be called by acquiring processor. -// Have no side effects on the acquiring device/processor. -SPI_MASTER_ISR_ATTR static inline void update_pend_core(spi_bus_lock_t *lock, uint32_t status) -{ - uint32_t active_req_bits = status & REQ_MASK; -#if PENDING_SHIFT > REQ_SHIFT - uint32_t pending_mask = active_req_bits << (PENDING_SHIFT - REQ_SHIFT); -#else - uint32_t pending_mask = active_req_bits >> (REQ_SHIFT - PENDING_SHIFT); -#endif - // We have to set the PEND bits and then clear the REQ bits, since BG bits are using bitwise OR logic, - // this will not influence the effectiveness of the BG bits of every device. - lock_status_fetch_set(lock, pending_mask); - lock_status_fetch_clear(lock, active_req_bits); -} - -// Clear the PEND bit (not REQ bit!) of a device, return the suggestion whether we can try to quit the ISR. -// Lost the acquiring processor immediately when the BG bits for active device are inactive, indiciating by the return value. -// Can be called only when ISR is acting as the acquiring processor. -SPI_MASTER_ISR_ATTR static inline bool clear_pend_core(spi_bus_lock_dev_t *dev_handle) -{ - bool finished; - spi_bus_lock_t *lock = dev_handle->parent; - uint32_t pend_mask = DEV_PEND_MASK(dev_handle); - BUS_LOCK_DEBUG_EXECUTE_CHECK(lock_status_fetch(lock) & pend_mask); - - uint32_t status = lock_status_clear(lock, pend_mask); - - if (lock->acquiring_dev == dev_handle) { - finished = ((status & DEV_REQ_MASK(dev_handle)) == 0); - if (finished) { - lock->acq_dev_bg_active = false; - } - } else { - finished = (status == 0); - } - return finished; -} - -// Return true if the ISR has already touched the HW, which means previous operations should -// be terminated first, before we use the HW again. Otherwise return false. -// In either case `in_isr` will be marked as true, until call to `bg_exit_core` with `wip=false` successfully. -SPI_MASTER_ISR_ATTR static inline bool bg_entry_core(spi_bus_lock_t *lock) -{ - BUS_LOCK_DEBUG_EXECUTE_CHECK(!lock->acquiring_dev || lock->acq_dev_bg_active); - /* - * The interrupt is disabled at the entry of ISR to avoid competitive risk as below: - * - * The `esp_intr_enable` will be called (b) after new BG request is queued (a) in the task; - * while `esp_intr_disable` should be called (c) if we check and found the sending queue is empty (d). - * If (c) happens after (d), if things happens in this sequence: - * (d) -> (a) -> (b) -> (c), the interrupt will be disabled while there's pending BG request in the queue. - * - * To avoid this, interrupt is disabled here, and re-enabled later if required. (c) -> (d) -> (a) -> (b) -> revert (c) if !d - */ - bg_disable(lock); - if (lock->in_isr) { - return false; - } else { - lock->in_isr = true; - return true; - } -} - -// Handle the conditions of status and interrupt, avoiding the ISR being disabled when there is any new coming BG requests. -// When called with `wip=true`, means the ISR is performing some operations. Will enable the interrupt again and exit unconditionally. -// When called with `wip=false`, will only return `true` when there is no coming BG request. If return value is `false`, the ISR should try again. -// Will not change acquiring device. -SPI_MASTER_ISR_ATTR static inline bool bg_exit_core(spi_bus_lock_t *lock, bool wip, BaseType_t *do_yield) -{ - //See comments in `bg_entry_core`, re-enable interrupt disabled in entry if we do need the interrupt - if (wip) { - bg_enable(lock); - BUS_LOCK_DEBUG_EXECUTE_CHECK(!lock->acquiring_dev || lock->acq_dev_bg_active); - return true; - } - - bool ret; - uint32_t status = lock_status_fetch(lock); - if (lock->acquiring_dev) { - if (status & DEV_BG_MASK(lock->acquiring_dev)) { - BUS_LOCK_DEBUG_EXECUTE_CHECK(lock->acq_dev_bg_active); - ret = false; - } else { - // The request may happen any time, even after we fetched the status. - // The value of `acq_dev_bg_active` is random. - resume_dev_in_isr(lock->acquiring_dev, do_yield); - ret = true; - } - } else { - BUS_LOCK_DEBUG_EXECUTE_CHECK(!lock->acq_dev_bg_active); - ret = !(status & BG_MASK); - } - if (ret) { - //when successfully exit, but no transaction done, mark BG as inactive - lock->in_isr = false; - } - return ret; -} - -IRAM_ATTR static inline void dev_wait_prepare(spi_bus_lock_dev_t *dev_handle) -{ - xSemaphoreTake(dev_handle->semphr, 0); -} - -SPI_MASTER_ISR_ATTR static inline esp_err_t dev_wait(spi_bus_lock_dev_t *dev_handle, TickType_t wait) -{ - BaseType_t ret = xSemaphoreTake(dev_handle->semphr, wait); - - if (ret == pdFALSE) return ESP_ERR_TIMEOUT; - return ESP_OK; -} - -/******************************************************************************* - * Initialization & Deinitialization - ******************************************************************************/ -esp_err_t spi_bus_init_lock(spi_bus_lock_handle_t *out_lock, const spi_bus_lock_config_t *config) -{ - spi_bus_lock_t* lock = (spi_bus_lock_t*)calloc(sizeof(spi_bus_lock_t), 1); - if (lock == NULL) { - return ESP_ERR_NO_MEM; - } - - lock_status_init(lock); - lock->acquiring_dev = NULL; - lock->last_dev = NULL; - lock->periph_cs_num = config->cs_num; - lock->host_id = config->host_id; - - *out_lock = lock; - return ESP_OK; -} - -void spi_bus_deinit_lock(spi_bus_lock_handle_t lock) -{ - for (int i = 0; i < DEV_NUM_MAX; i++) { - assert(atomic_load(&lock->dev[i]) == (intptr_t)NULL); - } - free(lock); -} - -static int try_acquire_free_dev(spi_bus_lock_t *lock, bool cs_required) -{ - if (cs_required) { - int i; - for (i = 0; i < lock->periph_cs_num; i++) { - intptr_t null = (intptr_t) NULL; - //use 1 to occupy the slot, actual setup comes later - if (atomic_compare_exchange_strong(&lock->dev[i], &null, (intptr_t) 1)) { - break; - } - } - return ((i == lock->periph_cs_num)? -1: i); - } else { - int i; - for (i = DEV_NUM_MAX - 1; i >= 0; i--) { - intptr_t null = (intptr_t) NULL; - //use 1 to occupy the slot, actual setup comes later - if (atomic_compare_exchange_strong(&lock->dev[i], &null, (intptr_t) 1)) { - break; - } - } - return i; - } -} - -esp_err_t spi_bus_lock_register_dev(spi_bus_lock_handle_t lock, spi_bus_lock_dev_config_t *config, - spi_bus_lock_dev_handle_t *out_dev_handle) -{ - if (lock == NULL) return ESP_ERR_INVALID_ARG; - int id = try_acquire_free_dev(lock, config->flags & SPI_BUS_LOCK_DEV_FLAG_CS_REQUIRED); - if (id == -1) return ESP_ERR_NOT_SUPPORTED; - - spi_bus_lock_dev_t* dev_lock = (spi_bus_lock_dev_t*)heap_caps_calloc(sizeof(spi_bus_lock_dev_t), 1, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); - if (dev_lock == NULL) { - return ESP_ERR_NO_MEM; - } - dev_lock->semphr = xSemaphoreCreateBinary(); - if (dev_lock->semphr == NULL) { - free(dev_lock); - atomic_store(&lock->dev[id], (intptr_t)NULL); - return ESP_ERR_NO_MEM; - } - dev_lock->parent = lock; - dev_lock->mask = DEV_MASK(id); - - ESP_LOGV(TAG, "device registered on bus %d slot %d.", lock->host_id, id); - atomic_store(&lock->dev[id], (intptr_t)dev_lock); - *out_dev_handle = dev_lock; - return ESP_OK; -} - -void spi_bus_lock_unregister_dev(spi_bus_lock_dev_handle_t dev_handle) -{ - int id = dev_lock_get_id(dev_handle); - - spi_bus_lock_t* lock = dev_handle->parent; - BUS_LOCK_DEBUG_EXECUTE_CHECK(atomic_load(&lock->dev[id]) == (intptr_t)dev_handle); - - if (lock->last_dev == dev_handle) lock->last_dev = NULL; - - atomic_store(&lock->dev[id], (intptr_t)NULL); - if (dev_handle->semphr) { - vSemaphoreDelete(dev_handle->semphr); - } - - free(dev_handle); -} - -IRAM_ATTR static inline int mask_get_id(uint32_t mask) -{ - return ID_DEV_MASK(mask); -} - -IRAM_ATTR static inline int dev_lock_get_id(spi_bus_lock_dev_t *dev_lock) -{ - return mask_get_id(dev_lock->mask); -} - -void spi_bus_lock_set_bg_control(spi_bus_lock_handle_t lock, bg_ctrl_func_t bg_enable, bg_ctrl_func_t bg_disable, void *arg) -{ - lock->bg_enable = bg_enable; - lock->bg_disable = bg_disable; - lock->bg_arg = arg; -} - -IRAM_ATTR int spi_bus_lock_get_dev_id(spi_bus_lock_dev_handle_t dev_handle) -{ - return (dev_handle? dev_lock_get_id(dev_handle): -1); -} - -//will be called when cache disabled -IRAM_ATTR bool spi_bus_lock_touch(spi_bus_lock_dev_handle_t dev_handle) -{ - spi_bus_lock_dev_t* last_dev = dev_handle->parent->last_dev; - dev_handle->parent->last_dev = dev_handle; - if (last_dev != dev_handle) { - int last_dev_id = (last_dev? dev_lock_get_id(last_dev): -1); - ESP_DRAM_LOGV(TAG, "SPI dev changed from %d to %d", - last_dev_id, dev_lock_get_id(dev_handle)); - } - return (dev_handle != last_dev); -} - -/******************************************************************************* - * Acquiring service - ******************************************************************************/ -IRAM_ATTR esp_err_t spi_bus_lock_acquire_start(spi_bus_lock_dev_t *dev_handle, TickType_t wait) -{ - LOCK_CHECK(wait == portMAX_DELAY, "timeout other than portMAX_DELAY not supported", ESP_ERR_INVALID_ARG); - - spi_bus_lock_t* lock = dev_handle->parent; - - // Clear the semaphore before checking - dev_wait_prepare(dev_handle); - if (!acquire_core(dev_handle)) { - //block until becoming the acquiring processor (help by previous acquiring processor) - esp_err_t err = dev_wait(dev_handle, wait); - //TODO: add timeout handling here. - if (err != ESP_OK) return err; - } - - ESP_DRAM_LOGV(TAG, "dev %d acquired.", dev_lock_get_id(dev_handle)); - BUS_LOCK_DEBUG_EXECUTE_CHECK(lock->acquiring_dev == dev_handle); - - //When arrives at here, requests of this device should already be handled - uint32_t status = lock_status_fetch(lock); - (void) status; - BUS_LOCK_DEBUG_EXECUTE_CHECK((status & DEV_BG_MASK(dev_handle)) == 0); - - return ESP_OK; -} - -IRAM_ATTR esp_err_t spi_bus_lock_acquire_end(spi_bus_lock_dev_t *dev_handle) -{ - //release the bus - spi_bus_lock_t* lock = dev_handle->parent; - LOCK_CHECK(lock->acquiring_dev == dev_handle, "Cannot release a lock that hasn't been acquired.", ESP_ERR_INVALID_STATE); - - acquire_end_core(dev_handle); - - ESP_LOGV(TAG, "dev %d released.", dev_lock_get_id(dev_handle)); - return ESP_OK; -} - -SPI_MASTER_ISR_ATTR spi_bus_lock_dev_handle_t spi_bus_lock_get_acquiring_dev(spi_bus_lock_t *lock) -{ - return lock->acquiring_dev; -} - -/******************************************************************************* - * BG (background operation) service - ******************************************************************************/ -SPI_MASTER_ISR_ATTR bool spi_bus_lock_bg_entry(spi_bus_lock_t* lock) -{ - return bg_entry_core(lock); -} - -SPI_MASTER_ISR_ATTR bool spi_bus_lock_bg_exit(spi_bus_lock_t* lock, bool wip, BaseType_t* do_yield) -{ - return bg_exit_core(lock, wip, do_yield); -} - -SPI_MASTER_ATTR esp_err_t spi_bus_lock_bg_request(spi_bus_lock_dev_t *dev_handle) -{ - req_core(dev_handle); - return ESP_OK; -} - -IRAM_ATTR esp_err_t spi_bus_lock_wait_bg_done(spi_bus_lock_dev_handle_t dev_handle, TickType_t wait) -{ - spi_bus_lock_t *lock = dev_handle->parent; - LOCK_CHECK(lock->acquiring_dev == dev_handle, "Cannot wait for a device that is not acquired", ESP_ERR_INVALID_STATE); - LOCK_CHECK(wait == portMAX_DELAY, "timeout other than portMAX_DELAY not supported", ESP_ERR_INVALID_ARG); - - // If no BG bits active, skip quickly. This is ensured by `spi_bus_lock_wait_bg_done` - // cannot be executed with `bg_request` on the same device concurrently. - if (lock_status_fetch(lock) & DEV_BG_MASK(dev_handle)) { - // Clear the semaphore before checking - dev_wait_prepare(dev_handle); - if (lock_status_fetch(lock) & DEV_BG_MASK(dev_handle)) { - //block until becoming the acquiring processor (help by previous acquiring processor) - esp_err_t err = dev_wait(dev_handle, wait); - //TODO: add timeout handling here. - if (err != ESP_OK) return err; - } - } - - BUS_LOCK_DEBUG_EXECUTE_CHECK(!lock->acq_dev_bg_active); - BUS_LOCK_DEBUG_EXECUTE_CHECK((lock_status_fetch(lock) & DEV_BG_MASK(dev_handle)) == 0); - return ESP_OK; -} - -SPI_MASTER_ISR_ATTR bool spi_bus_lock_bg_clear_req(spi_bus_lock_dev_t *dev_handle) -{ - bool finished = clear_pend_core(dev_handle); - ESP_EARLY_LOGV(TAG, "dev %d served from bg.", dev_lock_get_id(dev_handle)); - return finished; -} - -SPI_MASTER_ISR_ATTR bool spi_bus_lock_bg_check_dev_acq(spi_bus_lock_t *lock, - spi_bus_lock_dev_handle_t *out_dev_lock) -{ - BUS_LOCK_DEBUG_EXECUTE_CHECK(!lock->acquiring_dev); - uint32_t status = lock_status_fetch(lock); - return schedule_core(lock, status, out_dev_lock); -} - -SPI_MASTER_ISR_ATTR bool spi_bus_lock_bg_check_dev_req(spi_bus_lock_dev_t *dev_lock) -{ - spi_bus_lock_t* lock = dev_lock->parent; - uint32_t status = lock_status_fetch(lock); - uint32_t dev_status = status & dev_lock->mask; - - // move REQ bits of all device to corresponding PEND bits. - // To reduce executing time, only done when the REQ bit of the calling device is set. - if (dev_status & REQ_MASK) { - update_pend_core(lock, status); - return true; - } else { - return dev_status & PEND_MASK; - } -} - -SPI_MASTER_ISR_ATTR bool spi_bus_lock_bg_req_exist(spi_bus_lock_t *lock) -{ - uint32_t status = lock_status_fetch(lock); - return status & BG_MASK; -} - -/******************************************************************************* - * Static variables of the locks of the main flash - ******************************************************************************/ -#if CONFIG_SPI_FLASH_SHARE_SPI1_BUS -static spi_bus_lock_dev_t lock_main_flash_dev; - -static spi_bus_lock_t main_spi_bus_lock = { - /* - * the main bus cache is permanently required, this flag is set here and never clear so that the - * cache will always be enabled if acquiring devices yield. - */ - .status = ATOMIC_VAR_INIT(WEAK_BG_FLAG), - .acquiring_dev = NULL, - .dev = {ATOMIC_VAR_INIT((intptr_t)&lock_main_flash_dev)}, - .new_req = 0, - .periph_cs_num = SOC_SPI_PERIPH_CS_NUM(0), -}; -const spi_bus_lock_handle_t g_main_spi_bus_lock = &main_spi_bus_lock; - -esp_err_t spi_bus_lock_init_main_bus(void) -{ - spi_bus_main_set_lock(g_main_spi_bus_lock); - return ESP_OK; -} - -static StaticSemaphore_t main_flash_semphr; - -static spi_bus_lock_dev_t lock_main_flash_dev = { - .semphr = NULL, - .parent = &main_spi_bus_lock, - .mask = DEV_MASK(0), -}; -const spi_bus_lock_dev_handle_t g_spi_lock_main_flash_dev = &lock_main_flash_dev; - -esp_err_t spi_bus_lock_init_main_dev(void) -{ - g_spi_lock_main_flash_dev->semphr = xSemaphoreCreateBinaryStatic(&main_flash_semphr); - if (g_spi_lock_main_flash_dev->semphr == NULL) { - return ESP_ERR_NO_MEM; - } - return ESP_OK; -} -#else //CONFIG_SPI_FLASH_SHARE_SPI1_BUS - -//when the dev lock is not initialized, point to NULL -const spi_bus_lock_dev_handle_t g_spi_lock_main_flash_dev = NULL; - -#endif diff --git a/components/_override/spi_master.c.unused b/components/_override/spi_master.c.unused deleted file mode 100644 index 9e7a109a..00000000 --- a/components/_override/spi_master.c.unused +++ /dev/null @@ -1,1007 +0,0 @@ -// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/* -Architecture: - -We can initialize a SPI driver, but we don't talk to the SPI driver itself, we address a device. A device essentially -is a combination of SPI port and CS pin, plus some information about the specifics of communication to the device -(timing, command/address length etc). The arbitration between tasks is also in conception of devices. - -A device can work in interrupt mode and polling mode, and a third but -complicated mode which combines the two modes above: - -1. Work in the ISR with a set of queues; one per device. - - The idea is that to send something to a SPI device, you allocate a - transaction descriptor. It contains some information about the transfer - like the lenghth, address, command etc, plus pointers to transmit and - receive buffer. The address of this block gets pushed into the transmit - queue. The SPI driver does its magic, and sends and retrieves the data - eventually. The data gets written to the receive buffers, if needed the - transaction descriptor is modified to indicate returned parameters and - the entire thing goes into the return queue, where whatever software - initiated the transaction can retrieve it. - - The entire thing is run from the SPI interrupt handler. If SPI is done - transmitting/receiving but nothing is in the queue, it will not clear the - SPI interrupt but just disable it by esp_intr_disable. This way, when a - new thing is sent, pushing the packet into the send queue and re-enabling - the interrupt (by esp_intr_enable) will trigger the interrupt again, which - can then take care of the sending. - -2. Work in the polling mode in the task. - - In this mode we get rid of the ISR, FreeRTOS queue and task switching, the - task is no longer blocked during a transaction. This increase the cpu - load, but decrease the interval of SPI transactions. Each time only one - device (in one task) can send polling transactions, transactions to - other devices are blocked until the polling transaction of current device - is done. - - In the polling mode, the queue is not used, all the operations are done - in the task. The task calls ``spi_device_polling_start`` to setup and start - a new transaction, then call ``spi_device_polling_end`` to handle the - return value of the transaction. - - To handle the arbitration among devices, the device "temporarily" acquire - a bus by the ``device_acquire_bus_internal`` function, which writes - dev_request by CAS operation. Other devices which wants to send polling - transactions but don't own the bus will block and wait until given the - semaphore which indicates the ownership of bus. - - In case of the ISR is still sending transactions to other devices, the ISR - should maintain an ``random_idle`` flag indicating that it's not doing - transactions. When the bus is locked, the ISR can only send new - transactions to the acquiring device. The ISR will automatically disable - itself and send semaphore to the device if the ISR is free. If the device - sees the random_idle flag, it can directly start its polling transaction. - Otherwise it should block and wait for the semaphore from the ISR. - - After the polling transaction, the driver will release the bus. During the - release of the bus, the driver search all other devices to see whether - there is any device waiting to acquire the bus, if so, acquire for it and - send it a semaphore if the device queue is empty, or invoke the ISR for - it. If all other devices don't need to acquire the bus, but there are - still transactions in the queues, the ISR will also be invoked. - - To get better polling efficiency, user can call ``spi_device_acquire_bus`` - function, which also calls the ``spi_bus_lock_acquire_core`` function, - before a series of polling transactions to a device. The bus acquiring and - task switching before and after the polling transaction will be escaped. - -3. Mixed mode - - The driver is written under the assumption that polling and interrupt - transactions are not happening simultaneously. When sending polling - transactions, it will check whether the ISR is active, which includes the - case the ISR is sending the interrupt transactions of the acquiring - device. If the ISR is still working, the routine sending a polling - transaction will get blocked and wait until the semaphore from the ISR - which indicates the ISR is free now. - - A fatal case is, a polling transaction is in flight, but the ISR received - an interrupt transaction. The behavior of the driver is unpredictable, - which should be strictly forbidden. - -We have two bits to control the interrupt: - -1. The slave->trans_done bit, which is automatically asserted when a transaction is done. - - This bit is cleared during an interrupt transaction, so that the interrupt - will be triggered when the transaction is done, or the SW can check the - bit to see if the transaction is done for polling transactions. - - When no transaction is in-flight, the bit is kept active, so that the SW - can easily invoke the ISR by enable the interrupt. - -2. The system interrupt enable/disable, controlled by esp_intr_enable and esp_intr_disable. - - The interrupt is disabled (by the ISR itself) when no interrupt transaction - is queued. When the bus is not occupied, any task, which queues a - transaction into the queue, will enable the interrupt to invoke the ISR. - When the bus is occupied by a device, other device will put off the - invoking of ISR to the moment when the bus is released. The device - acquiring the bus can still send interrupt transactions by enable the - interrupt. - -*/ - -#include -#include "driver/spi_common_internal.h" -#include "driver/spi_master.h" - -#include "esp_log.h" -#include "freertos/task.h" -#include "freertos/queue.h" -#include "freertos/semphr.h" -#include "soc/soc_memory_layout.h" -#include "driver/gpio.h" -#include "hal/spi_hal.h" -#include "esp_heap_caps.h" - - -typedef struct spi_device_t spi_device_t; - -/// struct to hold private transaction data (like tx and rx buffer for DMA). -typedef struct { - spi_transaction_t *trans; - const uint32_t *buffer_to_send; //equals to tx_data, if SPI_TRANS_USE_RXDATA is applied; otherwise if original buffer wasn't in DMA-capable memory, this gets the address of a temporary buffer that is; - //otherwise sets to the original buffer or NULL if no buffer is assigned. - uint32_t *buffer_to_rcv; // similar to buffer_to_send -} spi_trans_priv_t; - -typedef struct { - int id; - spi_device_t* device[DEV_NUM_MAX]; - intr_handle_t intr; - spi_hal_context_t hal; - spi_trans_priv_t cur_trans_buf; - int cur_cs; //current device doing transaction - const spi_bus_attr_t* bus_attr; - - /** - * the bus is permanently controlled by a device until `spi_bus_release_bus`` is called. Otherwise - * the acquiring of SPI bus will be freed when `spi_device_polling_end` is called. - */ - spi_device_t* device_acquiring_lock; - -//debug information - bool polling; //in process of a polling, avoid of queue new transactions into ISR - -// PATCH - SemaphoreHandle_t mutex; - int count; -} spi_host_t; - -struct spi_device_t { - int id; - QueueHandle_t trans_queue; - QueueHandle_t ret_queue; - spi_device_interface_config_t cfg; - spi_hal_dev_config_t hal_dev; - spi_host_t *host; - spi_bus_lock_dev_handle_t dev_lock; -}; - -static spi_host_t* bus_driver_ctx[SOC_SPI_PERIPH_NUM] = {}; - -static const char *SPI_TAG = "spi_master"; -#define SPI_CHECK(a, str, ret_val, ...) \ - if (unlikely(!(a))) { \ - ESP_LOGE(SPI_TAG,"%s(%d): "str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ - return (ret_val); \ - } - - -static void spi_intr(void *arg); -static void spi_bus_intr_enable(void *host); -static void spi_bus_intr_disable(void *host); - -static esp_err_t spi_master_deinit_driver(void* arg); - -static inline bool is_valid_host(spi_host_device_t host) -{ -//SPI1 can be used as GPSPI only on ESP32 -#if CONFIG_IDF_TARGET_ESP32 - return host >= SPI1_HOST && host <= SPI3_HOST; -#elif (SOC_SPI_PERIPH_NUM == 2) - return host == SPI2_HOST; -#elif (SOC_SPI_PERIPH_NUM == 3) - return host >= SPI2_HOST && host <= SPI3_HOST; -#endif -} - -// Should be called before any devices are actually registered or used. -// Currently automatically called after `spi_bus_initialize()` and when first device is registered. -static esp_err_t spi_master_init_driver(spi_host_device_t host_id) -{ - esp_err_t err = ESP_OK; - - const spi_bus_attr_t* bus_attr = spi_bus_get_attr(host_id); - SPI_CHECK(bus_attr != NULL, "host_id not initialized", ESP_ERR_INVALID_STATE); - SPI_CHECK(bus_attr->lock != NULL, "SPI Master cannot attach to bus. (Check CONFIG_SPI_FLASH_SHARE_SPI1_BUS)", ESP_ERR_INVALID_ARG); - // spihost contains atomic variables, which should not be put in PSRAM - spi_host_t* host = heap_caps_malloc(sizeof(spi_host_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); - if (host == NULL) { - err = ESP_ERR_NO_MEM; - goto cleanup; - } - - *host = (spi_host_t) { - .id = host_id, - .cur_cs = DEV_NUM_MAX, - .polling = false, - .device_acquiring_lock = NULL, - .bus_attr = bus_attr, - }; - - if (host_id != SPI1_HOST) { - // interrupts are not allowed on SPI1 bus - err = esp_intr_alloc(spicommon_irqsource_for_host(host_id), - bus_attr->bus_cfg.intr_flags | ESP_INTR_FLAG_INTRDISABLED, - spi_intr, host, &host->intr); - if (err != ESP_OK) { - goto cleanup; - } - } - - //assign the SPI, RX DMA and TX DMA peripheral registers beginning address - spi_hal_config_t hal_config = { - //On ESP32-S2 and earlier chips, DMA registers are part of SPI registers. Pass the registers of SPI peripheral to control it. - .dma_in = SPI_LL_GET_HW(host_id), - .dma_out = SPI_LL_GET_HW(host_id), - .dma_enabled = bus_attr->dma_enabled, - .dmadesc_tx = bus_attr->dmadesc_tx, - .dmadesc_rx = bus_attr->dmadesc_rx, - .tx_dma_chan = bus_attr->tx_dma_chan, - .rx_dma_chan = bus_attr->rx_dma_chan, - .dmadesc_n = bus_attr->dma_desc_num, - }; - spi_hal_init(&host->hal, host_id, &hal_config); - - if (host_id != SPI1_HOST) { - //SPI1 attributes are already initialized at start up. - spi_bus_lock_handle_t lock = spi_bus_lock_get_by_id(host_id); - spi_bus_lock_set_bg_control(lock, spi_bus_intr_enable, spi_bus_intr_disable, host); - spi_bus_register_destroy_func(host_id, spi_master_deinit_driver, host); - } - - bus_driver_ctx[host_id] = host; - return ESP_OK; - -cleanup: - if (host) { - spi_hal_deinit(&host->hal); - if (host->intr) { - esp_intr_free(host->intr); - } - } - free(host); - return err; -} - -static esp_err_t spi_master_deinit_driver(void* arg) -{ - spi_host_t *host = (spi_host_t*)arg; - SPI_CHECK(host != NULL, "host_id not in use", ESP_ERR_INVALID_STATE); - - int host_id = host->id; - SPI_CHECK(is_valid_host(host_id), "invalid host_id", ESP_ERR_INVALID_ARG); - - int x; - for (x=0; xdevice[x] == NULL, "not all CSses freed", ESP_ERR_INVALID_STATE); - } - - spi_hal_deinit(&host->hal); - - if (host->intr) { - esp_intr_free(host->intr); - } - free(host); - bus_driver_ctx[host_id] = NULL; - return ESP_OK; -} - -void spi_get_timing(bool gpio_is_used, int input_delay_ns, int eff_clk, int* dummy_o, int* cycles_remain_o) -{ - int timing_dummy; - int timing_miso_delay; - - spi_hal_cal_timing(eff_clk, gpio_is_used, input_delay_ns, &timing_dummy, &timing_miso_delay); - if (dummy_o) *dummy_o = timing_dummy; - if (cycles_remain_o) *cycles_remain_o = timing_miso_delay; -} - -int spi_get_freq_limit(bool gpio_is_used, int input_delay_ns) -{ - return spi_hal_get_freq_limit(gpio_is_used, input_delay_ns); -} - -/* - Add a device. This allocates a CS line for the device, allocates memory for the device structure and hooks - up the CS pin to whatever is specified. -*/ -esp_err_t spi_bus_add_device(spi_host_device_t host_id, const spi_device_interface_config_t *dev_config, spi_device_handle_t *handle) -{ - spi_device_t *dev = NULL; - esp_err_t err = ESP_OK; - - SPI_CHECK(is_valid_host(host_id), "invalid host", ESP_ERR_INVALID_ARG); - if (bus_driver_ctx[host_id] == NULL) { - //lazy initialization the driver, get deinitialized by the bus is freed - err = spi_master_init_driver(host_id); - if (err != ESP_OK) { - return err; - } - } - - spi_host_t *host = bus_driver_ctx[host_id]; - const spi_bus_attr_t* bus_attr = host->bus_attr; - SPI_CHECK(dev_config->spics_io_num < 0 || GPIO_IS_VALID_OUTPUT_GPIO(dev_config->spics_io_num), "spics pin invalid", ESP_ERR_INVALID_ARG); - SPI_CHECK(dev_config->clock_speed_hz > 0, "invalid sclk speed", ESP_ERR_INVALID_ARG); -#ifdef CONFIG_IDF_TARGET_ESP32 - //The hardware looks like it would support this, but actually setting cs_ena_pretrans when transferring in full - //duplex mode does absolutely nothing on the ESP32. - SPI_CHECK(dev_config->cs_ena_pretrans <= 1 || (dev_config->address_bits == 0 && dev_config->command_bits == 0) || - (dev_config->flags & SPI_DEVICE_HALFDUPLEX), "In full-duplex mode, only support cs pretrans delay = 1 and without address_bits and command_bits", ESP_ERR_INVALID_ARG); -#endif - uint32_t lock_flag = ((dev_config->spics_io_num != -1)? SPI_BUS_LOCK_DEV_FLAG_CS_REQUIRED: 0); - - spi_bus_lock_dev_config_t lock_config = { - .flags = lock_flag, - }; - spi_bus_lock_dev_handle_t dev_handle; - err = spi_bus_lock_register_dev(bus_attr->lock, &lock_config, &dev_handle); - if (err != ESP_OK) { - goto nomem; - } - - int freecs = spi_bus_lock_get_dev_id(dev_handle); - SPI_CHECK(freecs != -1, "no free cs pins for the host", ESP_ERR_NOT_FOUND); - - //input parameters to calculate timing configuration - int half_duplex = dev_config->flags & SPI_DEVICE_HALFDUPLEX ? 1 : 0; - int no_compensate = dev_config->flags & SPI_DEVICE_NO_DUMMY ? 1 : 0; - int duty_cycle = (dev_config->duty_cycle_pos==0) ? 128 : dev_config->duty_cycle_pos; - int use_gpio = !(bus_attr->flags & SPICOMMON_BUSFLAG_IOMUX_PINS); - spi_hal_timing_param_t timing_param = { - .half_duplex = half_duplex, - .no_compensate = no_compensate, - .clock_speed_hz = dev_config->clock_speed_hz, - .duty_cycle = duty_cycle, - .input_delay_ns = dev_config->input_delay_ns, - .use_gpio = use_gpio - }; - - //output values of timing configuration - spi_hal_timing_conf_t temp_timing_conf; - int freq; - esp_err_t ret = spi_hal_cal_clock_conf(&timing_param, &freq, &temp_timing_conf); - SPI_CHECK(ret==ESP_OK, "assigned clock speed not supported", ret); - - //Allocate memory for device - dev = malloc(sizeof(spi_device_t)); - if (dev == NULL) goto nomem; - memset(dev, 0, sizeof(spi_device_t)); - - dev->id = freecs; - dev->dev_lock = dev_handle; - - //Allocate queues, set defaults - dev->trans_queue = xQueueCreate(dev_config->queue_size, sizeof(spi_trans_priv_t)); - dev->ret_queue = xQueueCreate(dev_config->queue_size, sizeof(spi_trans_priv_t)); - if (!dev->trans_queue || !dev->ret_queue) { - goto nomem; - } - - //We want to save a copy of the dev config in the dev struct. - memcpy(&dev->cfg, dev_config, sizeof(spi_device_interface_config_t)); - dev->cfg.duty_cycle_pos = duty_cycle; - // TODO: if we have to change the apb clock among transactions, re-calculate this each time the apb clock lock is locked. - - //Set CS pin, CS options - if (dev_config->spics_io_num >= 0) { - spicommon_cs_initialize(host_id, dev_config->spics_io_num, freecs, use_gpio); - } - - // create a mutex if we have more than one client - if (host->count++) { - ESP_LOGI(SPI_TAG, "More than one device on SPI %d => creating mutex", host_id); - host->mutex = xSemaphoreCreateMutex(); - } - - //save a pointer to device in spi_host_t - host->device[freecs] = dev; - //save a pointer to host in spi_device_t - dev->host= host; - - //initialise the device specific configuration - spi_hal_dev_config_t *hal_dev = &(dev->hal_dev); - hal_dev->mode = dev_config->mode; - hal_dev->cs_setup = dev_config->cs_ena_pretrans; - hal_dev->cs_hold = dev_config->cs_ena_posttrans; - //set hold_time to 0 will not actually append delay to CS - //set it to 1 since we do need at least one clock of hold time in most cases - if (hal_dev->cs_hold == 0) { - hal_dev->cs_hold = 1; - } - hal_dev->cs_pin_id = dev->id; - hal_dev->timing_conf = temp_timing_conf; - hal_dev->sio = (dev_config->flags) & SPI_DEVICE_3WIRE ? 1 : 0; - hal_dev->half_duplex = dev_config->flags & SPI_DEVICE_HALFDUPLEX ? 1 : 0; - hal_dev->tx_lsbfirst = dev_config->flags & SPI_DEVICE_TXBIT_LSBFIRST ? 1 : 0; - hal_dev->rx_lsbfirst = dev_config->flags & SPI_DEVICE_RXBIT_LSBFIRST ? 1 : 0; - hal_dev->no_compensate = dev_config->flags & SPI_DEVICE_NO_DUMMY ? 1 : 0; -#if SOC_SPI_SUPPORT_AS_CS - hal_dev->as_cs = dev_config->flags& SPI_DEVICE_CLK_AS_CS ? 1 : 0; -#endif - hal_dev->positive_cs = dev_config->flags & SPI_DEVICE_POSITIVE_CS ? 1 : 0; - - *handle = dev; - ESP_LOGD(SPI_TAG, "SPI%d: New device added to CS%d, effective clock: %dkHz", host_id+1, freecs, freq/1000); - - return ESP_OK; - -nomem: - if (dev) { - if (dev->trans_queue) vQueueDelete(dev->trans_queue); - if (dev->ret_queue) vQueueDelete(dev->ret_queue); - spi_bus_lock_unregister_dev(dev->dev_lock); - } - free(dev); - return ESP_ERR_NO_MEM; -} - -esp_err_t spi_bus_remove_device(spi_device_handle_t handle) -{ - SPI_CHECK(handle!=NULL, "invalid handle", ESP_ERR_INVALID_ARG); - //These checks aren't exhaustive; another thread could sneak in a transaction inbetween. These are only here to - //catch design errors and aren't meant to be triggered during normal operation. - SPI_CHECK(uxQueueMessagesWaiting(handle->trans_queue)==0, "Have unfinished transactions", ESP_ERR_INVALID_STATE); - SPI_CHECK(handle->host->cur_cs == DEV_NUM_MAX || handle->host->device[handle->host->cur_cs] != handle, "Have unfinished transactions", ESP_ERR_INVALID_STATE); - SPI_CHECK(uxQueueMessagesWaiting(handle->ret_queue)==0, "Have unfinished transactions", ESP_ERR_INVALID_STATE); - - //return - int spics_io_num = handle->cfg.spics_io_num; - if (spics_io_num >= 0) spicommon_cs_free_io(spics_io_num); - - //Kill queues - vQueueDelete(handle->trans_queue); - vQueueDelete(handle->ret_queue); - spi_bus_lock_unregister_dev(handle->dev_lock); - - assert(handle->host->device[handle->id] == handle); - handle->host->device[handle->id] = NULL; - free(handle); - return ESP_OK; -} - -int spi_cal_clock(int fapb, int hz, int duty_cycle, uint32_t *reg_o) -{ - return spi_ll_master_cal_clock(fapb, hz, duty_cycle, reg_o); -} - -int spi_get_actual_clock(int fapb, int hz, int duty_cycle) -{ - return spi_hal_master_cal_clock(fapb, hz, duty_cycle); -} - -// Setup the device-specified configuration registers. Called every time a new -// transaction is to be sent, but only apply new configurations when the device -// changes. -static SPI_MASTER_ISR_ATTR void spi_setup_device(spi_device_t *dev) -{ - spi_bus_lock_dev_handle_t dev_lock = dev->dev_lock; - - if (!spi_bus_lock_touch(dev_lock)) { - //if the configuration is already applied, skip the following. - return; - } - spi_hal_context_t *hal = &dev->host->hal; - spi_hal_dev_config_t *hal_dev = &(dev->hal_dev); - spi_hal_setup_device(hal, hal_dev); -} - -static SPI_MASTER_ISR_ATTR spi_device_t *get_acquiring_dev(spi_host_t *host) -{ - spi_bus_lock_dev_handle_t dev_lock = spi_bus_lock_get_acquiring_dev(host->bus_attr->lock); - if (!dev_lock) return NULL; - - return host->device[spi_bus_lock_get_dev_id(dev_lock)]; -} - -// Debug only -// NOTE if the acquiring is not fully completed, `spi_bus_lock_get_acquiring_dev` -// may return a false `NULL` cause the function returning false `false`. -static inline SPI_MASTER_ISR_ATTR bool spi_bus_device_is_polling(spi_device_t *dev) -{ - return get_acquiring_dev(dev->host) == dev && dev->host->polling; -} - -/*----------------------------------------------------------------------------- - Working Functions ------------------------------------------------------------------------------*/ - -// The interrupt may get invoked by the bus lock. -static void SPI_MASTER_ISR_ATTR spi_bus_intr_enable(void *host) -{ - esp_intr_enable(((spi_host_t*)host)->intr); -} - -// The interrupt is always disabled by the ISR itself, not exposed -static void SPI_MASTER_ISR_ATTR spi_bus_intr_disable(void *host) -{ - esp_intr_disable(((spi_host_t*)host)->intr); -} - -// The function is called to send a new transaction, in ISR or in the task. -// Setup the transaction-specified registers and linked-list used by the DMA (or FIFO if DMA is not used) -static void SPI_MASTER_ISR_ATTR spi_new_trans(spi_device_t *dev, spi_trans_priv_t *trans_buf) -{ - spi_transaction_t *trans = NULL; - spi_host_t *host = dev->host; - spi_hal_context_t *hal = &(host->hal); - spi_hal_dev_config_t *hal_dev = &(dev->hal_dev); - - trans = trans_buf->trans; - host->cur_cs = dev->id; - - //Reconfigure according to device settings, the function only has effect when the dev_id is changed. - spi_setup_device(dev); - - //set the transaction specific configuration each time before a transaction setup - spi_hal_trans_config_t hal_trans = {}; - hal_trans.tx_bitlen = trans->length; - hal_trans.rx_bitlen = trans->rxlength; - hal_trans.rcv_buffer = (uint8_t*)host->cur_trans_buf.buffer_to_rcv; - hal_trans.send_buffer = (uint8_t*)host->cur_trans_buf.buffer_to_send; - hal_trans.cmd = trans->cmd; - hal_trans.addr = trans->addr; - //Set up QIO/DIO if needed - hal_trans.io_mode = (trans->flags & SPI_TRANS_MODE_DIO ? - (trans->flags & SPI_TRANS_MODE_DIOQIO_ADDR ? SPI_LL_IO_MODE_DIO : SPI_LL_IO_MODE_DUAL) : - (trans->flags & SPI_TRANS_MODE_QIO ? - (trans->flags & SPI_TRANS_MODE_DIOQIO_ADDR ? SPI_LL_IO_MODE_QIO : SPI_LL_IO_MODE_QUAD) : - SPI_LL_IO_MODE_NORMAL - )); - - if (trans->flags & SPI_TRANS_VARIABLE_CMD) { - hal_trans.cmd_bits = ((spi_transaction_ext_t *)trans)->command_bits; - } else { - hal_trans.cmd_bits = dev->cfg.command_bits; - } - if (trans->flags & SPI_TRANS_VARIABLE_ADDR) { - hal_trans.addr_bits = ((spi_transaction_ext_t *)trans)->address_bits; - } else { - hal_trans.addr_bits = dev->cfg.address_bits; - } - if (trans->flags & SPI_TRANS_VARIABLE_DUMMY) { - hal_trans.dummy_bits = ((spi_transaction_ext_t *)trans)->dummy_bits; - } else { - hal_trans.dummy_bits = dev->cfg.dummy_bits; - } - - spi_hal_setup_trans(hal, hal_dev, &hal_trans); - spi_hal_prepare_data(hal, hal_dev, &hal_trans); - - //Call pre-transmission callback, if any - if (dev->cfg.pre_cb) dev->cfg.pre_cb(trans); - //Kick off transfer - spi_hal_user_start(hal); -} - -// The function is called when a transaction is done, in ISR or in the task. -// Fetch the data from FIFO and call the ``post_cb``. -static void SPI_MASTER_ISR_ATTR spi_post_trans(spi_host_t *host) -{ - spi_transaction_t *cur_trans = host->cur_trans_buf.trans; - - spi_hal_fetch_result(&host->hal); - //Call post-transaction callback, if any - spi_device_t* dev = host->device[host->cur_cs]; - if (dev->cfg.post_cb) dev->cfg.post_cb(cur_trans); - - host->cur_cs = DEV_NUM_MAX; -} - -// This is run in interrupt context. -static void SPI_MASTER_ISR_ATTR spi_intr(void *arg) -{ - BaseType_t do_yield = pdFALSE; - spi_host_t *host = (spi_host_t *)arg; - const spi_bus_attr_t* bus_attr = host->bus_attr; - - assert(spi_hal_usr_is_done(&host->hal)); - - /* - * Help to skip the handling of in-flight transaction, and disable of the interrupt. - * The esp_intr_enable will be called (b) after new BG request is queued (a) in the task; - * while esp_intr_disable should be called (c) if we check and found the sending queue is empty (d). - * If (c) is called after (d), then there is a risk that things happens in this sequence: - * (d) -> (a) -> (b) -> (c), and in this case the interrupt is disabled while there's pending BG request in the queue. - * To avoid this, interrupt is disabled here, and re-enabled later if required. - */ - if (!spi_bus_lock_bg_entry(bus_attr->lock)) { - /*------------ deal with the in-flight transaction -----------------*/ - assert(host->cur_cs != DEV_NUM_MAX); - //Okay, transaction is done. - const int cs = host->cur_cs; - //Tell common code DMA workaround that our DMA channel is idle. If needed, the code will do a DMA reset. - if (bus_attr->dma_enabled) { - //This workaround is only for esp32, where tx_dma_chan and rx_dma_chan are always same - spicommon_dmaworkaround_idle(bus_attr->tx_dma_chan); - } - - //cur_cs is changed to DEV_NUM_MAX here - spi_post_trans(host); - // spi_bus_lock_bg_pause(bus_attr->lock); - //Return transaction descriptor. - xQueueSendFromISR(host->device[cs]->ret_queue, &host->cur_trans_buf, &do_yield); -#ifdef CONFIG_PM_ENABLE - //Release APB frequency lock - esp_pm_lock_release(bus_attr->pm_lock); -#endif - } - - /*------------ new transaction starts here ------------------*/ - assert(host->cur_cs == DEV_NUM_MAX); - - spi_bus_lock_handle_t lock = host->bus_attr->lock; - BaseType_t trans_found = pdFALSE; - - - // There should be remaining requests - BUS_LOCK_DEBUG_EXECUTE_CHECK(spi_bus_lock_bg_req_exist(lock)); - - do { - spi_bus_lock_dev_handle_t acq_dev_lock = spi_bus_lock_get_acquiring_dev(lock); - spi_bus_lock_dev_handle_t desired_dev = acq_dev_lock; - bool resume_task = false; - spi_device_t* device_to_send = NULL; - - if (!acq_dev_lock) { - // This function may assign a new acquiring device, otherwise it will suggest a desired device with BG active - // We use either of them without further searching in the devices. - // If the return value is true, it means either there's no acquiring device, or the acquiring device's BG is active, - // We stay in the ISR to deal with those transactions of desired device, otherwise nothing will be done, check whether we need to resume some other tasks, or just quit the ISR - resume_task = spi_bus_lock_bg_check_dev_acq(lock, &desired_dev); - } - - if (!resume_task) { - bool dev_has_req = spi_bus_lock_bg_check_dev_req(desired_dev); - if (dev_has_req) { - device_to_send = host->device[spi_bus_lock_get_dev_id(desired_dev)]; - trans_found = xQueueReceiveFromISR(device_to_send->trans_queue, &host->cur_trans_buf, &do_yield); - if (!trans_found) { - spi_bus_lock_bg_clear_req(desired_dev); - } - } - } - - if (trans_found) { - spi_trans_priv_t *const cur_trans_buf = &host->cur_trans_buf; - if (bus_attr->dma_enabled && (cur_trans_buf->buffer_to_rcv || cur_trans_buf->buffer_to_send)) { - //mark channel as active, so that the DMA will not be reset by the slave - //This workaround is only for esp32, where tx_dma_chan and rx_dma_chan are always same - spicommon_dmaworkaround_transfer_active(bus_attr->tx_dma_chan); - } - spi_new_trans(device_to_send, cur_trans_buf); - } - // Exit of the ISR, handle interrupt re-enable (if sending transaction), retry (if there's coming BG), - // or resume acquiring device task (if quit due to bus acquiring). - } while (!spi_bus_lock_bg_exit(lock, trans_found, &do_yield)); - - if (do_yield) portYIELD_FROM_ISR(); -} - -static SPI_MASTER_ISR_ATTR esp_err_t check_trans_valid(spi_device_handle_t handle, spi_transaction_t *trans_desc) -{ - SPI_CHECK(handle!=NULL, "invalid dev handle", ESP_ERR_INVALID_ARG); - spi_host_t *host = handle->host; - const spi_bus_attr_t* bus_attr = host->bus_attr; - bool tx_enabled = (trans_desc->flags & SPI_TRANS_USE_TXDATA) || (trans_desc->tx_buffer); - bool rx_enabled = (trans_desc->flags & SPI_TRANS_USE_RXDATA) || (trans_desc->rx_buffer); - spi_transaction_ext_t *t_ext = (spi_transaction_ext_t *)trans_desc; - bool dummy_enabled = (((trans_desc->flags & SPI_TRANS_VARIABLE_DUMMY)? t_ext->dummy_bits: handle->cfg.dummy_bits) != 0); - bool extra_dummy_enabled = handle->hal_dev.timing_conf.timing_dummy; - bool is_half_duplex = ((handle->cfg.flags & SPI_DEVICE_HALFDUPLEX) != 0); - - //check transmission length - SPI_CHECK((trans_desc->flags & SPI_TRANS_USE_RXDATA)==0 || trans_desc->rxlength <= 32, "SPI_TRANS_USE_RXDATA only available for rxdata transfer <= 32 bits", ESP_ERR_INVALID_ARG); - SPI_CHECK((trans_desc->flags & SPI_TRANS_USE_TXDATA)==0 || trans_desc->length <= 32, "SPI_TRANS_USE_TXDATA only available for txdata transfer <= 32 bits", ESP_ERR_INVALID_ARG); - SPI_CHECK(trans_desc->length <= bus_attr->max_transfer_sz*8, "txdata transfer > host maximum", ESP_ERR_INVALID_ARG); - SPI_CHECK(trans_desc->rxlength <= bus_attr->max_transfer_sz*8, "rxdata transfer > host maximum", ESP_ERR_INVALID_ARG); - SPI_CHECK(is_half_duplex || trans_desc->rxlength <= trans_desc->length, "rx length > tx length in full duplex mode", ESP_ERR_INVALID_ARG); - //check working mode - SPI_CHECK(!((trans_desc->flags & (SPI_TRANS_MODE_DIO|SPI_TRANS_MODE_QIO)) && (handle->cfg.flags & SPI_DEVICE_3WIRE)), "incompatible iface params", ESP_ERR_INVALID_ARG); - SPI_CHECK(!((trans_desc->flags & (SPI_TRANS_MODE_DIO|SPI_TRANS_MODE_QIO)) && !is_half_duplex), "incompatible iface params", ESP_ERR_INVALID_ARG); -#ifdef CONFIG_IDF_TARGET_ESP32 - SPI_CHECK(!is_half_duplex || !bus_attr->dma_enabled || !rx_enabled || !tx_enabled, "SPI half duplex mode does not support using DMA with both MOSI and MISO phases.", ESP_ERR_INVALID_ARG ); -#elif CONFIG_IDF_TARGET_ESP32S3 - SPI_CHECK(!is_half_duplex || !tx_enabled || !rx_enabled, "SPI half duplex mode is not supported when both MOSI and MISO phases are enabled.", ESP_ERR_INVALID_ARG); -#endif - //MOSI phase is skipped only when both tx_buffer and SPI_TRANS_USE_TXDATA are not set. - SPI_CHECK(trans_desc->length != 0 || !tx_enabled, "trans tx_buffer should be NULL and SPI_TRANS_USE_TXDATA should be cleared to skip MOSI phase.", ESP_ERR_INVALID_ARG); - //MISO phase is skipped only when both rx_buffer and SPI_TRANS_USE_RXDATA are not set. - //If set rxlength=0 in full_duplex mode, it will be automatically set to length - SPI_CHECK(!is_half_duplex || trans_desc->rxlength != 0 || !rx_enabled, "trans rx_buffer should be NULL and SPI_TRANS_USE_RXDATA should be cleared to skip MISO phase.", ESP_ERR_INVALID_ARG); - //In Full duplex mode, default rxlength to be the same as length, if not filled in. - // set rxlength to length is ok, even when rx buffer=NULL - if (trans_desc->rxlength==0 && !is_half_duplex) { - trans_desc->rxlength=trans_desc->length; - } - //Dummy phase is not available when both data out and in are enabled, regardless of FD or HD mode. - SPI_CHECK(!tx_enabled || !rx_enabled || !dummy_enabled || !extra_dummy_enabled, "Dummy phase is not available when both data out and in are enabled", ESP_ERR_INVALID_ARG); - - return ESP_OK; -} - -static SPI_MASTER_ISR_ATTR void uninstall_priv_desc(spi_trans_priv_t* trans_buf) -{ - spi_transaction_t *trans_desc = trans_buf->trans; - if ((void *)trans_buf->buffer_to_send != &trans_desc->tx_data[0] && - trans_buf->buffer_to_send != trans_desc->tx_buffer) { - free((void *)trans_buf->buffer_to_send); //force free, ignore const - } - // copy data from temporary DMA-capable buffer back to IRAM buffer and free the temporary one. - if ((void *)trans_buf->buffer_to_rcv != &trans_desc->rx_data[0] && - trans_buf->buffer_to_rcv != trans_desc->rx_buffer) { // NOLINT(clang-analyzer-unix.Malloc) - if (trans_desc->flags & SPI_TRANS_USE_RXDATA) { - memcpy((uint8_t *) & trans_desc->rx_data[0], trans_buf->buffer_to_rcv, (trans_desc->rxlength + 7) / 8); - } else { - memcpy(trans_desc->rx_buffer, trans_buf->buffer_to_rcv, (trans_desc->rxlength + 7) / 8); - } - free(trans_buf->buffer_to_rcv); - } -} - -static SPI_MASTER_ISR_ATTR esp_err_t setup_priv_desc(spi_transaction_t *trans_desc, spi_trans_priv_t* new_desc, bool isdma) -{ - *new_desc = (spi_trans_priv_t) { .trans = trans_desc, }; - - // rx memory assign - uint32_t* rcv_ptr; - if ( trans_desc->flags & SPI_TRANS_USE_RXDATA ) { - rcv_ptr = (uint32_t *)&trans_desc->rx_data[0]; - } else { - //if not use RXDATA neither rx_buffer, buffer_to_rcv assigned to NULL - rcv_ptr = trans_desc->rx_buffer; - } - if (rcv_ptr && isdma && (!esp_ptr_dma_capable(rcv_ptr) || ((int)rcv_ptr % 4 != 0))) { - //if rxbuf in the desc not DMA-capable, malloc a new one. The rx buffer need to be length of multiples of 32 bits to avoid heap corruption. - ESP_LOGD(SPI_TAG, "Allocate RX buffer for DMA" ); - rcv_ptr = heap_caps_malloc((trans_desc->rxlength + 31) / 8, MALLOC_CAP_DMA); - if (rcv_ptr == NULL) goto clean_up; - } - new_desc->buffer_to_rcv = rcv_ptr; - - // tx memory assign - const uint32_t *send_ptr; - if ( trans_desc->flags & SPI_TRANS_USE_TXDATA ) { - send_ptr = (uint32_t *)&trans_desc->tx_data[0]; - } else { - //if not use TXDATA neither tx_buffer, tx data assigned to NULL - send_ptr = trans_desc->tx_buffer ; - } - if (send_ptr && isdma && !esp_ptr_dma_capable( send_ptr )) { - //if txbuf in the desc not DMA-capable, malloc a new one - ESP_LOGD(SPI_TAG, "Allocate TX buffer for DMA" ); - uint32_t *temp = heap_caps_malloc((trans_desc->length + 7) / 8, MALLOC_CAP_DMA); - if (temp == NULL) goto clean_up; - - memcpy( temp, send_ptr, (trans_desc->length + 7) / 8 ); - send_ptr = temp; - } - new_desc->buffer_to_send = send_ptr; - - return ESP_OK; - -clean_up: - uninstall_priv_desc(new_desc); - return ESP_ERR_NO_MEM; -} - -esp_err_t SPI_MASTER_ATTR spi_device_queue_trans(spi_device_handle_t handle, spi_transaction_t *trans_desc, TickType_t ticks_to_wait) -{ - esp_err_t ret = check_trans_valid(handle, trans_desc); - if (ret != ESP_OK) return ret; - - spi_host_t *host = handle->host; - - SPI_CHECK(!spi_bus_device_is_polling(handle), "Cannot queue new transaction while previous polling transaction is not terminated.", ESP_ERR_INVALID_STATE ); - - spi_trans_priv_t trans_buf; - ret = setup_priv_desc(trans_desc, &trans_buf, (host->bus_attr->dma_enabled)); - if (ret != ESP_OK) return ret; - -#ifdef CONFIG_PM_ENABLE - esp_pm_lock_acquire(host->bus_attr->pm_lock); -#endif - //Send to queue and invoke the ISR. - - BaseType_t r = xQueueSend(handle->trans_queue, (void *)&trans_buf, ticks_to_wait); - if (!r) { - ret = ESP_ERR_TIMEOUT; -#ifdef CONFIG_PM_ENABLE - //Release APB frequency lock - esp_pm_lock_release(host->bus_attr->pm_lock); -#endif - goto clean_up; - } - - // The ISR will be invoked at correct time by the lock with `spi_bus_intr_enable`. - ret = spi_bus_lock_bg_request(handle->dev_lock); - if (ret != ESP_OK) { - goto clean_up; - } - return ESP_OK; - -clean_up: - uninstall_priv_desc(&trans_buf); - return ret; -} - -esp_err_t SPI_MASTER_ATTR spi_device_get_trans_result(spi_device_handle_t handle, spi_transaction_t **trans_desc, TickType_t ticks_to_wait) -{ - BaseType_t r; - spi_trans_priv_t trans_buf; - SPI_CHECK(handle!=NULL, "invalid dev handle", ESP_ERR_INVALID_ARG); - - //use the interrupt, block until return - r=xQueueReceive(handle->ret_queue, (void*)&trans_buf, ticks_to_wait); - if (!r) { - // The memory occupied by rx and tx DMA buffer destroyed only when receiving from the queue (transaction finished). - // If timeout, wait and retry. - // Every in-flight transaction request occupies internal memory as DMA buffer if needed. - return ESP_ERR_TIMEOUT; - } - //release temporary buffers - uninstall_priv_desc(&trans_buf); - (*trans_desc) = trans_buf.trans; - - return ESP_OK; -} - -//Porcelain to do one blocking transmission. -esp_err_t SPI_MASTER_ATTR spi_device_transmit(spi_device_handle_t handle, spi_transaction_t *trans_desc) -{ - esp_err_t ret; - spi_transaction_t *ret_trans; - //ToDo: check if any spi transfers in flight - ret = spi_device_queue_trans(handle, trans_desc, portMAX_DELAY); - if (ret != ESP_OK) return ret; - - ret = spi_device_get_trans_result(handle, &ret_trans, portMAX_DELAY); - if (ret != ESP_OK) return ret; - - assert(ret_trans == trans_desc); - return ESP_OK; -} - -esp_err_t SPI_MASTER_ISR_ATTR spi_device_acquire_bus(spi_device_t *device, TickType_t wait) -{ - spi_host_t *const host = device->host; - SPI_CHECK(wait==portMAX_DELAY, "acquire finite time not supported now.", ESP_ERR_INVALID_ARG); - SPI_CHECK(!spi_bus_device_is_polling(device), "Cannot acquire bus when a polling transaction is in progress.", ESP_ERR_INVALID_STATE ); - - esp_err_t ret = spi_bus_lock_acquire_start(device->dev_lock, wait); - if (ret != ESP_OK) { - return ret; - } - host->device_acquiring_lock = device; - - ESP_LOGD(SPI_TAG, "device%d locked the bus", device->id); - -#ifdef CONFIG_PM_ENABLE - // though we don't suggest to block the task before ``release_bus``, still allow doing so. - // this keeps the spi clock at 80MHz even if all tasks are blocked - esp_pm_lock_acquire(host->bus_attr->pm_lock); -#endif - //configure the device ahead so that we don't need to do it again in the following transactions - spi_setup_device(host->device[device->id]); - //the DMA is also occupied by the device, all the slave devices that using DMA should wait until bus released. - if (host->bus_attr->dma_enabled) { - //This workaround is only for esp32, where tx_dma_chan and rx_dma_chan are always same - spicommon_dmaworkaround_transfer_active(host->bus_attr->tx_dma_chan); - } - return ESP_OK; -} - -// This function restore configurations required in the non-polling mode -void SPI_MASTER_ISR_ATTR spi_device_release_bus(spi_device_t *dev) -{ - spi_host_t *host = dev->host; - - if (spi_bus_device_is_polling(dev)){ - ESP_EARLY_LOGE(SPI_TAG, "Cannot release bus when a polling transaction is in progress."); - assert(0); - } - - if (host->bus_attr->dma_enabled) { - //This workaround is only for esp32, where tx_dma_chan and rx_dma_chan are always same - spicommon_dmaworkaround_idle(host->bus_attr->tx_dma_chan); - } - //Tell common code DMA workaround that our DMA channel is idle. If needed, the code will do a DMA reset. - - //allow clock to be lower than 80MHz when all tasks blocked -#ifdef CONFIG_PM_ENABLE - //Release APB frequency lock - esp_pm_lock_release(host->bus_attr->pm_lock); -#endif - ESP_LOGD(SPI_TAG, "device%d release bus", dev->id); - - host->device_acquiring_lock = NULL; - esp_err_t ret = spi_bus_lock_acquire_end(dev->dev_lock); - assert(ret == ESP_OK); -} - -esp_err_t SPI_MASTER_ISR_ATTR spi_device_polling_start(spi_device_handle_t handle, spi_transaction_t *trans_desc, TickType_t ticks_to_wait) -{ - esp_err_t ret; - SPI_CHECK(ticks_to_wait == portMAX_DELAY, "currently timeout is not available for polling transactions", ESP_ERR_INVALID_ARG); - ret = check_trans_valid(handle, trans_desc); - if (ret!=ESP_OK) return ret; - SPI_CHECK(!spi_bus_device_is_polling(handle), "Cannot send polling transaction while the previous polling transaction is not terminated.", ESP_ERR_INVALID_STATE ); - - /* If device_acquiring_lock is set to handle, it means that the user has already - * acquired the bus thanks to the function `spi_device_acquire_bus()`. - * In that case, we don't need to take the lock again. */ - spi_host_t *host = handle->host; - if (host->device_acquiring_lock != handle) { - ret = spi_bus_lock_acquire_start(handle->dev_lock, ticks_to_wait); - } else { - ret = spi_bus_lock_wait_bg_done(handle->dev_lock, ticks_to_wait); - } - if (ret != ESP_OK) return ret; - - ret = setup_priv_desc(trans_desc, &host->cur_trans_buf, (host->bus_attr->dma_enabled)); - if (ret!=ESP_OK) return ret; - - //Polling, no interrupt is used. - host->polling = true; - - ESP_LOGV(SPI_TAG, "polling trans"); - spi_new_trans(handle, &host->cur_trans_buf); - - return ESP_OK; -} - -esp_err_t SPI_MASTER_ISR_ATTR spi_device_polling_end(spi_device_handle_t handle, TickType_t ticks_to_wait) -{ - SPI_CHECK(handle != NULL, "invalid dev handle", ESP_ERR_INVALID_ARG); - spi_host_t *host = handle->host; - - assert(host->cur_cs == handle->id); - assert(handle == get_acquiring_dev(host)); - - TickType_t start = xTaskGetTickCount(); - while (!spi_hal_usr_is_done(&host->hal)) { - TickType_t end = xTaskGetTickCount(); - if (end - start > ticks_to_wait) { - return ESP_ERR_TIMEOUT; - } - } - - ESP_LOGV(SPI_TAG, "polling trans done"); - //deal with the in-flight transaction - spi_post_trans(host); - //release temporary buffers - uninstall_priv_desc(&host->cur_trans_buf); - - host->polling = false; - if (host->device_acquiring_lock != handle) { - assert(host->device_acquiring_lock == NULL); - spi_bus_lock_acquire_end(handle->dev_lock); - } - - return ESP_OK; -} - -esp_err_t SPI_MASTER_ISR_ATTR spi_device_polling_transmit(spi_device_handle_t handle, spi_transaction_t* trans_desc) -{ - esp_err_t ret; -if (handle->host->mutex) xSemaphoreTake(handle->host->mutex, portMAX_DELAY); - - ret = spi_device_polling_start(handle, trans_desc, portMAX_DELAY); - if (ret != ESP_OK) { - if (handle->host->mutex) xSemaphoreGive(handle->host->mutex); - return ret; - } - - ret = spi_device_polling_end(handle, portMAX_DELAY); - if (handle->host->mutex) xSemaphoreGive(handle->host->mutex); - return ret; -} diff --git a/components/driver_bt/CMakeLists.txt b/components/driver_bt/CMakeLists.txt index 66dd9955..3f66c182 100644 --- a/components/driver_bt/CMakeLists.txt +++ b/components/driver_bt/CMakeLists.txt @@ -1,6 +1,8 @@ -idf_component_register( SRC_DIRS . - INCLUDE_DIRS . - PRIV_REQUIRES services bt display console tools platform_config +if(IDF_TARGET STREQUAL "esp32") + idf_component_register( SRC_DIRS . + INCLUDE_DIRS . + PRIV_REQUIRES services bt display console tools platform_config ) +endif() diff --git a/components/driver_bt/bt_app_core - Copy.c.old b/components/driver_bt/bt_app_core - Copy.c.old new file mode 100644 index 00000000..4d986735 --- /dev/null +++ b/components/driver_bt/bt_app_core - Copy.c.old @@ -0,0 +1,172 @@ +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" +#include "esp_bt.h" +#include "esp_bt_main.h" +#include "esp_gap_bt_api.h" +#include "bt_app_core.h" +#include "tools.h" + +static const char *TAG = "btappcore"; + +static void bt_app_task_handler(void *arg); +static bool bt_app_send_msg(bt_app_msg_t *msg); +static void bt_app_work_dispatched(bt_app_msg_t *msg); + +static xQueueHandle s_bt_app_task_queue; +static bool running; + +bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback) +{ + ESP_LOGV(TAG,"%s event 0x%x, param len %d", __func__, event, param_len); + + bt_app_msg_t msg; + memset(&msg, 0, sizeof(bt_app_msg_t)); + + msg.sig = BT_APP_SIG_WORK_DISPATCH; + msg.event = event; + msg.cb = p_cback; + + if (param_len == 0) { + return bt_app_send_msg(&msg); + } else if (p_params && param_len > 0) { + if ((msg.param = clone_obj_psram(p_params, param_len)) != NULL) { + /* check if caller has provided a copy callback to do the deep copy */ + if (p_copy_cback) { + p_copy_cback(&msg, msg.param, p_params); + } + return bt_app_send_msg(&msg); + } + } + + return false; +} + +static bool bt_app_send_msg(bt_app_msg_t *msg) +{ + if (msg == NULL) { + return false; + } + + if (xQueueSend(s_bt_app_task_queue, msg, 10 / portTICK_RATE_MS) != pdTRUE) { + ESP_LOGE(TAG,"%s xQueue send failed", __func__); + return false; + } + return true; +} + +static void bt_app_work_dispatched(bt_app_msg_t *msg) +{ + if (msg->cb) { + msg->cb(msg->event, msg->param); + } +} + +static void bt_app_task_handler(void *arg) +{ + bt_app_msg_t msg; + esp_err_t err; + + s_bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t)); + + esp_bt_controller_mem_release(ESP_BT_MODE_BLE); + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + + if ((err = esp_bt_controller_init(&bt_cfg)) != ESP_OK) { + ESP_LOGE(TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(err)); + goto exit; + } + + if ((err = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) != ESP_OK) { + ESP_LOGE(TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(err)); + goto exit; + } + + if ((err = esp_bluedroid_init()) != ESP_OK) { + ESP_LOGE(TAG, "%s initialize bluedroid failed: %s\n", __func__, esp_err_to_name(err)); + goto exit; + } + + if ((err = esp_bluedroid_enable()) != ESP_OK) { + ESP_LOGE(TAG, "%s enable bluedroid failed: %s\n", __func__, esp_err_to_name(err)); + goto exit; + } + + /* Bluetooth device name, connection mode and profile set up */ + bt_app_work_dispatch((bt_av_hdl_stack_evt_t*) arg, BT_APP_EVT_STACK_UP, NULL, 0, NULL); + +#if (CONFIG_BT_SSP_ENABLED) + /* Set default parameters for Secure Simple Pairing */ + esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE; + esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO; + esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t)); +#endif + + running = true; + + while (running) { + if (pdTRUE == xQueueReceive(s_bt_app_task_queue, &msg, (portTickType)portMAX_DELAY)) { + ESP_LOGV(TAG,"%s, sig 0x%x, 0x%x", __func__, msg.sig, msg.event); + + switch (msg.sig) { + case BT_APP_SIG_WORK_DISPATCH: + bt_app_work_dispatched(&msg); + break; + default: + ESP_LOGW(TAG,"%s, unhandled sig: %d", __func__, msg.sig); + break; + } + + if (msg.param) { + free(msg.param); + } + } else { + ESP_LOGW(TAG,"No messaged received from queue."); + } + } + + ESP_LOGD(TAG, "bt_app_task shutting down"); + + if (esp_bluedroid_disable() != ESP_OK) goto exit; + // this disable has a sleep timer BTA_DISABLE_DELAY in bt_target.h and + // if we don't wait for it then disable crashes... don't know why + vTaskDelay(2*200 / portTICK_PERIOD_MS); + + ESP_LOGD(TAG, "esp_bluedroid_disable called successfully"); + if (esp_bluedroid_deinit() != ESP_OK) goto exit; + + ESP_LOGD(TAG, "esp_bluedroid_deinit called successfully"); + if (esp_bt_controller_disable() != ESP_OK) goto exit; + + ESP_LOGD(TAG, "esp_bt_controller_disable called successfully"); + if (esp_bt_controller_deinit() != ESP_OK) goto exit; + + ESP_LOGD(TAG, "bt stopped successfully"); + +exit: + vQueueDelete(s_bt_app_task_queue); + running = false; + vTaskDelete(NULL); +} + +void bt_app_task_start_up(bt_av_hdl_stack_evt_t* handler) +{ + xTaskCreate(bt_app_task_handler, "BtAppT", 4096, handler, configMAX_PRIORITIES - 3, NULL); +} + +void bt_app_task_shut_down(void) +{ + running = false; +} diff --git a/components/driver_bt/bt_app_source - Copy.c.old b/components/driver_bt/bt_app_source - Copy.c.old new file mode 100644 index 00000000..29c54663 --- /dev/null +++ b/components/driver_bt/bt_app_source - Copy.c.old @@ -0,0 +1,1064 @@ +#include +#include +#include +#include +#include "bt_app_core.h" +#include "esp_log.h" +#include "esp_bt.h" +#include "esp_bt_device.h" +#include "esp_bt_main.h" +#include "esp_gap_bt_api.h" +#include "esp_a2dp_api.h" +#include "esp_avrc_api.h" +#include "esp_console.h" +#include "esp_pthread.h" +#include "esp_system.h" +#include "esp_wifi.h" +#include "freertos/timers.h" +#include "argtable3/argtable3.h" +#include "platform_config.h" +#include "messaging.h" +#include "cJSON.h" +#include "tools.h" + +static const char * TAG = "bt_app_source"; +static const char * BT_RC_CT_TAG="RCCT"; +extern int32_t output_bt_data(uint8_t *data, int32_t len); +extern void output_bt_tick(void); +extern char* output_state_str(void); +extern bool output_stopped(void); +extern bool is_recovery_running; + +static void bt_app_av_state_connecting(uint16_t event, void *param); +static void filter_inquiry_scan_result(esp_bt_gap_cb_param_t *param); + +char * APP_AV_STATE_DESC[] = { + "APP_AV_STATE_IDLE", + "APP_AV_STATE_DISCOVERING", + "APP_AV_STATE_DISCOVERED", + "APP_AV_STATE_UNCONNECTED", + "APP_AV_STATE_CONNECTING", + "APP_AV_STATE_CONNECTED", + "APP_AV_STATE_DISCONNECTING" +}; +static char * ESP_AVRC_CT_DESC[]={ + "ESP_AVRC_CT_CONNECTION_STATE_EVT", + "ESP_AVRC_CT_PASSTHROUGH_RSP_EVT", + "ESP_AVRC_CT_METADATA_RSP_EVT", + "ESP_AVRC_CT_PLAY_STATUS_RSP_EVT", + "ESP_AVRC_CT_CHANGE_NOTIFY_EVT", + "ESP_AVRC_CT_REMOTE_FEATURES_EVT", + "ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT", + "ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT" + }; + +#define BT_APP_HEART_BEAT_EVT (0xff00) +// AVRCP used transaction label +#define APP_RC_CT_TL_GET_CAPS (0) +#define APP_RC_CT_TL_RN_VOLUME_CHANGE (1) +#define PEERS_LIST_MAINTAIN_RESET -129 +#define PEERS_LIST_MAINTAIN_PURGE -129 + +/// handler for bluetooth stack enabled events +static void bt_av_hdl_stack_evt(uint16_t event, void *p_param); + +/// callback function for A2DP source +static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param); + +/// callback function for AVRCP controller +static void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param); + +/// avrc CT event handler +static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param); + +/// callback function for A2DP source audio data stream +static void a2d_app_heart_beat(void *arg); + +/// A2DP application state machine +static void bt_app_av_sm_hdlr(uint16_t event, void *param); + +/* A2DP application state machine handler for each state */ +static void bt_app_av_state_unconnected(uint16_t event, void *param); +static void bt_app_av_state_connecting(uint16_t event, void *param); +static void bt_app_av_state_connected(uint16_t event, void *param); +static void bt_app_av_state_disconnecting(uint16_t event, void *param); +static void handle_connect_state_unconnected(uint16_t event, esp_a2d_cb_param_t *param); +static void handle_connect_state_connecting(uint16_t event, esp_a2d_cb_param_t *param); +static void handle_connect_state_connected(uint16_t event, esp_a2d_cb_param_t *param); +static void handle_connect_state_disconnecting(uint16_t event, esp_a2d_cb_param_t *param); +static void bt_av_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *event_parameter); + +static esp_bd_addr_t s_peer_bda = {0}; +static uint8_t s_peer_bdname[ESP_BT_GAP_MAX_BDNAME_LEN + 1]; +int bt_app_source_a2d_state = APP_AV_STATE_IDLE; +int bt_app_source_media_state = APP_AV_MEDIA_STATE_IDLE; +static uint32_t s_pkt_cnt = 0; +static TimerHandle_t s_tmr=NULL; +static int prev_duration=10000; +static esp_avrc_rn_evt_cap_mask_t s_avrc_peer_rn_cap; +static int s_connecting_intv = 0; +cJSON * peers_list=NULL; + +static struct { + char * sink_name; +} squeezelite_conf; + +static cJSON * peers_list_get_entry(const char * s_peer_bdname){ + cJSON * element=NULL; + cJSON_ArrayForEach(element,peers_list){ + cJSON * name = cJSON_GetObjectItem(element,"name"); + if(name && !strcmp(cJSON_GetStringValue(name),s_peer_bdname)){ + ESP_LOGV(TAG,"Entry name %s found in current scan list", s_peer_bdname); + return element; + } + } + ESP_LOGV(TAG,"Entry name %s NOT found in current scan list", s_peer_bdname); + return NULL; +} + +static void peers_list_reset(){ + cJSON * element=NULL; + cJSON_ArrayForEach(element,peers_list){ + cJSON * rssi = cJSON_GetObjectItem(element,"rssi"); + if(rssi){ + rssi->valuedouble = -129; + rssi->valueint = -129; + } + } +} + +static void peers_list_purge(){ + cJSON * element=NULL; + cJSON_ArrayForEach(element,peers_list){ + cJSON * rssi_val = cJSON_GetObjectItem(element,"rssi"); + if(rssi_val && rssi_val->valuedouble == -129){ + cJSON * name = cJSON_GetObjectItem(element,"name"); + ESP_LOGV(TAG,"Purging %s", cJSON_GetStringValue(name)?cJSON_GetStringValue(name):"Unknown"); + cJSON_DetachItemViaPointer(peers_list,element); + cJSON_Delete(element); + } + } +} + +static cJSON * peers_list_create_entry(const char * s_peer_bdname, int32_t rssi){ + cJSON * entry = cJSON_CreateObject(); + cJSON_AddStringToObject(entry,"name",s_peer_bdname); + cJSON_AddNumberToObject(entry,"rssi",rssi); + return entry; +} + +static void peers_list_update_add(const char * s_peer_bdname, int32_t rssi){ + cJSON * element= peers_list_get_entry(s_peer_bdname); + if(element){ + cJSON * rssi_val = cJSON_GetObjectItem(element,"rssi"); + if(rssi_val && rssi_val->valuedouble != rssi){ + ESP_LOGV(TAG,"Updating BT Sink Device: %s rssi to %i", s_peer_bdname,rssi); + rssi_val->valuedouble = rssi; + rssi_val->valueint = rssi; + } + } + else { + ESP_LOGI(TAG,"Found BT Sink Device: %s rssi is %i", s_peer_bdname,rssi); + element = peers_list_create_entry( s_peer_bdname, rssi); + cJSON_AddItemToArray(peers_list,element); + } +} + +static void peers_list_maintain(const char * s_peer_bdname, int32_t rssi){ + if(!peers_list){ + ESP_LOGV(TAG,"Initializing BT peers list"); + peers_list=cJSON_CreateArray(); + } + if(rssi==PEERS_LIST_MAINTAIN_RESET){ + ESP_LOGV(TAG,"Resetting BT peers list"); + peers_list_reset(); + } + else if(rssi==PEERS_LIST_MAINTAIN_PURGE){ + ESP_LOGV(TAG,"Purging BT peers list"); + peers_list_purge(); + } + if(s_peer_bdname) { + ESP_LOGV(TAG,"Adding/Updating peer %s rssi %i", s_peer_bdname,rssi); + peers_list_update_add(s_peer_bdname, rssi); + } + char * list_json = cJSON_Print(peers_list); + if(list_json){ + messaging_post_message(MESSAGING_INFO, MESSAGING_CLASS_BT, list_json); + ESP_LOGV(TAG,"%s", list_json); + free(list_json); + } +} + +int bt_app_source_get_a2d_state(){ + if(!is_recovery_running){ + // if we are in recovery mode, don't log BT status + ESP_LOGD(TAG,"a2dp status: %u = %s", bt_app_source_a2d_state, APP_AV_STATE_DESC[bt_app_source_a2d_state]); + } + return bt_app_source_a2d_state; +} + +int bt_app_source_get_media_state(){ + ESP_LOGD(TAG,"media state : %u ", bt_app_source_media_state); + return bt_app_source_media_state; +} + +void set_app_source_state(int new_state){ + if(bt_app_source_a2d_state!=new_state){ + ESP_LOGD(TAG, "Updating state from %s to %s", APP_AV_STATE_DESC[bt_app_source_a2d_state], APP_AV_STATE_DESC[new_state]); + bt_app_source_a2d_state=new_state; + } +} + +void set_a2dp_media_state(int new_state){ + if(bt_app_source_media_state!=new_state){ + bt_app_source_media_state=new_state; + } +} + +void hal_bluetooth_init(const char * options) +{ + struct { + struct arg_str *sink_name; + struct arg_end *end; + } squeezelite_args; + + ESP_LOGD(TAG,"Initializing Bluetooth HAL"); + + squeezelite_args.sink_name = arg_str0("n", "name", "", "the name of the bluetooth to connect to"); + squeezelite_args.end = arg_end(2); + + ESP_LOGD(TAG,"Copying parameters"); + char * opts = strdup_psram(options); + char **argv = malloc_init_external(sizeof(char**)*15); + + size_t argv_size=15; + + // change parms so ' appear as " for parsing the options + for (char* p = opts; (p = strchr(p, '\'')); ++p) *p = '"'; + ESP_LOGD(TAG,"Splitting arg line: %s", opts); + + argv_size = esp_console_split_argv(opts, argv, argv_size); + ESP_LOGD(TAG,"Parsing parameters"); + int nerrors = arg_parse(argv_size , argv, (void **) &squeezelite_args); + if (nerrors != 0) { + ESP_LOGD(TAG,"Parsing Errors"); + arg_print_errors(stdout, squeezelite_args.end, "BT"); + arg_print_glossary_gnu(stdout, (void **) &squeezelite_args); + free(opts); + free(argv); + return; + } + + if(squeezelite_args.sink_name->count == 0) + { + squeezelite_conf.sink_name = config_alloc_get_default(NVS_TYPE_STR, "a2dp_sink_name", NULL, 0); + if(!squeezelite_conf.sink_name || strlen(squeezelite_conf.sink_name)==0 ){ + ESP_LOGW(TAG,"Unable to retrieve the a2dp sink name from nvs."); + } + } else { + squeezelite_conf.sink_name=strdup_psram(squeezelite_args.sink_name->sval[0]); + // sync with NVS + esp_err_t err=ESP_OK; + if((err= config_set_value(NVS_TYPE_STR, "a2dp_sink_name", squeezelite_args.sink_name->sval[0]))!=ESP_OK){ + ESP_LOGE(TAG,"Error setting Bluetooth audio device name %s. %s",squeezelite_args.sink_name->sval[0], esp_err_to_name(err)); + } + else { + ESP_LOGI(TAG,"Bluetooth audio device name changed to %s",squeezelite_args.sink_name->sval[0]); + } + } + + ESP_LOGD(TAG,"Freeing options"); + free(argv); + free(opts); + + // create task and run event loop + bt_app_task_start_up(bt_av_hdl_stack_evt); + + /* + * Set default parameters for Legacy Pairing + * Use variable pin, input pin code when pairing + */ + esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE; + esp_bt_pin_code_t pin_code; + esp_bt_gap_set_pin(pin_type, 0, pin_code); + +} + +void hal_bluetooth_stop(void) { + bt_app_task_shut_down(); +} + +static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param) +{ + bt_app_work_dispatch(bt_app_av_sm_hdlr, event, param, sizeof(esp_a2d_cb_param_t), NULL); +} + +static void handle_bt_gap_pin_req(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param){ + char * pin_str = config_alloc_get_default(NVS_TYPE_STR, "a2dp_spin", "0000", 0); + int pinlen=pin_str?strlen(pin_str):0; + if (pin_str && ((!param->pin_req.min_16_digit && pinlen==4) || (param->pin_req.min_16_digit && pinlen==16))) { + ESP_LOGI(TAG,"Input pin code %s: ",pin_str); + esp_bt_pin_code_t pin_code; + for (size_t i = 0; i < pinlen; i++) + { + pin_code[i] = pin_str[i]; + } + esp_bt_gap_pin_reply(param->pin_req.bda, true, pinlen, pin_code); + } + else { + if(pinlen>0){ + ESP_LOGW(TAG,"Pin length: %u does not match the length expected by the device: %u", pinlen, ((param->pin_req.min_16_digit)?16:4)); + } + else { + ESP_LOGW(TAG, "No security Pin provided. Trying with default pins."); + } + if (param->pin_req.min_16_digit) { + ESP_LOGI(TAG,"Input pin code: 0000 0000 0000 0000"); + esp_bt_pin_code_t pin_code = {0}; + esp_bt_gap_pin_reply(param->pin_req.bda, true, 16, pin_code); + } else { + ESP_LOGI(TAG,"Input pin code: 1234"); + esp_bt_pin_code_t pin_code; + pin_code[0] = '1'; + pin_code[1] = '2'; + pin_code[2] = '3'; + pin_code[3] = '4'; + esp_bt_gap_pin_reply(param->pin_req.bda, true, 4, pin_code); + } + } + FREE_AND_NULL(pin_str); +} + +static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) +{ + + switch (event) { + case ESP_BT_GAP_DISC_RES_EVT: { + filter_inquiry_scan_result(param); + break; + } + case ESP_BT_GAP_DISC_STATE_CHANGED_EVT: { + + if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STOPPED) { + peers_list_maintain(NULL, PEERS_LIST_MAINTAIN_PURGE); + if (bt_app_source_a2d_state == APP_AV_STATE_DISCOVERED) { + set_app_source_state(APP_AV_STATE_CONNECTING); + ESP_LOGI(TAG,"Discovery completed. Ready to start connecting to %s. ", s_peer_bdname); + esp_a2d_source_connect(s_peer_bda); + } else { + // not discovered, continue to discover + ESP_LOGI(TAG, "Device discovery failed, continue to discover..."); + esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0); + } + } else if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STARTED) { + ESP_LOGI(TAG, "Discovery started."); + peers_list_maintain(NULL, PEERS_LIST_MAINTAIN_RESET); + } + break; + } + case ESP_BT_GAP_RMT_SRVCS_EVT: + ESP_LOG_DEBUG_EVENT(TAG,QUOTE(ESP_BT_GAP_RMT_SRVCS_EVT)); + break; + case ESP_BT_GAP_RMT_SRVC_REC_EVT: + ESP_LOG_DEBUG_EVENT(TAG,QUOTE(ESP_BT_GAP_RMT_SRVC_REC_EVT)); + break; + case ESP_BT_GAP_AUTH_CMPL_EVT: { + if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) { + ESP_LOGI(TAG,"authentication success: %s", param->auth_cmpl.device_name); + //esp_log_buffer_hex(param->auth_cmpl.bda, ESP_BD_ADDR_LEN); + } else { + ESP_LOGE(TAG,"authentication failed, status:%d", param->auth_cmpl.stat); + } + break; + } + case ESP_BT_GAP_PIN_REQ_EVT: + handle_bt_gap_pin_req(event, param); + break; + +#if (CONFIG_BT_SSP_ENABLED == true) + case ESP_BT_GAP_CFM_REQ_EVT: + ESP_LOGI(TAG,"ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %d", param->cfm_req.num_val); + esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true); + break; + case ESP_BT_GAP_KEY_NOTIF_EVT: + ESP_LOGI(TAG,"ESP_BT_GAP_KEY_NOTIF_EVT passkey:%d", param->key_notif.passkey); + break; + ESP_LOGI(TAG,"ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!"); + break; +#endif + + default: { + ESP_LOGI(TAG,"event: %d", event); + break; + } + } + return; +} +int heart_beat_delay[] = { + 1000, + 1000, + 1000, + 1000, + 10000, + 500, + 1000 +}; + +static void a2d_app_heart_beat(void *arg) +{ + bt_app_work_dispatch(bt_app_av_sm_hdlr, BT_APP_HEART_BEAT_EVT, NULL, 0, NULL); + int tmrduration=heart_beat_delay[bt_app_source_a2d_state]; + if(prev_duration!=tmrduration){ + xTimerChangePeriod(s_tmr,tmrduration, portMAX_DELAY); + ESP_LOGD(TAG,"New heartbeat is %u",tmrduration); + prev_duration=tmrduration; + } + else { + ESP_LOGD(TAG,"Starting Heart beat timer for %ums",tmrduration); + } + xTimerStart(s_tmr, portMAX_DELAY); +} + +static const char * conn_state_str(esp_a2d_connection_state_t state){ + char * statestr = "Unknown"; + switch (state) + { + case ESP_A2D_CONNECTION_STATE_DISCONNECTED: + statestr=STR(ESP_A2D_CONNECTION_STATE_DISCONNECTED); + break; + case ESP_A2D_CONNECTION_STATE_CONNECTING: + statestr=STR(ESP_A2D_CONNECTION_STATE_CONNECTING); + break; + case ESP_A2D_CONNECTION_STATE_CONNECTED: + statestr=STR(ESP_A2D_CONNECTION_STATE_CONNECTED); + break; + case ESP_A2D_CONNECTION_STATE_DISCONNECTING: + statestr=STR(ESP_A2D_CONNECTION_STATE_DISCONNECTING); + break; + default: + break; + } + return statestr; +} + +static void unexpected_connection_state(int from, esp_a2d_connection_state_t to) +{ + ESP_LOGW(TAG,"Unexpected connection state change. App State: %s (%u) Connection State %s (%u)", APP_AV_STATE_DESC[from], from,conn_state_str(to), to); +} + +static void handle_connect_state_unconnected(uint16_t event, esp_a2d_cb_param_t *param){ + ESP_LOGV(TAG, "A2DP Event while unconnected "); + switch (param->conn_stat.state) + { + case ESP_A2D_CONNECTION_STATE_DISCONNECTED: + unexpected_connection_state(bt_app_source_a2d_state, param->conn_stat.state); + break; + case ESP_A2D_CONNECTION_STATE_CONNECTING: + unexpected_connection_state(bt_app_source_a2d_state, param->conn_stat.state); + break; + case ESP_A2D_CONNECTION_STATE_CONNECTED: + unexpected_connection_state(bt_app_source_a2d_state, param->conn_stat.state); + ESP_LOGE(TAG,"Connection state event received while status was unconnected. Routing message to connecting state handler. State : %u",param->conn_stat.state); + if (param->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED){ + handle_connect_state_connecting(event, param); + } + break; + case ESP_A2D_CONNECTION_STATE_DISCONNECTING: + unexpected_connection_state(bt_app_source_a2d_state, param->conn_stat.state); + break; + default: + break; + } + +} + +static void handle_connect_state_connecting(uint16_t event, esp_a2d_cb_param_t *param){ + ESP_LOGV(TAG, "A2DP connection state event : %s ",conn_state_str(param->conn_stat.state)); + + switch (param->conn_stat.state) + { + case ESP_A2D_CONNECTION_STATE_DISCONNECTED: + if(param->conn_stat.disc_rsn!=ESP_A2D_DISC_RSN_NORMAL){ + ESP_LOGE(TAG,"A2DP had an abnormal disconnect event"); + } + else { + ESP_LOGW(TAG,"A2DP connect unsuccessful"); + } + set_app_source_state(APP_AV_STATE_UNCONNECTED); + break; + case ESP_A2D_CONNECTION_STATE_CONNECTING: + break; + case ESP_A2D_CONNECTION_STATE_CONNECTED: + set_app_source_state(APP_AV_STATE_CONNECTED); + set_a2dp_media_state(APP_AV_MEDIA_STATE_IDLE); + ESP_LOGD(TAG,"Setting scan mode to ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE"); + esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE); + ESP_LOGD(TAG,"Done setting scan mode. App state is now CONNECTED and media state IDLE."); + break; + case ESP_A2D_CONNECTION_STATE_DISCONNECTING: + unexpected_connection_state(bt_app_source_a2d_state, param->conn_stat.state); + set_app_source_state(APP_AV_STATE_DISCONNECTING); + break; + default: + break; + } +} +static void handle_connect_state_connected(uint16_t event, esp_a2d_cb_param_t *param){ + ESP_LOGV(TAG, "A2DP Event while connected "); + switch (param->conn_stat.state) + { + case ESP_A2D_CONNECTION_STATE_DISCONNECTED: + ESP_LOGW(TAG,"a2dp disconnected"); + set_app_source_state(APP_AV_STATE_UNCONNECTED); + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + break; + case ESP_A2D_CONNECTION_STATE_CONNECTING: + unexpected_connection_state(bt_app_source_a2d_state, param->conn_stat.state); + break; + case ESP_A2D_CONNECTION_STATE_CONNECTED: + unexpected_connection_state(bt_app_source_a2d_state, param->conn_stat.state); + break; + case ESP_A2D_CONNECTION_STATE_DISCONNECTING: + set_app_source_state(APP_AV_STATE_DISCONNECTING); + + break; + default: + break; + } +} + +static void handle_connect_state_disconnecting(uint16_t event, esp_a2d_cb_param_t *param){ + ESP_LOGV(TAG, "A2DP Event while disconnecting "); + switch (param->conn_stat.state) + { + case ESP_A2D_CONNECTION_STATE_DISCONNECTED: + ESP_LOGI(TAG,"a2dp disconnected"); + set_app_source_state(APP_AV_STATE_UNCONNECTED); + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + break; + case ESP_A2D_CONNECTION_STATE_CONNECTING: + unexpected_connection_state(bt_app_source_a2d_state, param->conn_stat.state); + break; + case ESP_A2D_CONNECTION_STATE_CONNECTED: + unexpected_connection_state(bt_app_source_a2d_state, param->conn_stat.state); + break; + case ESP_A2D_CONNECTION_STATE_DISCONNECTING: + unexpected_connection_state(bt_app_source_a2d_state, param->conn_stat.state); + break; + default: + break; + } + +} + +static void bt_app_av_sm_hdlr(uint16_t event, void *param) +{ + ESP_LOGV(TAG,"bt_app_av_sm_hdlr.%s a2d state: %s", event==BT_APP_HEART_BEAT_EVT?"Heart Beat.":"",APP_AV_STATE_DESC[bt_app_source_a2d_state]); + switch (bt_app_source_a2d_state) { + case APP_AV_STATE_DISCOVERING: + ESP_LOGV(TAG,"state %s, evt 0x%x, output state: %s", APP_AV_STATE_DESC[bt_app_source_a2d_state], event, output_state_str()); + break; + case APP_AV_STATE_DISCOVERED: + ESP_LOGV(TAG,"state %s, evt 0x%x, output state: %s", APP_AV_STATE_DESC[bt_app_source_a2d_state], event, output_state_str()); + break; + case APP_AV_STATE_UNCONNECTED: + bt_app_av_state_unconnected(event, param); + break; + case APP_AV_STATE_CONNECTING: + bt_app_av_state_connecting(event, param); + break; + case APP_AV_STATE_CONNECTED: + bt_app_av_state_connected(event, param); + break; + case APP_AV_STATE_DISCONNECTING: + bt_app_av_state_disconnecting(event, param); + break; + default: + ESP_LOGE(TAG,"%s invalid state %d", __func__, bt_app_source_a2d_state); + break; + } +} + +static char *bda2str(esp_bd_addr_t bda, char *str, size_t size) +{ + if (bda == NULL || str == NULL || size < 18) { + return NULL; + } + + uint8_t *p = bda; + sprintf(str, "%02x:%02x:%02x:%02x:%02x:%02x", + p[0], p[1], p[2], p[3], p[4], p[5]); + return str; +} +static bool get_name_from_eir(uint8_t *eir, uint8_t *bdname, uint8_t *bdname_len) +{ + uint8_t *rmt_bdname = NULL; + uint8_t rmt_bdname_len = 0; + + if (!eir) { + return false; + } + + rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_CMPL_LOCAL_NAME, &rmt_bdname_len); + if (!rmt_bdname) { + rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_SHORT_LOCAL_NAME, &rmt_bdname_len); + } + + if (rmt_bdname) { + if (rmt_bdname_len > ESP_BT_GAP_MAX_BDNAME_LEN) { + rmt_bdname_len = ESP_BT_GAP_MAX_BDNAME_LEN; + } + + if (bdname) { + memcpy(bdname, rmt_bdname, rmt_bdname_len); + bdname[rmt_bdname_len] = '\0'; + } + if (bdname_len) { + *bdname_len = rmt_bdname_len; + } + return true; + } + + return false; +} + +static void filter_inquiry_scan_result(esp_bt_gap_cb_param_t *param) +{ + char bda_str[18]; + uint32_t cod = 0; + int32_t rssi = -129; /* invalid value */ + uint8_t *eir = NULL; + uint8_t nameLen = 0; + esp_bt_gap_dev_prop_t *p; + memset(bda_str, 0x00, sizeof(bda_str)); + if(bt_app_source_a2d_state != APP_AV_STATE_DISCOVERING) + { + // Ignore messages that might have been queued already + // when we've discovered the target device. + return; + } + memset(s_peer_bdname, 0x00,sizeof(s_peer_bdname)); + + bda2str(param->disc_res.bda, bda_str, 18); + + ESP_LOGV(TAG,"\n=======================\nScanned device: %s",bda_str ); + for (int i = 0; i < param->disc_res.num_prop; i++) { + p = param->disc_res.prop + i; + switch (p->type) { + case ESP_BT_GAP_DEV_PROP_COD: + cod = *(uint32_t *)(p->val); + ESP_LOGV(TAG,"-- Class of Device: 0x%x", cod); + break; + case ESP_BT_GAP_DEV_PROP_RSSI: + rssi = *(int8_t *)(p->val); + ESP_LOGV(TAG,"-- RSSI: %d", rssi); + break; + case ESP_BT_GAP_DEV_PROP_EIR: + eir = (uint8_t *)(p->val); + ESP_LOGV(TAG,"-- EIR: %u", *eir); + break; + case ESP_BT_GAP_DEV_PROP_BDNAME: + nameLen = (p->len > ESP_BT_GAP_MAX_BDNAME_LEN) ? ESP_BT_GAP_MAX_BDNAME_LEN : (uint8_t)p->len; + memcpy(s_peer_bdname, (uint8_t *)(p->val), nameLen); + s_peer_bdname[nameLen] = '\0'; + ESP_LOGV(TAG,"-- Name: %s", s_peer_bdname); + break; + default: + break; + } + } + if (!esp_bt_gap_is_valid_cod(cod)){ + /* search for device with MAJOR service class as "rendering" in COD */ + ESP_LOGV(TAG,"--Invalid class of device. Skipping.\n"); + return; + } + else if (!(esp_bt_gap_get_cod_srvc(cod) & ESP_BT_COD_SRVC_RENDERING)) + { + ESP_LOGV(TAG,"--Not a rendering device. Skipping.\n"); + return; + } + + if (eir) { + ESP_LOGV(TAG,"--Getting details from eir.\n"); + get_name_from_eir(eir, s_peer_bdname, NULL); + ESP_LOGV(TAG,"--Device name is %s\n",s_peer_bdname); + } + if(strlen((char *)s_peer_bdname)>0) { + peers_list_maintain((const char *)s_peer_bdname, rssi); + } + + if (squeezelite_conf.sink_name && strlen(squeezelite_conf.sink_name) >0 && strcmp((char *)s_peer_bdname, squeezelite_conf.sink_name) == 0) { + ESP_LOGI(TAG,"Found our target device. address %s, name %s", bda_str, s_peer_bdname); + memcpy(s_peer_bda, param->disc_res.bda, ESP_BD_ADDR_LEN); + set_app_source_state(APP_AV_STATE_DISCOVERED); + esp_bt_gap_cancel_discovery(); + } else { + ESP_LOGV(TAG,"Not the device we are looking for (%s). Continuing scan", squeezelite_conf.sink_name?squeezelite_conf.sink_name:"N/A"); + } +} + +static void bt_av_hdl_stack_evt(uint16_t event, void *p_param) +{ + switch (event) { + case BT_APP_EVT_STACK_UP: { + ESP_LOGI(TAG,"BT Stack going up."); + /* set up device name */ + + + char * a2dp_dev_name = config_alloc_get_default(NVS_TYPE_STR, "a2dp_dev_name", CONFIG_A2DP_DEV_NAME, 0); + if(a2dp_dev_name == NULL){ + ESP_LOGW(TAG,"Unable to retrieve the a2dp device name from nvs"); + esp_bt_dev_set_device_name(CONFIG_A2DP_DEV_NAME); + } + else { + esp_bt_dev_set_device_name(a2dp_dev_name); + free(a2dp_dev_name); + } + + ESP_LOGI(TAG,"Preparing to connect"); + + /* register GAP callback function */ + esp_bt_gap_register_callback(bt_app_gap_cb); + + /* initialize AVRCP controller */ + esp_avrc_ct_init(); + esp_avrc_ct_register_callback(bt_app_rc_ct_cb); + + esp_avrc_rn_evt_cap_mask_t evt_set = {0}; + esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_SET, &evt_set, ESP_AVRC_RN_VOLUME_CHANGE); + assert(esp_avrc_tg_set_rn_evt_cap(&evt_set) == ESP_OK); + + + /* initialize A2DP source */ + esp_a2d_register_callback(&bt_app_a2d_cb); + esp_a2d_source_register_data_callback(&output_bt_data); + esp_a2d_source_init(); + + /* set discoverable and connectable mode */ + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + + /* start device discovery */ + ESP_LOGI(TAG,"Starting device discovery..."); + set_app_source_state(APP_AV_STATE_DISCOVERING); + esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0); + + /* create and start heart beat timer */ + int tmr_id = 0; + s_tmr = xTimerCreate("connTmr", ( prev_duration/ portTICK_RATE_MS),pdFALSE, (void *)tmr_id, a2d_app_heart_beat); + xTimerStart(s_tmr, portMAX_DELAY); + break; + } + default: + ESP_LOGE(TAG,"%s unhandled evt %d", __func__, event); + break; + } +} +static void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param) +{ + switch (event) { + case ESP_AVRC_CT_METADATA_RSP_EVT: + case ESP_AVRC_CT_CONNECTION_STATE_EVT: + case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: + case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: + case ESP_AVRC_CT_REMOTE_FEATURES_EVT: + case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: + case ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT: { + ESP_LOGD(TAG,"Received %s message", ESP_AVRC_CT_DESC[event]); + bt_app_work_dispatch(bt_av_hdl_avrc_ct_evt, event, param, sizeof(esp_avrc_ct_cb_param_t), NULL); + break; + } + default: + ESP_LOGE(BT_RC_CT_TAG, "Invalid AVRC event: %d", event); + break; + } +} +static void bt_app_av_media_proc(uint16_t event, void *param) +{ + esp_a2d_cb_param_t *a2d = NULL; + switch (bt_app_source_media_state) { + case APP_AV_MEDIA_STATE_IDLE: { + if (event == BT_APP_HEART_BEAT_EVT) { + if(!output_stopped()) + { + ESP_LOGI(TAG,"Output state is %s, Checking if A2DP is ready.", output_state_str()); + esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_CHECK_SRC_RDY); + } + + } else if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) { + a2d = (esp_a2d_cb_param_t *)(param); + if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_CHECK_SRC_RDY && + a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS + ) { + ESP_LOGI(TAG,"a2dp media ready, starting playback!"); + set_a2dp_media_state(APP_AV_MEDIA_STATE_STARTING); + esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_START); + } + } + break; + } + + case APP_AV_MEDIA_STATE_STARTING: { + if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) { + a2d = (esp_a2d_cb_param_t *)(param); + if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_START && + a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) { + ESP_LOGI(TAG,"a2dp media started successfully."); + set_a2dp_media_state(APP_AV_MEDIA_STATE_STARTED); + } else { + // not started succesfully, transfer to idle state + ESP_LOGI(TAG,"a2dp media start failed."); + set_a2dp_media_state(APP_AV_MEDIA_STATE_IDLE); + } + } + break; + } + case APP_AV_MEDIA_STATE_STARTED: { + if (event == BT_APP_HEART_BEAT_EVT) { + if(output_stopped()) { + ESP_LOGI(TAG,"Output state is %s. Stopping a2dp media ...", output_state_str()); + set_a2dp_media_state(APP_AV_MEDIA_STATE_STOPPING); + esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_STOP); + } else { + output_bt_tick(); + } + } + break; + } + case APP_AV_MEDIA_STATE_STOPPING: { + ESP_LOG_DEBUG_EVENT(TAG,QUOTE(APP_AV_MEDIA_STATE_STOPPING)); + if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) { + a2d = (esp_a2d_cb_param_t *)(param); + if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_STOP && + a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) { + ESP_LOGI(TAG,"a2dp media stopped successfully..."); + set_a2dp_media_state(APP_AV_MEDIA_STATE_IDLE); + } else { + ESP_LOGI(TAG,"a2dp media stopping..."); + esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_STOP); + } + } + break; + } + + case APP_AV_MEDIA_STATE_WAIT_DISCONNECT:{ + esp_a2d_source_disconnect(s_peer_bda); + set_app_source_state(APP_AV_STATE_DISCONNECTING); + ESP_LOGI(TAG,"a2dp disconnecting..."); + } + } +} + +static void bt_app_av_state_unconnected(uint16_t event, void *param) +{ + ESP_LOGV(TAG, "Handling state unconnected A2DP event"); + switch (event) { + case ESP_A2D_CONNECTION_STATE_EVT: + handle_connect_state_unconnected(event, (esp_a2d_cb_param_t *)param); + break; + case ESP_A2D_AUDIO_STATE_EVT: + ESP_LOG_DEBUG_EVENT(TAG,QUOTE(ESP_A2D_AUDIO_STATE_EVT)); + break; + case ESP_A2D_AUDIO_CFG_EVT: + ESP_LOG_DEBUG_EVENT(TAG,QUOTE(ESP_A2D_AUDIO_CFG_EVT)); + break; + case ESP_A2D_MEDIA_CTRL_ACK_EVT: + ESP_LOG_DEBUG_EVENT(TAG,QUOTE(ESP_A2D_MEDIA_CTRL_ACK_EVT)); + break; + case BT_APP_HEART_BEAT_EVT: { + ESP_LOG_DEBUG_EVENT(TAG,QUOTE(BT_APP_HEART_BEAT_EVT)); + switch (esp_bluedroid_get_status()) { + case ESP_BLUEDROID_STATUS_UNINITIALIZED: + ESP_LOGV(TAG,"BlueDroid Status is ESP_BLUEDROID_STATUS_UNINITIALIZED."); + break; + case ESP_BLUEDROID_STATUS_INITIALIZED: + ESP_LOGV(TAG,"BlueDroid Status is ESP_BLUEDROID_STATUS_INITIALIZED."); + break; + case ESP_BLUEDROID_STATUS_ENABLED: + ESP_LOGV(TAG,"BlueDroid Status is ESP_BLUEDROID_STATUS_ENABLED."); + break; + default: + break; + } + uint8_t *p = s_peer_bda; + ESP_LOGI(TAG, "a2dp connecting to %s, BT peer: %02x:%02x:%02x:%02x:%02x:%02x",s_peer_bdname,p[0], p[1], p[2], p[3], p[4], p[5]); + if(esp_a2d_source_connect(s_peer_bda)==ESP_OK) { + set_app_source_state(APP_AV_STATE_CONNECTING); + s_connecting_intv = 0; + } + else { + set_app_source_state(APP_AV_STATE_UNCONNECTED); + // there was an issue connecting... continue to discover + ESP_LOGE(TAG,"Attempt at connecting failed, restart at discover..."); + esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0); + } + break; + } + default: + ESP_LOGE(TAG,"%s unhandled evt %d", __func__, event); + break; + } +} + +static void bt_app_av_state_connecting(uint16_t event, void *param) +{ + switch (event) { + case ESP_A2D_CONNECTION_STATE_EVT: + handle_connect_state_connecting(event, (esp_a2d_cb_param_t *)param); + break; + case ESP_A2D_AUDIO_STATE_EVT: + ESP_LOG_DEBUG_EVENT(TAG,QUOTE(ESP_A2D_AUDIO_STATE_EVT)); + break; + case ESP_A2D_AUDIO_CFG_EVT: + ESP_LOG_DEBUG_EVENT(TAG,QUOTE(ESP_A2D_AUDIO_CFG_EVT)); + break; + case ESP_A2D_MEDIA_CTRL_ACK_EVT: + ESP_LOG_DEBUG_EVENT(TAG,QUOTE(ESP_A2D_MEDIA_CTRL_ACK_EVT)); + break; + case BT_APP_HEART_BEAT_EVT: + if (++s_connecting_intv >= 2) { + set_app_source_state(APP_AV_STATE_UNCONNECTED); + ESP_LOGW(TAG,"A2DP Connect time out! Setting state to Unconnected. "); + s_connecting_intv = 0; + } + break; + default: + ESP_LOGE(TAG,"%s unhandled evt %d", __func__, event); + break; + } +} + + +static void bt_app_av_state_connected(uint16_t event, void *param) +{ + esp_a2d_cb_param_t *a2d = NULL; + switch (event) { + case ESP_A2D_CONNECTION_STATE_EVT: { + handle_connect_state_connected(event, (esp_a2d_cb_param_t *)param); + break; + } + case ESP_A2D_AUDIO_STATE_EVT: { + ESP_LOG_DEBUG_EVENT(TAG,QUOTE(ESP_A2D_AUDIO_STATE_EVT)); + a2d = (esp_a2d_cb_param_t *)(param); + if (ESP_A2D_AUDIO_STATE_STARTED == a2d->audio_stat.state) { + s_pkt_cnt = 0; + } + break; + } + case ESP_A2D_AUDIO_CFG_EVT: + // not suppposed to occur for A2DP source + ESP_LOG_DEBUG_EVENT(TAG,QUOTE(ESP_A2D_AUDIO_CFG_EVT)); + break; + case ESP_A2D_MEDIA_CTRL_ACK_EVT:{ + ESP_LOG_DEBUG_EVENT(TAG,QUOTE(ESP_A2D_MEDIA_CTRL_ACK_EVT)); + bt_app_av_media_proc(event, param); + break; + } + case BT_APP_HEART_BEAT_EVT: { + ESP_LOGV(TAG,QUOTE(BT_APP_HEART_BEAT_EVT)); + bt_app_av_media_proc(event, param); + break; + } + default: + ESP_LOGE(TAG,"%s unhandled evt %d", __func__, event); + break; + } +} + +static void bt_app_av_state_disconnecting(uint16_t event, void *param) +{ + switch (event) { + case ESP_A2D_CONNECTION_STATE_EVT: + handle_connect_state_disconnecting( event, (esp_a2d_cb_param_t *)param); + break; + case ESP_A2D_AUDIO_STATE_EVT: + ESP_LOG_DEBUG_EVENT(TAG,QUOTE(ESP_A2D_AUDIO_STATE_EVT)); + break; + case ESP_A2D_AUDIO_CFG_EVT: + ESP_LOG_DEBUG_EVENT(TAG,QUOTE(ESP_A2D_AUDIO_CFG_EVT)); + break; + case ESP_A2D_MEDIA_CTRL_ACK_EVT: + ESP_LOG_DEBUG_EVENT(TAG,QUOTE(ESP_A2D_MEDIA_CTRL_ACK_EVT)); + break; + case BT_APP_HEART_BEAT_EVT: + ESP_LOG_DEBUG_EVENT(TAG,QUOTE(BT_APP_HEART_BEAT_EVT)); + break; + default: + ESP_LOGE(TAG,"%s unhandled evt %d", __func__, event); + break; + } +} + +static void bt_av_volume_changed(void) +{ + if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, &s_avrc_peer_rn_cap, + ESP_AVRC_RN_VOLUME_CHANGE)) { + esp_avrc_ct_send_register_notification_cmd(APP_RC_CT_TL_RN_VOLUME_CHANGE, ESP_AVRC_RN_VOLUME_CHANGE, 0); + } +} + +static void bt_av_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *event_parameter) +{ + switch (event_id) { + case ESP_AVRC_RN_VOLUME_CHANGE: + ESP_LOGI(BT_RC_CT_TAG, "Volume changed: %d", event_parameter->volume); + ESP_LOGI(BT_RC_CT_TAG, "Set absolute volume: volume %d", event_parameter->volume + 5); + esp_avrc_ct_send_set_absolute_volume_cmd(APP_RC_CT_TL_RN_VOLUME_CHANGE, event_parameter->volume + 5); + bt_av_volume_changed(); + break; + } +} +static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param) +{ + ESP_LOGD(BT_RC_CT_TAG, "%s evt %d", __func__, event); + esp_avrc_ct_cb_param_t *rc = (esp_avrc_ct_cb_param_t *)(p_param); + switch (event) { + case ESP_AVRC_CT_CONNECTION_STATE_EVT: { + uint8_t *bda = rc->conn_stat.remote_bda; + ESP_LOGI(BT_RC_CT_TAG, "AVRC conn_state evt: state %d, [%02x:%02x:%02x:%02x:%02x:%02x]", + rc->conn_stat.connected, bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); + + if (rc->conn_stat.connected) { + // get remote supported event_ids of peer AVRCP Target + esp_avrc_ct_send_get_rn_capabilities_cmd(APP_RC_CT_TL_GET_CAPS); + } else { + // clear peer notification capability record + s_avrc_peer_rn_cap.bits = 0; + } + break; + } + case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "AVRC passthrough rsp: key_code 0x%x, key_state %d", rc->psth_rsp.key_code, rc->psth_rsp.key_state); + break; + } + case ESP_AVRC_CT_METADATA_RSP_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "AVRC metadata rsp: attribute id 0x%x, %s", rc->meta_rsp.attr_id, rc->meta_rsp.attr_text); + free(rc->meta_rsp.attr_text); + break; + } + case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "AVRC event notification: %d", rc->change_ntf.event_id); + bt_av_notify_evt_handler(rc->change_ntf.event_id, &rc->change_ntf.event_parameter); + break; + } + case ESP_AVRC_CT_REMOTE_FEATURES_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "AVRC remote features %x, TG features %x", rc->rmt_feats.feat_mask, rc->rmt_feats.tg_feat_flag); + break; + } + case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "remote rn_cap: count %d, bitmask 0x%x", rc->get_rn_caps_rsp.cap_count, + rc->get_rn_caps_rsp.evt_set.bits); + s_avrc_peer_rn_cap.bits = rc->get_rn_caps_rsp.evt_set.bits; + + bt_av_volume_changed(); + break; + } + case ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "Set absolute volume rsp: volume %d", rc->set_volume_rsp.volume); + break; + } + + default: + ESP_LOGE(BT_RC_CT_TAG, "%s unhandled evt %d", __func__, event); + break; + } +} diff --git a/components/heap/CMakeLists.txt b/components/heap/CMakeLists.txt deleted file mode 100644 index 0a77108f..00000000 --- a/components/heap/CMakeLists.txt +++ /dev/null @@ -1,49 +0,0 @@ -set(srcs - "heap_caps.c" - "heap_caps_init.c" - "multi_heap.c" - "heap_tlsf.c") - -if(NOT CONFIG_HEAP_POISONING_DISABLED) - list(APPEND srcs "multi_heap_poisoning.c") -endif() - -if(CONFIG_HEAP_TASK_TRACKING) - list(APPEND srcs "heap_task_info.c") -endif() - -if(CONFIG_HEAP_TRACING_STANDALONE) - list(APPEND srcs "heap_trace_standalone.c") - set_source_files_properties(heap_trace_standalone.c - PROPERTIES COMPILE_FLAGS - -Wno-frame-address) -endif() - -idf_component_register(SRCS "${srcs}" - INCLUDE_DIRS include - LDFRAGMENTS linker.lf - PRIV_REQUIRES soc) - -if(CONFIG_HEAP_TRACING) - set(WRAP_FUNCTIONS - calloc - malloc - free - realloc - heap_caps_malloc - heap_caps_free - heap_caps_realloc - heap_caps_malloc_default - heap_caps_realloc_default) - - foreach(wrap ${WRAP_FUNCTIONS}) - target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=${wrap}") - endforeach() -endif() - -if(NOT CMAKE_BUILD_EARLY_EXPANSION) - idf_build_get_property(build_components BUILD_COMPONENTS) - if(freertos IN_LIST build_components) - target_compile_options(${COMPONENT_TARGET} PRIVATE "-DMULTI_HEAP_FREERTOS") - endif() -endif() diff --git a/components/heap/Kconfig b/components/heap/Kconfig deleted file mode 100644 index 5b9dc561..00000000 --- a/components/heap/Kconfig +++ /dev/null @@ -1,74 +0,0 @@ -menu "Heap memory debugging" - - choice HEAP_CORRUPTION_DETECTION - prompt "Heap corruption detection" - default HEAP_POISONING_DISABLED - help - Enable heap poisoning features to detect heap corruption caused by out-of-bounds access to heap memory. - - See the "Heap Memory Debugging" page of the IDF documentation - for a description of each level of heap corruption detection. - - config HEAP_POISONING_DISABLED - bool "Basic (no poisoning)" - config HEAP_POISONING_LIGHT - bool "Light impact" - config HEAP_POISONING_COMPREHENSIVE - bool "Comprehensive" - endchoice - - choice HEAP_TRACING_DEST - bool "Heap tracing" - default HEAP_TRACING_OFF - help - Enables the heap tracing API defined in esp_heap_trace.h. - - This function causes a moderate increase in IRAM code side and a minor increase in heap function - (malloc/free/realloc) CPU overhead, even when the tracing feature is not used. - So it's best to keep it disabled unless tracing is being used. - - config HEAP_TRACING_OFF - bool "Disabled" - config HEAP_TRACING_STANDALONE - bool "Standalone" - select HEAP_TRACING - config HEAP_TRACING_TOHOST - bool "Host-based" - select HEAP_TRACING - endchoice - - config HEAP_TRACING - bool - default F - help - Enables/disables heap tracing API. - - config HEAP_TRACING_STACK_DEPTH - int "Heap tracing stack depth" - range 0 0 if IDF_TARGET_ARCH_RISCV # Disabled for RISC-V due to `__builtin_return_address` limitation - default 0 if IDF_TARGET_ARCH_RISCV - range 0 10 - default 2 - depends on HEAP_TRACING - help - Number of stack frames to save when tracing heap operation callers. - - More stack frames uses more memory in the heap trace buffer (and slows down allocation), but - can provide useful information. - - config HEAP_TASK_TRACKING - bool "Enable heap task tracking" - depends on !HEAP_POISONING_DISABLED - help - Enables tracking the task responsible for each heap allocation. - - This function depends on heap poisoning being enabled and adds four more bytes of overhead for each block - allocated. - - config HEAP_ABORT_WHEN_ALLOCATION_FAILS - bool "Abort if memory allocation fails" - default n - help - When enabled, if a memory allocation operation fails it will cause a system abort. - -endmenu diff --git a/components/heap/component.mk b/components/heap/component.mk deleted file mode 100644 index 5c8e7bcf..00000000 --- a/components/heap/component.mk +++ /dev/null @@ -1,32 +0,0 @@ -# -# Component Makefile -# - -COMPONENT_OBJS := heap_caps_init.o heap_caps.o multi_heap.o heap_tlsf.o - -ifndef CONFIG_HEAP_POISONING_DISABLED -COMPONENT_OBJS += multi_heap_poisoning.o - -ifdef CONFIG_HEAP_TASK_TRACKING -COMPONENT_OBJS += heap_task_info.o -endif -endif - -ifdef CONFIG_HEAP_TRACING_STANDALONE - -COMPONENT_OBJS += heap_trace_standalone.o - -endif - -ifdef CONFIG_HEAP_TRACING - -WRAP_FUNCTIONS = calloc malloc free realloc heap_caps_malloc heap_caps_free heap_caps_realloc heap_caps_malloc_default heap_caps_realloc_default -WRAP_ARGUMENT := -Wl,--wrap= - -COMPONENT_ADD_LDFLAGS = -l$(COMPONENT_NAME) $(addprefix $(WRAP_ARGUMENT),$(WRAP_FUNCTIONS)) - -endif - -COMPONENT_ADD_LDFRAGMENTS += linker.lf - -CFLAGS += -DMULTI_HEAP_FREERTOS diff --git a/components/heap/heap_caps.c b/components/heap/heap_caps.c deleted file mode 100644 index 07cc258e..00000000 --- a/components/heap/heap_caps.c +++ /dev/null @@ -1,609 +0,0 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -#include -#include -#include -#include -#include -#include "esp_attr.h" -#include "esp_heap_caps.h" -#include "multi_heap.h" -#include "esp_log.h" -#include "heap_private.h" -#include "esp_system.h" - -/* -This file, combined with a region allocator that supports multiple heaps, solves the problem that the ESP32 has RAM -that's slightly heterogeneous. Some RAM can be byte-accessed, some allows only 32-bit accesses, some can execute memory, -some can be remapped by the MMU to only be accessed by a certain PID etc. In order to allow the most flexible memory -allocation possible, this code makes it possible to request memory that has certain capabilities. The code will then use -its knowledge of how the memory is configured along with a priority scheme to allocate that memory in the most sane way -possible. This should optimize the amount of RAM accessible to the code without hardwiring addresses. -*/ - -static esp_alloc_failed_hook_t alloc_failed_callback; - -/* - This takes a memory chunk in a region that can be addressed as both DRAM as well as IRAM. It will convert it to - IRAM in such a way that it can be later freed. It assumes both the address as well as the length to be word-aligned. - It returns a region that's 1 word smaller than the region given because it stores the original Dram address there. -*/ -IRAM_ATTR static void *dram_alloc_to_iram_addr(void *addr, size_t len) -{ - uintptr_t dstart = (uintptr_t)addr; //First word - uintptr_t dend = dstart + len - 4; //Last word - assert(esp_ptr_in_diram_dram((void *)dstart)); - assert(esp_ptr_in_diram_dram((void *)dend)); - assert((dstart & 3) == 0); - assert((dend & 3) == 0); -#if SOC_DIRAM_INVERTED // We want the word before the result to hold the DRAM address - uint32_t *iptr = esp_ptr_diram_dram_to_iram((void *)dend); -#else - uint32_t *iptr = esp_ptr_diram_dram_to_iram((void *)dstart); -#endif - *iptr = dstart; - return iptr + 1; -} - - -static void heap_caps_alloc_failed(size_t requested_size, uint32_t caps, const char *function_name) -{ - if (alloc_failed_callback) { - alloc_failed_callback(requested_size, caps, function_name); - } - - #ifdef CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS - esp_system_abort("Memory allocation failed"); - #endif -} - -esp_err_t heap_caps_register_failed_alloc_callback(esp_alloc_failed_hook_t callback) -{ - if (callback == NULL) { - return ESP_ERR_INVALID_ARG; - } - - alloc_failed_callback = callback; - - return ESP_OK; -} - -bool heap_caps_match(const heap_t *heap, uint32_t caps) -{ - return heap->heap != NULL && ((get_all_caps(heap) & caps) == caps); -} - -/* -Routine to allocate a bit of memory with certain capabilities. caps is a bitfield of MALLOC_CAP_* bits. -*/ -IRAM_ATTR void *heap_caps_malloc( size_t size, uint32_t caps ) -{ - void *ret = NULL; - - if (size > HEAP_SIZE_MAX) { - // Avoids int overflow when adding small numbers to size, or - // calculating 'end' from start+size, by limiting 'size' to the possible range - heap_caps_alloc_failed(size, caps, __func__); - - return NULL; - } - - if (caps & MALLOC_CAP_EXEC) { - //MALLOC_CAP_EXEC forces an alloc from IRAM. There is a region which has both this as well as the following - //caps, but the following caps are not possible for IRAM. Thus, the combination is impossible and we return - //NULL directly, even although our heap capabilities (based on soc_memory_tags & soc_memory_regions) would - //indicate there is a tag for this. - if ((caps & MALLOC_CAP_8BIT) || (caps & MALLOC_CAP_DMA)) { - heap_caps_alloc_failed(size, caps, __func__); - - return NULL; - } - caps |= MALLOC_CAP_32BIT; // IRAM is 32-bit accessible RAM - } - - if (caps & MALLOC_CAP_32BIT) { - /* 32-bit accessible RAM should allocated in 4 byte aligned sizes - * (Future versions of ESP-IDF should possibly fail if an invalid size is requested) - */ - size = (size + 3) & (~3); // int overflow checked above - } - - for (int prio = 0; prio < SOC_MEMORY_TYPE_NO_PRIOS; prio++) { - //Iterate over heaps and check capabilities at this priority - heap_t *heap; - SLIST_FOREACH(heap, ®istered_heaps, next) { - if (heap->heap == NULL) { - continue; - } - if ((heap->caps[prio] & caps) != 0) { - //Heap has at least one of the caps requested. If caps has other bits set that this prio - //doesn't cover, see if they're available in other prios. - if ((get_all_caps(heap) & caps) == caps) { - //This heap can satisfy all the requested capabilities. See if we can grab some memory using it. - if ((caps & MALLOC_CAP_EXEC) && esp_ptr_in_diram_dram((void *)heap->start)) { - //This is special, insofar that what we're going to get back is a DRAM address. If so, - //we need to 'invert' it (lowest address in DRAM == highest address in IRAM and vice-versa) and - //add a pointer to the DRAM equivalent before the address we're going to return. - ret = multi_heap_malloc(heap->heap, size + 4); // int overflow checked above - - if (ret != NULL) { - return dram_alloc_to_iram_addr(ret, size + 4); // int overflow checked above - } - } else { - //Just try to alloc, nothing special. - ret = multi_heap_malloc(heap->heap, size); - if (ret != NULL) { - return ret; - } - } - } - } - } - } - - heap_caps_alloc_failed(size, caps, __func__); - - //Nothing usable found. - return NULL; -} - - -#define MALLOC_DISABLE_EXTERNAL_ALLOCS -1 -//Dual-use: -1 (=MALLOC_DISABLE_EXTERNAL_ALLOCS) disables allocations in external memory, >=0 sets the limit for allocations preferring internal memory. -static int malloc_alwaysinternal_limit=MALLOC_DISABLE_EXTERNAL_ALLOCS; - -void heap_caps_malloc_extmem_enable(size_t limit) -{ - malloc_alwaysinternal_limit=limit; -} - -/* - Default memory allocation implementation. Should return standard 8-bit memory. malloc() essentially resolves to this function. -*/ -IRAM_ATTR void *heap_caps_malloc_default( size_t size ) -{ - if (malloc_alwaysinternal_limit==MALLOC_DISABLE_EXTERNAL_ALLOCS) { - return heap_caps_malloc( size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL); - } else { - void *r; - if (size <= (size_t)malloc_alwaysinternal_limit) { - r=heap_caps_malloc( size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL ); - } else { - r=heap_caps_malloc( size, MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM ); - } - if (r==NULL) { - //try again while being less picky - r=heap_caps_malloc( size, MALLOC_CAP_DEFAULT ); - } - return r; - } -} - -/* - Same for realloc() - Note: keep the logic in here the same as in heap_caps_malloc_default (or merge the two as soon as this gets more complex...) - */ -IRAM_ATTR void *heap_caps_realloc_default( void *ptr, size_t size ) -{ - if (malloc_alwaysinternal_limit==MALLOC_DISABLE_EXTERNAL_ALLOCS) { - return heap_caps_realloc( ptr, size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL ); - } else { - void *r; - if (size <= (size_t)malloc_alwaysinternal_limit) { - r=heap_caps_realloc( ptr, size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL ); - } else { - r=heap_caps_realloc( ptr, size, MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM ); - } - if (r==NULL && size>0) { - //We needed to allocate memory, but we didn't. Try again while being less picky. - r=heap_caps_realloc( ptr, size, MALLOC_CAP_DEFAULT ); - } - return r; - } -} - -/* - Memory allocation as preference in decreasing order. - */ -IRAM_ATTR void *heap_caps_malloc_prefer( size_t size, size_t num, ... ) -{ - va_list argp; - va_start( argp, num ); - void *r = NULL; - while (num--) { - uint32_t caps = va_arg( argp, uint32_t ); - r = heap_caps_malloc( size, caps ); - if (r != NULL) { - break; - } - } - va_end( argp ); - return r; -} - -/* - Memory reallocation as preference in decreasing order. - */ -IRAM_ATTR void *heap_caps_realloc_prefer( void *ptr, size_t size, size_t num, ... ) -{ - va_list argp; - va_start( argp, num ); - void *r = NULL; - while (num--) { - uint32_t caps = va_arg( argp, uint32_t ); - r = heap_caps_realloc( ptr, size, caps ); - if (r != NULL || size == 0) { - break; - } - } - va_end( argp ); - return r; -} - -/* - Memory callocation as preference in decreasing order. - */ -IRAM_ATTR void *heap_caps_calloc_prefer( size_t n, size_t size, size_t num, ... ) -{ - va_list argp; - va_start( argp, num ); - void *r = NULL; - while (num--) { - uint32_t caps = va_arg( argp, uint32_t ); - r = heap_caps_calloc( n, size, caps ); - if (r != NULL) break; - } - va_end( argp ); - return r; -} - -/* Find the heap which belongs to ptr, or return NULL if it's - not in any heap. - - (This confirms if ptr is inside the heap's region, doesn't confirm if 'ptr' - is an allocated block or is some other random address inside the heap.) -*/ -IRAM_ATTR static heap_t *find_containing_heap(void *ptr ) -{ - intptr_t p = (intptr_t)ptr; - heap_t *heap; - SLIST_FOREACH(heap, ®istered_heaps, next) { - if (heap->heap != NULL && p >= heap->start && p < heap->end) { - return heap; - } - } - return NULL; -} - -IRAM_ATTR void heap_caps_free( void *ptr) -{ - if (ptr == NULL) { - return; - } - - if (esp_ptr_in_diram_iram(ptr)) { - //Memory allocated here is actually allocated in the DRAM alias region and - //cannot be de-allocated as usual. dram_alloc_to_iram_addr stores a pointer to - //the equivalent DRAM address, though; free that. - uint32_t *dramAddrPtr = (uint32_t *)ptr; - ptr = (void *)dramAddrPtr[-1]; - } - - heap_t *heap = find_containing_heap(ptr); - assert(heap != NULL && "free() target pointer is outside heap areas"); - multi_heap_free(heap->heap, ptr); -} - -IRAM_ATTR void *heap_caps_realloc( void *ptr, size_t size, uint32_t caps) -{ - bool ptr_in_diram_case = false; - heap_t *heap = NULL; - void *dram_ptr = NULL; - - if (ptr == NULL) { - return heap_caps_malloc(size, caps); - } - - if (size == 0) { - heap_caps_free(ptr); - return NULL; - } - - if (size > HEAP_SIZE_MAX) { - heap_caps_alloc_failed(size, caps, __func__); - - return NULL; - } - - //The pointer to memory may be aliased, we need to - //recover the corresponding address before to manage a new allocation: - if(esp_ptr_in_diram_iram((void *)ptr)) { - uint32_t *dram_addr = (uint32_t *)ptr; - dram_ptr = (void *)dram_addr[-1]; - - heap = find_containing_heap(dram_ptr); - assert(heap != NULL && "realloc() pointer is outside heap areas"); - - //with pointers that reside on diram space, we avoid using - //the realloc implementation due to address translation issues, - //instead force a malloc/copy/free - ptr_in_diram_case = true; - - } else { - heap = find_containing_heap(ptr); - assert(heap != NULL && "realloc() pointer is outside heap areas"); - } - - // are the existing heap's capabilities compatible with the - // requested ones? - bool compatible_caps = (caps & get_all_caps(heap)) == caps; - - if (compatible_caps && !ptr_in_diram_case) { - // try to reallocate this memory within the same heap - // (which will resize the block if it can) - void *r = multi_heap_realloc(heap->heap, ptr, size); - if (r != NULL) { - return r; - } - } - - // if we couldn't do that, try to see if we can reallocate - // in a different heap with requested capabilities. - void *new_p = heap_caps_malloc(size, caps); - if (new_p != NULL) { - size_t old_size = 0; - - //If we're dealing with aliased ptr, information regarding its containing - //heap can only be obtained with translated address. - if(ptr_in_diram_case) { - old_size = multi_heap_get_allocated_size(heap->heap, dram_ptr); - } else { - old_size = multi_heap_get_allocated_size(heap->heap, ptr); - } - - assert(old_size > 0); - memcpy(new_p, ptr, MIN(size, old_size)); - heap_caps_free(ptr); - return new_p; - } - - heap_caps_alloc_failed(size, caps, __func__); - - return NULL; -} - -IRAM_ATTR void *heap_caps_calloc( size_t n, size_t size, uint32_t caps) -{ - void *result; - size_t size_bytes; - - if (__builtin_mul_overflow(n, size, &size_bytes)) { - return NULL; - } - - result = heap_caps_malloc(size_bytes, caps); - if (result != NULL) { - bzero(result, size_bytes); - } - return result; -} - -size_t heap_caps_get_total_size(uint32_t caps) -{ - size_t total_size = 0; - heap_t *heap; - SLIST_FOREACH(heap, ®istered_heaps, next) { - if (heap_caps_match(heap, caps)) { - total_size += (heap->end - heap->start); - } - } - return total_size; -} - -size_t heap_caps_get_free_size( uint32_t caps ) -{ - size_t ret = 0; - heap_t *heap; - SLIST_FOREACH(heap, ®istered_heaps, next) { - if (heap_caps_match(heap, caps)) { - ret += multi_heap_free_size(heap->heap); - } - } - return ret; -} - -size_t heap_caps_get_minimum_free_size( uint32_t caps ) -{ - size_t ret = 0; - heap_t *heap; - SLIST_FOREACH(heap, ®istered_heaps, next) { - if (heap_caps_match(heap, caps)) { - ret += multi_heap_minimum_free_size(heap->heap); - } - } - return ret; -} - -size_t heap_caps_get_largest_free_block( uint32_t caps ) -{ - multi_heap_info_t info; - heap_caps_get_info(&info, caps); - return info.largest_free_block; -} - -void heap_caps_get_info( multi_heap_info_t *info, uint32_t caps ) -{ - bzero(info, sizeof(multi_heap_info_t)); - - heap_t *heap; - SLIST_FOREACH(heap, ®istered_heaps, next) { - if (heap_caps_match(heap, caps)) { - multi_heap_info_t hinfo; - multi_heap_get_info(heap->heap, &hinfo); - - info->total_free_bytes += hinfo.total_free_bytes; - info->total_allocated_bytes += hinfo.total_allocated_bytes; - info->largest_free_block = MAX(info->largest_free_block, - hinfo.largest_free_block); - info->minimum_free_bytes += hinfo.minimum_free_bytes; - info->allocated_blocks += hinfo.allocated_blocks; - info->free_blocks += hinfo.free_blocks; - info->total_blocks += hinfo.total_blocks; - } - } -} - -void heap_caps_print_heap_info( uint32_t caps ) -{ - multi_heap_info_t info; - printf("Heap summary for capabilities 0x%08X:\n", caps); - heap_t *heap; - SLIST_FOREACH(heap, ®istered_heaps, next) { - if (heap_caps_match(heap, caps)) { - multi_heap_get_info(heap->heap, &info); - - printf(" At 0x%08x len %d free %d allocated %d min_free %d\n", - heap->start, heap->end - heap->start, info.total_free_bytes, info.total_allocated_bytes, info.minimum_free_bytes); - printf(" largest_free_block %d alloc_blocks %d free_blocks %d total_blocks %d\n", - info.largest_free_block, info.allocated_blocks, - info.free_blocks, info.total_blocks); - } - } - printf(" Totals:\n"); - heap_caps_get_info(&info, caps); - - printf(" free %d allocated %d min_free %d largest_free_block %d\n", info.total_free_bytes, info.total_allocated_bytes, info.minimum_free_bytes, info.largest_free_block); -} - -bool heap_caps_check_integrity(uint32_t caps, bool print_errors) -{ - bool all_heaps = caps & MALLOC_CAP_INVALID; - bool valid = true; - - heap_t *heap; - SLIST_FOREACH(heap, ®istered_heaps, next) { - if (heap->heap != NULL - && (all_heaps || (get_all_caps(heap) & caps) == caps)) { - valid = multi_heap_check(heap->heap, print_errors) && valid; - } - } - - return valid; -} - -bool heap_caps_check_integrity_all(bool print_errors) -{ - return heap_caps_check_integrity(MALLOC_CAP_INVALID, print_errors); -} - -bool heap_caps_check_integrity_addr(intptr_t addr, bool print_errors) -{ - heap_t *heap = find_containing_heap((void *)addr); - if (heap == NULL) { - return false; - } - return multi_heap_check(heap->heap, print_errors); -} - -void heap_caps_dump(uint32_t caps) -{ - bool all_heaps = caps & MALLOC_CAP_INVALID; - heap_t *heap; - SLIST_FOREACH(heap, ®istered_heaps, next) { - if (heap->heap != NULL - && (all_heaps || (get_all_caps(heap) & caps) == caps)) { - multi_heap_dump(heap->heap); - } - } -} - -void heap_caps_dump_all(void) -{ - heap_caps_dump(MALLOC_CAP_INVALID); -} - -size_t heap_caps_get_allocated_size( void *ptr ) -{ - heap_t *heap = find_containing_heap(ptr); - size_t size = multi_heap_get_allocated_size(heap->heap, ptr); - return size; -} - -IRAM_ATTR void *heap_caps_aligned_alloc(size_t alignment, size_t size, uint32_t caps) -{ - void *ret = NULL; - - if(!alignment) { - return NULL; - } - - //Alignment must be a power of two: - if((alignment & (alignment - 1)) != 0) { - return NULL; - } - - if (size > HEAP_SIZE_MAX) { - // Avoids int overflow when adding small numbers to size, or - // calculating 'end' from start+size, by limiting 'size' to the possible range - heap_caps_alloc_failed(size, caps, __func__); - - return NULL; - } - - for (int prio = 0; prio < SOC_MEMORY_TYPE_NO_PRIOS; prio++) { - //Iterate over heaps and check capabilities at this priority - heap_t *heap; - SLIST_FOREACH(heap, ®istered_heaps, next) { - if (heap->heap == NULL) { - continue; - } - if ((heap->caps[prio] & caps) != 0) { - //Heap has at least one of the caps requested. If caps has other bits set that this prio - //doesn't cover, see if they're available in other prios. - if ((get_all_caps(heap) & caps) == caps) { - //Just try to alloc, nothing special. - ret = multi_heap_aligned_alloc(heap->heap, size, alignment); - if (ret != NULL) { - return ret; - } - } - } - } - } - - heap_caps_alloc_failed(size, caps, __func__); - - //Nothing usable found. - return NULL; -} - -IRAM_ATTR void heap_caps_aligned_free(void *ptr) -{ - heap_caps_free(ptr); -} - -void *heap_caps_aligned_calloc(size_t alignment, size_t n, size_t size, uint32_t caps) -{ - size_t size_bytes; - if (__builtin_mul_overflow(n, size, &size_bytes)) { - return NULL; - } - - void *ptr = heap_caps_aligned_alloc(alignment,size_bytes, caps); - if(ptr != NULL) { - memset(ptr, 0, size_bytes); - } - - return ptr; -} diff --git a/components/heap/heap_caps_init.c b/components/heap/heap_caps_init.c deleted file mode 100644 index 26c819de..00000000 --- a/components/heap/heap_caps_init.c +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -#include "heap_private.h" -#include -#include -#include - -#include "esp_log.h" -#include "multi_heap.h" -#include "multi_heap_platform.h" -#include "esp_heap_caps_init.h" -#include "soc/soc_memory_layout.h" - -static const char *TAG = "heap_init"; - -/* Linked-list of registered heaps */ -struct registered_heap_ll registered_heaps; - -static void register_heap(heap_t *region) -{ - size_t heap_size = region->end - region->start; - assert(heap_size <= HEAP_SIZE_MAX); - region->heap = multi_heap_register((void *)region->start, heap_size); - if (region->heap != NULL) { - ESP_EARLY_LOGD(TAG, "New heap initialised at %p", region->heap); - } -} - -void heap_caps_enable_nonos_stack_heaps(void) -{ - heap_t *heap; - SLIST_FOREACH(heap, ®istered_heaps, next) { - // Assume any not-yet-registered heap is - // a nonos-stack heap - if (heap->heap == NULL) { - register_heap(heap); - if (heap->heap != NULL) { - multi_heap_set_lock(heap->heap, &heap->heap_mux); - } - } - } -} - -/* Initialize the heap allocator to use all of the memory not - used by static data or reserved for other purposes - */ -void heap_caps_init(void) -{ - /* Get the array of regions that we can use for heaps - (with reserved memory removed already.) - */ - size_t num_regions = soc_get_available_memory_region_max_count(); - soc_memory_region_t regions[num_regions]; - num_regions = soc_get_available_memory_regions(regions); - - //The heap allocator will treat every region given to it as separate. In order to get bigger ranges of contiguous memory, - //it's useful to coalesce adjacent regions that have the same type. - for (size_t i = 1; i < num_regions; i++) { - soc_memory_region_t *a = ®ions[i - 1]; - soc_memory_region_t *b = ®ions[i]; - if (b->start == (intptr_t)(a->start + a->size) && b->type == a->type ) { - a->type = -1; - b->start = a->start; - b->size += a->size; - } - } - - /* Count the heaps left after merging */ - size_t num_heaps = 0; - for (size_t i = 0; i < num_regions; i++) { - if (regions[i].type != -1) { - num_heaps++; - } - } - - /* Start by allocating the registered heap data on the stack. - - Once we have a heap to copy it to, we will copy it to a heap buffer. - */ - heap_t temp_heaps[num_heaps]; - size_t heap_idx = 0; - - ESP_EARLY_LOGI(TAG, "Initializing. RAM available for dynamic allocation:"); - for (size_t i = 0; i < num_regions; i++) { - soc_memory_region_t *region = ®ions[i]; - const soc_memory_type_desc_t *type = &soc_memory_types[region->type]; - heap_t *heap = &temp_heaps[heap_idx]; - if (region->type == -1) { - continue; - } - heap_idx++; - assert(heap_idx <= num_heaps); - - memcpy(heap->caps, type->caps, sizeof(heap->caps)); - heap->start = region->start; - heap->end = region->start + region->size; - MULTI_HEAP_LOCK_INIT(&heap->heap_mux); - if (type->startup_stack) { - /* Will be registered when OS scheduler starts */ - heap->heap = NULL; - } else { - register_heap(heap); - } - SLIST_NEXT(heap, next) = NULL; - - ESP_EARLY_LOGI(TAG, "At %08X len %08X (%d KiB): %s", - region->start, region->size, region->size / 1024, type->name); - } - - assert(heap_idx == num_heaps); - - /* Allocate the permanent heap data that we'll use as a linked list at runtime. - - Allocate this part of data contiguously, even though it's a linked list... */ - assert(SLIST_EMPTY(®istered_heaps)); - - heap_t *heaps_array = NULL; - for (size_t i = 0; i < num_heaps; i++) { - if (heap_caps_match(&temp_heaps[i], MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL)) { - /* use the first DRAM heap which can fit the data */ - heaps_array = multi_heap_malloc(temp_heaps[i].heap, sizeof(heap_t) * num_heaps); - if (heaps_array != NULL) { - break; - } - } - } - assert(heaps_array != NULL); /* if NULL, there's not enough free startup heap space */ - - memcpy(heaps_array, temp_heaps, sizeof(heap_t)*num_heaps); - - /* Iterate the heaps and set their locks, also add them to the linked list. */ - for (size_t i = 0; i < num_heaps; i++) { - if (heaps_array[i].heap != NULL) { - multi_heap_set_lock(heaps_array[i].heap, &heaps_array[i].heap_mux); - } - if (i == 0) { - SLIST_INSERT_HEAD(®istered_heaps, &heaps_array[0], next); - } else { - SLIST_INSERT_AFTER(&heaps_array[i-1], &heaps_array[i], next); - } - } -} - -esp_err_t heap_caps_add_region(intptr_t start, intptr_t end) -{ - if (start == 0) { - return ESP_ERR_INVALID_ARG; - } - - for (size_t i = 0; i < soc_memory_region_count; i++) { - const soc_memory_region_t *region = &soc_memory_regions[i]; - // Test requested start only as 'end' may be in a different region entry, assume 'end' has same caps - if (region->start <= start && (intptr_t)(region->start + region->size) > start) { - const uint32_t *caps = soc_memory_types[region->type].caps; - return heap_caps_add_region_with_caps(caps, start, end); - } - } - - return ESP_ERR_NOT_FOUND; -} - -esp_err_t heap_caps_add_region_with_caps(const uint32_t caps[], intptr_t start, intptr_t end) -{ - esp_err_t err = ESP_FAIL; - if (caps == NULL || start == 0 || end == 0 || end <= start) { - return ESP_ERR_INVALID_ARG; - } - - //Check if region overlaps the start and/or end of an existing region. If so, the - //region is invalid (or maybe added twice) - /* - * assume that in on region, start must be less than end (cannot equal to) !! - * Specially, the 4th scenario can be allowed. For example, allocate memory from heap, - * then change the capability and call this function to create a new region for special - * application. - * In the following chart, 'start = start' and 'end = end' is contained in 3rd scenario. - * This all equal scenario is incorrect because the same region cannot be add twice. For example, - * add the .bss memory to region twice, if not do the check, it will cause exception. - * - * the existing heap region s(tart) e(nd) - * |----------------------| - * 1.add region [Correct] (s1start && end > heap->start) - || (start < heap->end && end > heap->end)) { - return ESP_FAIL; - } - } - - heap_t *p_new = heap_caps_malloc(sizeof(heap_t), MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT); - if (p_new == NULL) { - err = ESP_ERR_NO_MEM; - goto done; - } - memcpy(p_new->caps, caps, sizeof(p_new->caps)); - p_new->start = start; - p_new->end = end; - MULTI_HEAP_LOCK_INIT(&p_new->heap_mux); - p_new->heap = multi_heap_register((void *)start, end - start); - SLIST_NEXT(p_new, next) = NULL; - if (p_new->heap == NULL) { - err = ESP_ERR_INVALID_SIZE; - goto done; - } - multi_heap_set_lock(p_new->heap, &p_new->heap_mux); - - /* (This insertion is atomic to registered_heaps, so - we don't need to worry about thread safety for readers, - only for writers. */ - static multi_heap_lock_t registered_heaps_write_lock = MULTI_HEAP_LOCK_STATIC_INITIALIZER; - MULTI_HEAP_LOCK(®istered_heaps_write_lock); - SLIST_INSERT_HEAD(®istered_heaps, p_new, next); - MULTI_HEAP_UNLOCK(®istered_heaps_write_lock); - - err = ESP_OK; - - done: - if (err != ESP_OK) { - free(p_new); - } - return err; -} diff --git a/components/heap/heap_private.h b/components/heap/heap_private.h deleted file mode 100644 index 5e172c03..00000000 --- a/components/heap/heap_private.h +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -#pragma once - -#include -#include -#include -#include "multi_heap.h" -#include "multi_heap_platform.h" -#include "sys/queue.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/* Some common heap registration data structures used - for heap_caps_init.c to share heap information with heap_caps.c -*/ - -#define HEAP_SIZE_MAX (SOC_MAX_CONTIGUOUS_RAM_SIZE) - -/* Type for describing each registered heap */ -typedef struct heap_t_ { - uint32_t caps[SOC_MEMORY_TYPE_NO_PRIOS]; ///< Capabilities for the type of memory in this heap (as a prioritised set). Copied from soc_memory_types so it's in RAM not flash. - intptr_t start; - intptr_t end; - multi_heap_lock_t heap_mux; - multi_heap_handle_t heap; - SLIST_ENTRY(heap_t_) next; -} heap_t; - -/* All registered heaps. - - Forms a single linked list, even though most entries are contiguous. - This means at the expense of 4 bytes per heap, new heaps can be - added at runtime in a fast & thread-safe way. -*/ -extern SLIST_HEAD(registered_heap_ll, heap_t_) registered_heaps; - -bool heap_caps_match(const heap_t *heap, uint32_t caps); - -/* return all possible capabilities (across all priorities) for a given heap */ -inline static IRAM_ATTR uint32_t get_all_caps(const heap_t *heap) -{ - if (heap->heap == NULL) { - return 0; - } - uint32_t all_caps = 0; - for (int prio = 0; prio < SOC_MEMORY_TYPE_NO_PRIOS; prio++) { - all_caps |= heap->caps[prio]; - } - return all_caps; -} - -/* - Because we don't want to add _another_ known allocation method to the stack of functions to trace wrt memory tracing, - these are declared private. The newlib malloc()/realloc() implementation also calls these, so they are declared - separately in newlib/syscalls.c. -*/ -void *heap_caps_realloc_default(void *p, size_t size); -void *heap_caps_malloc_default(size_t size); - - -#ifdef __cplusplus -} -#endif diff --git a/components/heap/heap_task_info.c b/components/heap/heap_task_info.c deleted file mode 100644 index 5914e664..00000000 --- a/components/heap/heap_task_info.c +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright 2018 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include -#include -#include -#include "multi_heap_internal.h" -#include "heap_private.h" -#include "esp_heap_task_info.h" - -#ifdef CONFIG_HEAP_TASK_TRACKING - -/* - * Return per-task heap allocation totals and lists of blocks. - * - * For each task that has allocated memory from the heap, return totals for - * allocations within regions matching one or more sets of capabilities. - * - * Optionally also return an array of structs providing details about each - * block allocated by one or more requested tasks, or by all tasks. - * - * Returns the number of block detail structs returned. - */ -size_t heap_caps_get_per_task_info(heap_task_info_params_t *params) -{ - heap_t *reg; - heap_task_block_t *blocks = params->blocks; - size_t count = *params->num_totals; - size_t remaining = params->max_blocks; - - // Clear out totals for any prepopulated tasks. - if (params->totals) { - for (size_t i = 0; i < count; ++i) { - for (size_t type = 0; type < NUM_HEAP_TASK_CAPS; ++type) { - params->totals[i].size[type] = 0; - params->totals[i].count[type] = 0; - } - } - } - - SLIST_FOREACH(reg, ®istered_heaps, next) { - multi_heap_handle_t heap = reg->heap; - if (heap == NULL) { - continue; - } - - // Find if the capabilities of this heap region match on of the desired - // sets of capabilities. - uint32_t caps = get_all_caps(reg); - uint32_t type; - for (type = 0; type < NUM_HEAP_TASK_CAPS; ++type) { - if ((caps & params->mask[type]) == params->caps[type]) { - break; - } - } - if (type == NUM_HEAP_TASK_CAPS) { - continue; - } - - multi_heap_block_handle_t b = multi_heap_get_first_block(heap); - multi_heap_internal_lock(heap); - for ( ; b ; b = multi_heap_get_next_block(heap, b)) { - if (multi_heap_is_free(b)) { - continue; - } - void *p = multi_heap_get_block_address(b); // Safe, only arithmetic - size_t bsize = multi_heap_get_allocated_size(heap, p); // Validates - TaskHandle_t btask = (TaskHandle_t)multi_heap_get_block_owner(b); - - // Accumulate per-task allocation totals. - if (params->totals) { - size_t i; - for (i = 0; i < count; ++i) { - if (params->totals[i].task == btask) { - break; - } - } - if (i < count) { - params->totals[i].size[type] += bsize; - params->totals[i].count[type] += 1; - } - else { - if (count < params->max_totals) { - params->totals[count].task = btask; - params->totals[count].size[type] = bsize; - params->totals[i].count[type] = 1; - ++count; - } - } - } - - // Return details about allocated blocks for selected tasks. - if (blocks && remaining > 0) { - if (params->tasks) { - size_t i; - for (i = 0; i < params->num_tasks; ++i) { - if (btask == params->tasks[i]) { - break; - } - } - if (i == params->num_tasks) { - continue; - } - } - blocks->task = btask; - blocks->address = p; - blocks->size = bsize; - ++blocks; - --remaining; - } - } - multi_heap_internal_unlock(heap); - } - *params->num_totals = count; - return params->max_blocks - remaining; -} - -#endif // CONFIG_HEAP_TASK_TRACKING diff --git a/components/heap/heap_tlsf.c b/components/heap/heap_tlsf.c deleted file mode 100644 index 42a1ad2e..00000000 --- a/components/heap/heap_tlsf.c +++ /dev/null @@ -1,1015 +0,0 @@ -/* -** Two Level Segregated Fit memory allocator, version 3.1. -** Written by Matthew Conte -** http://tlsf.baisoku.org -** -** Based on the original documentation by Miguel Masmano: -** http://www.gii.upv.es/tlsf/main/docs -** -** This implementation was written to the specification -** of the document, therefore no GPL restrictions apply. -** -** Copyright (c) 2006-2016, Matthew Conte -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** * Neither the name of the copyright holder nor the -** names of its contributors may be used to endorse or promote products -** derived from this software without specific prior written permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -** DISCLAIMED. IN NO EVENT SHALL MATTHEW CONTE BE LIABLE FOR ANY -** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -#include "multi_heap_config.h" -#include "multi_heap.h" -#include "multi_heap_internal.h" -#include "heap_tlsf_config.h" -#include "heap_tlsf.h" - -#include "esp_log.h" -/* -** Architecture-specific bit manipulation routines. -** -** TLSF achieves O(1) cost for malloc and free operations by limiting -** the search for a free block to a free list of guaranteed size -** adequate to fulfill the request, combined with efficient free list -** queries using bitmasks and architecture-specific bit-manipulation -** routines. -** -** Most modern processors provide instructions to count leading zeroes -** in a word, find the lowest and highest set bit, etc. These -** specific implementations will be used when available, falling back -** to a reasonably efficient generic implementation. -** -** NOTE: TLSF spec relies on ffs/fls returning value 0..31. -** ffs/fls return 1-32 by default, returning 0 for error. -*/ - -/* The TLSF control structure. */ -typedef struct control_t -{ - /* Empty lists point at this block to indicate they are free. */ - block_header_t block_null; - - /* Local parameter for the pool */ - unsigned int fl_index_count; - unsigned int fl_index_shift; - unsigned int fl_index_max; - unsigned int sl_index_count; - unsigned int sl_index_count_log2; - unsigned int small_block_size; - size_t size; - - /* Bitmaps for free lists. */ - unsigned int fl_bitmap; - unsigned int *sl_bitmap; - - /* Head of free lists. */ - block_header_t** blocks; -} control_t; - -static inline __attribute__((__always_inline__)) int tlsf_ffs(unsigned int word) -{ - const unsigned int reverse = word & (~word + 1); - const int bit = 32 - __builtin_clz(reverse); - return bit - 1; -} - -static inline __attribute__((__always_inline__)) int tlsf_fls(unsigned int word) -{ - const int bit = word ? 32 - __builtin_clz(word) : 0; - return bit - 1; -} - -/* -** Set assert macro, if it has not been provided by the user. -*/ -#if !defined (tlsf_assert) -#define tlsf_assert assert -#endif - -/* -** Static assertion mechanism. -*/ -#define _tlsf_glue2(x, y) x ## y -#define _tlsf_glue(x, y) _tlsf_glue2(x, y) -#define tlsf_static_assert(exp) \ - typedef char _tlsf_glue(static_assert, __LINE__) [(exp) ? 1 : -1] - -/* This code has been tested on 32- and 64-bit (LP/LLP) architectures. */ -tlsf_static_assert(sizeof(int) * CHAR_BIT == 32); -tlsf_static_assert(sizeof(size_t) * CHAR_BIT >= 32); -tlsf_static_assert(sizeof(size_t) * CHAR_BIT <= 64); - -static inline __attribute__((__always_inline__)) size_t align_up(size_t x, size_t align) -{ - tlsf_assert(0 == (align & (align - 1)) && "must align to a power of two"); - return (x + (align - 1)) & ~(align - 1); -} - -static inline __attribute__((__always_inline__)) size_t align_down(size_t x, size_t align) -{ - tlsf_assert(0 == (align & (align - 1)) && "must align to a power of two"); - return x - (x & (align - 1)); -} - -static inline __attribute__((__always_inline__)) void* align_ptr(const void* ptr, size_t align) -{ - const tlsfptr_t aligned = - (tlsf_cast(tlsfptr_t, ptr) + (align - 1)) & ~(align - 1); - tlsf_assert(0 == (align & (align - 1)) && "must align to a power of two"); - return tlsf_cast(void*, aligned); -} - -/* -** Adjust an allocation size to be aligned to word size, and no smaller -** than internal minimum. -*/ -static inline __attribute__((__always_inline__)) size_t adjust_request_size(tlsf_t tlsf, size_t size, size_t align) -{ - size_t adjust = 0; - if (size) - { - const size_t aligned = align_up(size, align); - - /* aligned sized must not exceed block_size_max or we'll go out of bounds on sl_bitmap */ - if (aligned < tlsf_block_size_max(tlsf)) - { - adjust = tlsf_max(aligned, block_size_min); - } - } - return adjust; -} - -/* -** TLSF utility functions. In most cases, these are direct translations of -** the documentation found in the white paper. -*/ - -static inline __attribute__((__always_inline__)) void mapping_insert(control_t *control, size_t size, int* fli, int* sli) -{ - int fl, sl; - if (size < control->small_block_size) - { - /* Store small blocks in first list. */ - fl = 0; - sl = tlsf_cast(int, size) >> 2; - } - else - { - fl = tlsf_fls(size); - sl = tlsf_cast(int, size >> (fl - control->sl_index_count_log2)) ^ (1 << control->sl_index_count_log2); - fl -= (control->fl_index_shift - 1); - } - *fli = fl; - *sli = sl; -} - -/* This version rounds up to the next block size (for allocations) */ -static inline __attribute__((__always_inline__)) void mapping_search(control_t *control, size_t size, int* fli, int* sli) -{ - if (size >= control->small_block_size) - { - const size_t round = (1 << (tlsf_fls(size) - control->sl_index_count_log2)) - 1; - size += round; - } - mapping_insert(control, size, fli, sli); -} - -static inline __attribute__((__always_inline__)) block_header_t* search_suitable_block(control_t* control, int* fli, int* sli) -{ - int fl = *fli; - int sl = *sli; - - /* - ** First, search for a block in the list associated with the given - ** fl/sl index. - */ - unsigned int sl_map = control->sl_bitmap[fl] & (~0U << sl); - if (!sl_map) - { - /* No block exists. Search in the next largest first-level list. */ - const unsigned int fl_map = control->fl_bitmap & (~0U << (fl + 1)); - if (!fl_map) - { - /* No free blocks available, memory has been exhausted. */ - return 0; - } - - fl = tlsf_ffs(fl_map); - *fli = fl; - sl_map = control->sl_bitmap[fl]; - } - tlsf_assert(sl_map && "internal error - second level bitmap is null"); - sl = tlsf_ffs(sl_map); - *sli = sl; - - /* Return the first block in the free list. */ - return control->blocks[fl*control->sl_index_count + sl]; -} - -/* Remove a free block from the free list.*/ -static inline __attribute__((__always_inline__)) void remove_free_block(control_t* control, block_header_t* block, int fl, int sl) -{ - block_header_t* prev = block->prev_free; - block_header_t* next = block->next_free; - tlsf_assert(prev && "prev_free field can not be null"); - tlsf_assert(next && "next_free field can not be null"); - next->prev_free = prev; - prev->next_free = next; - - /* If this block is the head of the free list, set new head. */ - if (control->blocks[fl*control->sl_index_count + sl] == block) - { - control->blocks[fl*control->sl_index_count + sl] = next; - - /* If the new head is null, clear the bitmap. */ - if (next == &control->block_null) - { - control->sl_bitmap[fl] &= ~(1 << sl); - - /* If the second bitmap is now empty, clear the fl bitmap. */ - if (!control->sl_bitmap[fl]) - { - control->fl_bitmap &= ~(1 << fl); - } - } - } -} - -/* Insert a free block into the free block list. */ -static inline __attribute__((__always_inline__)) void insert_free_block(control_t* control, block_header_t* block, int fl, int sl) -{ - block_header_t* current = control->blocks[fl*control->sl_index_count + sl]; - tlsf_assert(current && "free list cannot have a null entry"); - tlsf_assert(block && "cannot insert a null entry into the free list"); - block->next_free = current; - block->prev_free = &control->block_null; - current->prev_free = block; - - tlsf_assert(block_to_ptr(block) == align_ptr(block_to_ptr(block), ALIGN_SIZE) - && "block not aligned properly"); - /* - ** Insert the new block at the head of the list, and mark the first- - ** and second-level bitmaps appropriately. - */ - control->blocks[fl*control->sl_index_count + sl] = block; - control->fl_bitmap |= (1 << fl); - control->sl_bitmap[fl] |= (1 << sl); -} - -/* Remove a given block from the free list. */ -static inline __attribute__((__always_inline__)) void block_remove(control_t* control, block_header_t* block) -{ - int fl, sl; - mapping_insert(control, block_size(block), &fl, &sl); - remove_free_block(control, block, fl, sl); -} - -/* Insert a given block into the free list. */ -static inline __attribute__((__always_inline__)) void block_insert(control_t* control, block_header_t* block) -{ - int fl, sl; - mapping_insert(control, block_size(block), &fl, &sl); - insert_free_block(control, block, fl, sl); -} - -static inline __attribute__((__always_inline__)) int block_can_split(block_header_t* block, size_t size) -{ - return block_size(block) >= sizeof(block_header_t) + size; -} - -/* Split a block into two, the second of which is free. */ -static inline __attribute__((__always_inline__)) block_header_t* block_split(block_header_t* block, size_t size) -{ - /* Calculate the amount of space left in the remaining block. - * REMINDER: remaining pointer's first field is `prev_phys_block` but this field is part of the - * previous physical block. */ - block_header_t* remaining = - offset_to_block(block_to_ptr(block), size - block_header_overhead); - - /* `size` passed as an argument is the first block's new size, thus, the remaining block's size - * is `block_size(block) - size`. However, the block's data must be precedeed by the data size. - * This field is NOT part of the size, so it has to be substracted from the calculation. */ - const size_t remain_size = block_size(block) - (size + block_header_overhead); - - tlsf_assert(block_to_ptr(remaining) == align_ptr(block_to_ptr(remaining), ALIGN_SIZE) - && "remaining block not aligned properly"); - - tlsf_assert(block_size(block) == remain_size + size + block_header_overhead); - block_set_size(remaining, remain_size); - tlsf_assert(block_size(remaining) >= block_size_min && "block split with invalid size"); - - block_set_size(block, size); - block_mark_as_free(remaining); - - /** - * Here is the final outcome of this function: - * - * block remaining (block_ptr + size - BHO) - * + + - * | | - * v v - * +----------------------------------------------------------------------+ - * |0000| |xxxxxxxxxxxxxxxxxxxxxx|xxxx| |###########################| - * |0000| |xxxxxxxxxxxxxxxxxxxxxx|xxxx| |###########################| - * |0000| |xxxxxxxxxxxxxxxxxxxxxx|xxxx| |###########################| - * |0000| |xxxxxxxxxxxxxxxxxxxxxx|xxxx| |###########################| - * +----------------------------------------------------------------------+ - * | | | | - * + +<------------------------->+ +<-------------------------> - * BHO `size` (argument) bytes BHO `remain_size` bytes - * - * Where BHO = block_header_overhead, - * 0: part of the memory owned by a `block`'s previous neighbour, - * x: part of the memory owned by `block`. - * #: part of the memory owned by `remaining`. - */ - - return remaining; -} - -/* Absorb a free block's storage into an adjacent previous free block. */ -static inline __attribute__((__always_inline__)) block_header_t* block_absorb(block_header_t* prev, block_header_t* block) -{ - tlsf_assert(!block_is_last(prev) && "previous block can't be last"); - /* Note: Leaves flags untouched. */ - prev->size += block_size(block) + block_header_overhead; - block_link_next(prev); - -#ifdef MULTI_HEAP_POISONING_SLOW - /* next_block header needs to be replaced with a fill pattern */ - multi_heap_internal_poison_fill_region(block, sizeof(block_header_t), true /* free */); -#endif - - return prev; -} - -/* Merge a just-freed block with an adjacent previous free block. */ -static inline __attribute__((__always_inline__)) block_header_t* block_merge_prev(control_t* control, block_header_t* block) -{ - if (block_is_prev_free(block)) - { - block_header_t* prev = block_prev(block); - tlsf_assert(prev && "prev physical block can't be null"); - tlsf_assert(block_is_free(prev) && "prev block is not free though marked as such"); - block_remove(control, prev); - block = block_absorb(prev, block); - } - - return block; -} - -/* Merge a just-freed block with an adjacent free block. */ -static inline __attribute__((__always_inline__)) block_header_t* block_merge_next(control_t* control, block_header_t* block) -{ - block_header_t* next = block_next(block); - tlsf_assert(next && "next physical block can't be null"); - - if (block_is_free(next)) - { - tlsf_assert(!block_is_last(block) && "previous block can't be last"); - block_remove(control, next); - block = block_absorb(block, next); - } - - return block; -} - -/* Trim any trailing block space off the end of a block, return to pool. */ -static inline __attribute__((__always_inline__)) void block_trim_free(control_t* control, block_header_t* block, size_t size) -{ - tlsf_assert(block_is_free(block) && "block must be free"); - if (block_can_split(block, size)) - { - block_header_t* remaining_block = block_split(block, size); - block_link_next(block); - block_set_prev_free(remaining_block); - block_insert(control, remaining_block); - } -} - -/* Trim any trailing block space off the end of a used block, return to pool. */ -static inline __attribute__((__always_inline__)) void block_trim_used(control_t* control, block_header_t* block, size_t size) -{ - tlsf_assert(!block_is_free(block) && "block must be used"); - if (block_can_split(block, size)) - { - /* If the next block is free, we must coalesce. */ - block_header_t* remaining_block = block_split(block, size); - block_set_prev_used(remaining_block); - - remaining_block = block_merge_next(control, remaining_block); - block_insert(control, remaining_block); - } -} - -static inline __attribute__((__always_inline__)) block_header_t* block_trim_free_leading(control_t* control, block_header_t* block, size_t size) -{ - block_header_t* remaining_block = block; - if (block_can_split(block, size)) - { - /* We want to split `block` in two: the first block will be freed and the - * second block will be returned. */ - remaining_block = block_split(block, size - block_header_overhead); - - /* `remaining_block` is the second block, mark its predecessor (first - * block) as free. */ - block_set_prev_free(remaining_block); - - block_link_next(block); - - /* Put back the first block into the free memory list. */ - block_insert(control, block); - } - - return remaining_block; -} - -static inline __attribute__((__always_inline__)) block_header_t* block_locate_free(control_t* control, size_t size) -{ - int fl = 0, sl = 0; - block_header_t* block = 0; - - if (size) - { - mapping_search(control, size, &fl, &sl); - - /* - ** mapping_search can futz with the size, so for excessively large sizes it can sometimes wind up - ** with indices that are off the end of the block array. - ** So, we protect against that here, since this is the only callsite of mapping_search. - ** Note that we don't need to check sl, since it comes from a modulo operation that guarantees it's always in range. - */ - if (fl < control->fl_index_count) - { - block = search_suitable_block(control, &fl, &sl); - } - } - - if (block) - { - tlsf_assert(block_size(block) >= size); - remove_free_block(control, block, fl, sl); - } - - return block; -} - -static inline __attribute__((__always_inline__)) void* block_prepare_used(control_t* control, block_header_t* block, size_t size) -{ - void* p = 0; - if (block) - { - tlsf_assert(size && "size must be non-zero"); - block_trim_free(control, block, size); - block_mark_as_used(block); - p = block_to_ptr(block); - } - return p; -} - -/* Clear structure and point all empty lists at the null block. */ -static void control_construct(control_t* control, size_t bytes) -{ - int i, j; - - control->block_null.next_free = &control->block_null; - control->block_null.prev_free = &control->block_null; - - /* find the closest ^2 for first layer */ - i = (bytes - 1) / (16 * 1024); - control->fl_index_max = FL_INDEX_MAX_MIN + sizeof(i) * 8 - __builtin_clz(i); - - /* adapt second layer to the pool */ - if (bytes <= 16 * 1024) control->sl_index_count_log2 = 3; - else if (bytes <= 256 * 1024) control->sl_index_count_log2 = 4; - else control->sl_index_count_log2 = 5; - - control->fl_index_shift = (control->sl_index_count_log2 + ALIGN_SIZE_LOG2); - control->sl_index_count = 1 << control->sl_index_count_log2; - control->fl_index_count = control->fl_index_max - control->fl_index_shift + 1; - control->small_block_size = 1 << control->fl_index_shift; - control->fl_bitmap = 0; - - control->sl_bitmap = align_ptr(control + 1, sizeof(*control->sl_bitmap)); - control->blocks = align_ptr(control->sl_bitmap + control->fl_index_count, sizeof(*control->blocks)); - control->size = (void*) (control->blocks + control->sl_index_count * control->fl_index_count) - (void*) control; - - ESP_EARLY_LOGW( "REMOVE", "NEW POOL of %d bytes, ctrl_size: %d sli_c:%d fli_c:%d small_b %d max_b:%d", - bytes, - control->size, control->sl_index_count, control->fl_index_count, - control->small_block_size, 1 << control->fl_index_max ); - - /* SL_INDEX_COUNT must be <= number of bits in sl_bitmap's storage type. */ - tlsf_assert(sizeof(unsigned int) * CHAR_BIT >= control->sl_index_count && "CHAR_BIT less than sl_index_count"); - - /* Ensure we've properly tuned our sizes. */ - tlsf_assert(ALIGN_SIZE == control->small_block_size / control->sl_index_count && "ALIGN_SIZE does not match"); - - for (i = 0; i < control->fl_index_count; ++i) - { - control->sl_bitmap[i] = 0; - for (j = 0; j < control->sl_index_count; ++j) - { - control->blocks[i*control->sl_index_count + j] = &control->block_null; - } - } -} - -/* -** Debugging utilities. -*/ - -typedef struct integrity_t -{ - int prev_status; - int status; -} integrity_t; - -#define tlsf_insist(x) { tlsf_assert(x); if (!(x)) { status--; } } - -static void integrity_walker(void* ptr, size_t size, int used, void* user) -{ - block_header_t* block = block_from_ptr(ptr); - integrity_t* integ = tlsf_cast(integrity_t*, user); - const int this_prev_status = block_is_prev_free(block) ? 1 : 0; - const int this_status = block_is_free(block) ? 1 : 0; - const size_t this_block_size = block_size(block); - - int status = 0; - (void)used; - tlsf_insist(integ->prev_status == this_prev_status && "prev status incorrect"); - tlsf_insist(size == this_block_size && "block size incorrect"); - - integ->prev_status = this_status; - integ->status += status; -} - -int tlsf_check(tlsf_t tlsf) -{ - int i, j; - - control_t* control = tlsf_cast(control_t*, tlsf); - int status = 0; - - /* Check that the free lists and bitmaps are accurate. */ - for (i = 0; i < control->fl_index_count; ++i) - { - for (j = 0; j < control->sl_index_count; ++j) - { - const int fl_map = control->fl_bitmap & (1 << i); - const int sl_list = control->sl_bitmap[i]; - const int sl_map = sl_list & (1 << j); - const block_header_t* block = control->blocks[i*control->sl_index_count + j]; - - /* Check that first- and second-level lists agree. */ - if (!fl_map) - { - tlsf_insist(!sl_map && "second-level map must be null"); - } - - if (!sl_map) - { - tlsf_insist(block == &control->block_null && "block list must be null"); - continue; - } - - /* Check that there is at least one free block. */ - tlsf_insist(sl_list && "no free blocks in second-level map"); - tlsf_insist(block != &control->block_null && "block should not be null"); - - while (block != &control->block_null) - { - int fli, sli; - tlsf_insist(block_is_free(block) && "block should be free"); - tlsf_insist(!block_is_prev_free(block) && "blocks should have coalesced"); - tlsf_insist(!block_is_free(block_next(block)) && "blocks should have coalesced"); - tlsf_insist(block_is_prev_free(block_next(block)) && "block should be free"); - tlsf_insist(block_size(block) >= block_size_min && "block not minimum size"); - - mapping_insert(control, block_size(block), &fli, &sli); - tlsf_insist(fli == i && sli == j && "block size indexed in wrong list"); - block = block->next_free; - } - } - } - - return status; -} - -#undef tlsf_insist - -static void default_walker(void* ptr, size_t size, int used, void* user) -{ - (void)user; - printf("\t%p %s size: %x (%p)\n", ptr, used ? "used" : "free", (unsigned int)size, block_from_ptr(ptr)); -} - -void tlsf_walk_pool(pool_t pool, tlsf_walker walker, void* user) -{ - tlsf_walker pool_walker = walker ? walker : default_walker; - block_header_t* block = - offset_to_block(pool, -(int)block_header_overhead); - - while (block && !block_is_last(block)) - { - pool_walker( - block_to_ptr(block), - block_size(block), - !block_is_free(block), - user); - block = block_next(block); - } -} - -size_t tlsf_block_size(void* ptr) -{ - size_t size = 0; - if (ptr) - { - const block_header_t* block = block_from_ptr(ptr); - size = block_size(block); - } - return size; -} - -int tlsf_check_pool(pool_t pool) -{ - /* Check that the blocks are physically correct. */ - integrity_t integ = { 0, 0 }; - tlsf_walk_pool(pool, integrity_walker, &integ); - - return integ.status; -} - -size_t tlsf_fit_size(tlsf_t tlsf, size_t size) -{ - /* because it's GoodFit, allocable size is one range lower */ - if (size) - { - control_t* control = tlsf_cast(control_t*, tlsf); - size_t sl_interval = (1 << ((sizeof(size_t) * 8 - 1) - __builtin_clz(size))) / control->sl_index_count; - return size & ~(sl_interval - 1); - } - - return 0; -} - - -/* -** Size of the TLSF structures in a given memory block passed to -** tlsf_create, equal to the size of a control_t -*/ -size_t tlsf_size(tlsf_t tlsf) -{ - if (tlsf) - { - control_t* control = tlsf_cast(control_t*, tlsf); - return control->size; - } - - /* no tlsf, we'll just return a min size */ - return sizeof(control_t) + - sizeof(int) * SL_INDEX_COUNT_MIN + - sizeof(block_header_t*) * SL_INDEX_COUNT_MIN * FL_INDEX_COUNT_MIN; -} - -size_t tlsf_align_size(void) -{ - return ALIGN_SIZE; -} - -size_t tlsf_block_size_min(void) -{ - return block_size_min; -} - -size_t tlsf_block_size_max(tlsf_t tlsf) -{ - control_t* control = tlsf_cast(control_t*, tlsf); - return tlsf_cast(size_t, 1) << control->fl_index_max; -} - -/* -** Overhead of the TLSF structures in a given memory block passed to -** tlsf_add_pool, equal to the overhead of a free block and the -** sentinel block. -*/ -size_t tlsf_pool_overhead(void) -{ - return 2 * block_header_overhead; -} - -size_t tlsf_alloc_overhead(void) -{ - return block_header_overhead; -} - -pool_t tlsf_add_pool(tlsf_t tlsf, void* mem, size_t bytes) -{ - block_header_t* block; - block_header_t* next; - - const size_t pool_overhead = tlsf_pool_overhead(); - const size_t pool_bytes = align_down(bytes - pool_overhead, ALIGN_SIZE); - - if (((ptrdiff_t)mem % ALIGN_SIZE) != 0) - { - printf("tlsf_add_pool: Memory must be aligned by %u bytes.\n", - (unsigned int)ALIGN_SIZE); - return 0; - } - - if (pool_bytes < block_size_min || pool_bytes > tlsf_block_size_max(tlsf)) - { -#if defined (TLSF_64BIT) - printf("tlsf_add_pool: Memory size must be between 0x%x and 0x%x00 bytes.\n", - (unsigned int)(pool_overhead + block_size_min), - (unsigned int)((pool_overhead + tlsf_block_size_max(tlsf)) / 256)); -#else - printf("tlsf_add_pool: Memory size must be between %u and %u bytes.\n", - (unsigned int)(pool_overhead + block_size_min), - (unsigned int)(pool_overhead + tlsf_block_size_max(tlsf))); -#endif - return 0; - } - - /* - ** Create the main free block. Offset the start of the block slightly - ** so that the prev_phys_block field falls outside of the pool - - ** it will never be used. - */ - block = offset_to_block(mem, -(tlsfptr_t)block_header_overhead); - block_set_size(block, pool_bytes); - block_set_free(block); - block_set_prev_used(block); - block_insert(tlsf_cast(control_t*, tlsf), block); - - /* Split the block to create a zero-size sentinel block. */ - next = block_link_next(block); - block_set_size(next, 0); - block_set_used(next); - block_set_prev_free(next); - - return mem; -} - -void tlsf_remove_pool(tlsf_t tlsf, pool_t pool) -{ - control_t* control = tlsf_cast(control_t*, tlsf); - block_header_t* block = offset_to_block(pool, -(int)block_header_overhead); - - int fl = 0, sl = 0; - - tlsf_assert(block_is_free(block) && "block should be free"); - tlsf_assert(!block_is_free(block_next(block)) && "next block should not be free"); - tlsf_assert(block_size(block_next(block)) == 0 && "next block size should be zero"); - - mapping_insert(control, block_size(block), &fl, &sl); - remove_free_block(control, block, fl, sl); -} - -/* -** TLSF main interface. -*/ - - -tlsf_t tlsf_create(void* mem, size_t max_bytes) -{ -#if _DEBUG - if (test_ffs_fls()) - { - return 0; - } -#endif - - if (((tlsfptr_t)mem % ALIGN_SIZE) != 0) - { - printf("tlsf_create: Memory must be aligned to %u bytes.\n", - (unsigned int)ALIGN_SIZE); - return 0; - } - - control_construct(tlsf_cast(control_t*, mem), max_bytes); - - return tlsf_cast(tlsf_t, mem); -} - -pool_t tlsf_get_pool(tlsf_t tlsf) -{ - return tlsf_cast(pool_t, (char*)tlsf + tlsf_size(tlsf)); -} - -tlsf_t tlsf_create_with_pool(void* mem, size_t pool_bytes, size_t max_bytes) -{ - tlsf_t tlsf = tlsf_create(mem, max_bytes ? max_bytes : pool_bytes); - tlsf_add_pool(tlsf, (char*)mem + tlsf_size(tlsf), pool_bytes - tlsf_size(tlsf)); - return tlsf; -} - -void* tlsf_malloc(tlsf_t tlsf, size_t size) -{ - control_t* control = tlsf_cast(control_t*, tlsf); - size_t adjust = adjust_request_size(tlsf, size, ALIGN_SIZE); - block_header_t* block = block_locate_free(control, adjust); - return block_prepare_used(control, block, adjust); -} - -/** - * @brief Allocate memory of at least `size` bytes where byte at `data_offset` will be aligned to `alignment`. - * - * This function will allocate memory pointed by `ptr`. However, the byte at `data_offset` of - * this piece of memory (i.e., byte at `ptr` + `data_offset`) will be aligned to `alignment`. - * This function is useful for allocating memory that will internally have a header, and the - * usable memory following the header (i.e. `ptr` + `data_offset`) must be aligned. - * - * For example, a call to `multi_heap_aligned_alloc_impl_offs(heap, 64, 256, 20)` will return a - * pointer `ptr` to free memory of minimum 64 bytes, where `ptr + 20` is aligned on `256`. - * So `(ptr + 20) % 256` equals 0. - * - * @param tlsf TLSF structure to allocate memory from. - * @param align Alignment for the returned pointer's offset. - * @param size Minimum size, in bytes, of the memory to allocate INCLUDING - * `data_offset` bytes. - * @param data_offset Offset to be aligned on `alignment`. This can be 0, in - * this case, the returned pointer will be aligned on - * `alignment`. If it is not a multiple of CPU word size, - * it will be aligned up to the closest multiple of it. - * - * @return pointer to free memory. - */ -void* tlsf_memalign_offs(tlsf_t tlsf, size_t align, size_t size, size_t data_offset) -{ - control_t* control = tlsf_cast(control_t*, tlsf); - const size_t adjust = adjust_request_size(tlsf, size, ALIGN_SIZE); - const size_t off_adjust = align_up(data_offset, ALIGN_SIZE); - - /* - ** We must allocate an additional minimum block size bytes so that if - ** our free block will leave an alignment gap which is smaller, we can - ** trim a leading free block and release it back to the pool. We must - ** do this because the previous physical block is in use, therefore - ** the prev_phys_block field is not valid, and we can't simply adjust - ** the size of that block. - */ - const size_t gap_minimum = sizeof(block_header_t) + off_adjust; - /* The offset is included in both `adjust` and `gap_minimum`, so we - ** need to subtract it once. - */ - const size_t size_with_gap = adjust_request_size(tlsf, adjust + align + gap_minimum - off_adjust, align); - - /* - ** If alignment is less than or equals base alignment, we're done. - ** If we requested 0 bytes, return null, as tlsf_malloc(0) does. - */ - const size_t aligned_size = (adjust && align > ALIGN_SIZE) ? size_with_gap : adjust; - - block_header_t* block = block_locate_free(control, aligned_size); - - /* This can't be a static assert. */ - tlsf_assert(sizeof(block_header_t) == block_size_min + block_header_overhead); - - if (block) - { - void* ptr = block_to_ptr(block); - void* aligned = align_ptr(ptr, align); - size_t gap = tlsf_cast(size_t, - tlsf_cast(tlsfptr_t, aligned) - tlsf_cast(tlsfptr_t, ptr)); - - /* - ** If gap size is too small or if there is not gap but we need one, - ** offset to next aligned boundary. - */ - if ((gap && gap < gap_minimum) || (!gap && off_adjust)) - { - const size_t gap_remain = gap_minimum - gap; - const size_t offset = tlsf_max(gap_remain, align); - const void* next_aligned = tlsf_cast(void*, - tlsf_cast(tlsfptr_t, aligned) + offset); - - aligned = align_ptr(next_aligned, align); - gap = tlsf_cast(size_t, - tlsf_cast(tlsfptr_t, aligned) - tlsf_cast(tlsfptr_t, ptr)); - } - - if (gap) - { - tlsf_assert(gap >= gap_minimum && "gap size too small"); - block = block_trim_free_leading(control, block, gap - off_adjust); - } - } - - /* Preparing the block will also the trailing free memory. */ - return block_prepare_used(control, block, adjust); -} - -/** - * @brief Same as `tlsf_memalign_offs` function but with a 0 offset. - * The pointer returned is aligned on `align`. - */ -void* tlsf_memalign(tlsf_t tlsf, size_t align, size_t size) -{ - return tlsf_memalign_offs(tlsf, align, size, 0); -} - - -void tlsf_free(tlsf_t tlsf, void* ptr) -{ - /* Don't attempt to free a NULL pointer. */ - if (ptr) - { - control_t* control = tlsf_cast(control_t*, tlsf); - block_header_t* block = block_from_ptr(ptr); - tlsf_assert(!block_is_free(block) && "block already marked as free"); - block_mark_as_free(block); - block = block_merge_prev(control, block); - block = block_merge_next(control, block); - block_insert(control, block); - } -} - -/* -** The TLSF block information provides us with enough information to -** provide a reasonably intelligent implementation of realloc, growing or -** shrinking the currently allocated block as required. -** -** This routine handles the somewhat esoteric edge cases of realloc: -** - a non-zero size with a null pointer will behave like malloc -** - a zero size with a non-null pointer will behave like free -** - a request that cannot be satisfied will leave the original buffer -** untouched -** - an extended buffer size will leave the newly-allocated area with -** contents undefined -*/ -void* tlsf_realloc(tlsf_t tlsf, void* ptr, size_t size) -{ - control_t* control = tlsf_cast(control_t*, tlsf); - void* p = 0; - - /* Zero-size requests are treated as free. */ - if (ptr && size == 0) - { - tlsf_free(tlsf, ptr); - } - /* Requests with NULL pointers are treated as malloc. */ - else if (!ptr) - { - p = tlsf_malloc(tlsf, size); - } - else - { - block_header_t* block = block_from_ptr(ptr); - block_header_t* next = block_next(block); - - const size_t cursize = block_size(block); - const size_t combined = cursize + block_size(next) + block_header_overhead; - const size_t adjust = adjust_request_size(tlsf, size, ALIGN_SIZE); - - tlsf_assert(!block_is_free(block) && "block already marked as free"); - - /* - ** If the next block is used, or when combined with the current - ** block, does not offer enough space, we must reallocate and copy. - */ - if (adjust > cursize && (!block_is_free(next) || adjust > combined)) - { - p = tlsf_malloc(tlsf, size); - if (p) - { - const size_t minsize = tlsf_min(cursize, size); - memcpy(p, ptr, minsize); - tlsf_free(tlsf, ptr); - } - } - else - { - /* Do we need to expand to the next block? */ - if (adjust > cursize) - { - block_merge_next(control, block); - block_mark_as_used(block); - } - - /* Trim the resulting block and return the original pointer. */ - block_trim_used(control, block, adjust); - p = ptr; - } - } - - return p; -} diff --git a/components/heap/heap_tlsf.h b/components/heap/heap_tlsf.h deleted file mode 100644 index b2f94a62..00000000 --- a/components/heap/heap_tlsf.h +++ /dev/null @@ -1,119 +0,0 @@ -/* -** Two Level Segregated Fit memory allocator, version 3.1. -** Written by Matthew Conte -** http://tlsf.baisoku.org -** -** Based on the original documentation by Miguel Masmano: -** http://www.gii.upv.es/tlsf/main/docs -** -** This implementation was written to the specification -** of the document, therefore no GPL restrictions apply. -** -** Copyright (c) 2006-2016, Matthew Conte -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** * Neither the name of the copyright holder nor the -** names of its contributors may be used to endorse or promote products -** derived from this software without specific prior written permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -** DISCLAIMED. IN NO EVENT SHALL MATTHEW CONTE BE LIABLE FOR ANY -** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include "heap_tlsf_config.h" - -#if defined(__cplusplus) -extern "C" { -#endif - -/* -** Cast and min/max macros. -*/ -#define tlsf_cast(t, exp) ((t) (exp)) -#define tlsf_min(a, b) ((a) < (b) ? (a) : (b)) -#define tlsf_max(a, b) ((a) > (b) ? (a) : (b)) - -/* A type used for casting when doing pointer arithmetic. */ -typedef ptrdiff_t tlsfptr_t; - -typedef struct block_header_t -{ - /* Points to the previous physical block. */ - struct block_header_t* prev_phys_block; - - /* The size of this block, excluding the block header. */ - size_t size; - - /* Next and previous free blocks. */ - struct block_header_t* next_free; - struct block_header_t* prev_free; -} block_header_t; - -#include "heap_tlsf_block_functions.h" - -/* tlsf_t: a TLSF structure. Can contain 1 to N pools. */ -/* pool_t: a block of memory that TLSF can manage. */ -typedef void* tlsf_t; -typedef void* pool_t; - -/* Create/destroy a memory pool. */ -tlsf_t tlsf_create(void* mem, size_t max_bytes); -tlsf_t tlsf_create_with_pool(void* mem, size_t pool_bytes, size_t max_bytes); -pool_t tlsf_get_pool(tlsf_t tlsf); - -/* Add/remove memory pools. */ -pool_t tlsf_add_pool(tlsf_t tlsf, void* mem, size_t bytes); -void tlsf_remove_pool(tlsf_t tlsf, pool_t pool); - -/* malloc/memalign/realloc/free replacements. */ -void* tlsf_malloc(tlsf_t tlsf, size_t size); -void* tlsf_memalign(tlsf_t tlsf, size_t align, size_t size); -void* tlsf_memalign_offs(tlsf_t tlsf, size_t align, size_t size, size_t offset); -void* tlsf_realloc(tlsf_t tlsf, void* ptr, size_t size); -void tlsf_free(tlsf_t tlsf, void* ptr); - -/* Returns internal block size, not original request size */ -size_t tlsf_block_size(void* ptr); - -/* Overheads/limits of internal structures. */ -size_t tlsf_size(tlsf_t tlsf); -size_t tlsf_align_size(void); -size_t tlsf_block_size_min(void); -size_t tlsf_block_size_max(tlsf_t tlsf); -size_t tlsf_pool_overhead(void); -size_t tlsf_alloc_overhead(void); -size_t tlsf_fit_size(tlsf_t tlsf, size_t size); - -/* Debugging. */ -typedef void (*tlsf_walker)(void* ptr, size_t size, int used, void* user); -void tlsf_walk_pool(pool_t pool, tlsf_walker walker, void* user); -/* Returns nonzero if any internal consistency check fails. */ -int tlsf_check(tlsf_t tlsf); -int tlsf_check_pool(pool_t pool); - -#if defined(__cplusplus) -}; -#endif diff --git a/components/heap/heap_tlsf_block_functions.h b/components/heap/heap_tlsf_block_functions.h deleted file mode 100644 index efc92c9c..00000000 --- a/components/heap/heap_tlsf_block_functions.h +++ /dev/null @@ -1,174 +0,0 @@ -/* -** Two Level Segregated Fit memory allocator, version 3.1. -** Written by Matthew Conte -** http://tlsf.baisoku.org -** -** Based on the original documentation by Miguel Masmano: -** http://www.gii.upv.es/tlsf/main/docs -** -** This implementation was written to the specification -** of the document, therefore no GPL restrictions apply. -** -** Copyright (c) 2006-2016, Matthew Conte -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** * Neither the name of the copyright holder nor the -** names of its contributors may be used to endorse or promote products -** derived from this software without specific prior written permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -** DISCLAIMED. IN NO EVENT SHALL MATTHEW CONTE BE LIABLE FOR ANY -** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -/* -** Data structures and associated constants. -*/ - -/* -** Since block sizes are always at least a multiple of 4, the two least -** significant bits of the size field are used to store the block status: -** - bit 0: whether block is busy or free -** - bit 1: whether previous block is busy or free -*/ -#define block_header_free_bit (1 << 0) -#define block_header_prev_free_bit (1 << 1) - -/* -** The size of the block header exposed to used blocks is the size field. -** The prev_phys_block field is stored *inside* the previous free block. -*/ -#define block_header_overhead (sizeof(size_t)) - -/* User data starts directly after the size field in a used block. */ -#define block_start_offset (offsetof(block_header_t, size) + sizeof(size_t)) - -/* -** A free block must be large enough to store its header minus the size of -** the prev_phys_block field, and no larger than the number of addressable -** bits for FL_INDEX. -** The block_size_max macro returns the maximum block for the minimum pool -** use tlsf_block_size_max for a value specific to the pool -*/ -#define block_size_min (sizeof(block_header_t) - sizeof(block_header_t*)) -#define block_size_max (tlsf_cast(size_t, 1) << FL_INDEX_MAX_MIN) - -/* -** block_header_t member functions. -*/ -static inline __attribute__((__always_inline__)) size_t block_size(const block_header_t* block) -{ - return block->size & ~(block_header_free_bit | block_header_prev_free_bit); -} - -static inline __attribute__((__always_inline__)) void block_set_size(block_header_t* block, size_t size) -{ - const size_t oldsize = block->size; - block->size = size | (oldsize & (block_header_free_bit | block_header_prev_free_bit)); -} - -static inline __attribute__((__always_inline__)) int block_is_last(const block_header_t* block) -{ - return block_size(block) == 0; -} - -static inline __attribute__((__always_inline__)) int block_is_free(const block_header_t* block) -{ - return tlsf_cast(int, block->size & block_header_free_bit); -} - -static inline __attribute__((__always_inline__)) void block_set_free(block_header_t* block) -{ - block->size |= block_header_free_bit; -} - -static inline __attribute__((__always_inline__)) void block_set_used(block_header_t* block) -{ - block->size &= ~block_header_free_bit; -} - -static inline __attribute__((__always_inline__)) int block_is_prev_free(const block_header_t* block) -{ - return tlsf_cast(int, block->size & block_header_prev_free_bit); -} - -static inline __attribute__((__always_inline__)) void block_set_prev_free(block_header_t* block) -{ - block->size |= block_header_prev_free_bit; -} - -static inline __attribute__((__always_inline__)) void block_set_prev_used(block_header_t* block) -{ - block->size &= ~block_header_prev_free_bit; -} - -static inline __attribute__((__always_inline__)) block_header_t* block_from_ptr(const void* ptr) -{ - return tlsf_cast(block_header_t*, - tlsf_cast(unsigned char*, ptr) - block_start_offset); -} - -static inline __attribute__((__always_inline__)) void* block_to_ptr(const block_header_t* block) -{ - return tlsf_cast(void*, - tlsf_cast(unsigned char*, block) + block_start_offset); -} - -/* Return location of next block after block of given size. */ -static inline __attribute__((__always_inline__)) block_header_t* offset_to_block(const void* ptr, size_t size) -{ - return tlsf_cast(block_header_t*, tlsf_cast(tlsfptr_t, ptr) + size); -} - -/* Return location of previous block. */ -static inline __attribute__((__always_inline__)) block_header_t* block_prev(const block_header_t* block) -{ - return block->prev_phys_block; -} - -/* Return location of next existing block. */ -static inline __attribute__((__always_inline__)) block_header_t* block_next(const block_header_t* block) -{ - block_header_t* next = offset_to_block(block_to_ptr(block), - block_size(block) - block_header_overhead); - return next; -} - -/* Link a new block with its physical neighbor, return the neighbor. */ -static inline __attribute__((__always_inline__)) block_header_t* block_link_next(block_header_t* block) -{ - block_header_t* next = block_next(block); - next->prev_phys_block = block; - return next; -} - -static inline __attribute__((__always_inline__)) void block_mark_as_free(block_header_t* block) -{ - /* Link the block to the next block, first. */ - block_header_t* next = block_link_next(block); - block_set_prev_free(next); - block_set_free(block); -} - -static inline __attribute__((__always_inline__)) void block_mark_as_used(block_header_t* block) -{ - block_header_t* next = block_next(block); - block_set_prev_used(next); - block_set_used(block); -} diff --git a/components/heap/heap_tlsf_config.h b/components/heap/heap_tlsf_config.h deleted file mode 100644 index a3899e7e..00000000 --- a/components/heap/heap_tlsf_config.h +++ /dev/null @@ -1,66 +0,0 @@ -/* -** Two Level Segregated Fit memory allocator, version 3.1. -** Written by Matthew Conte -** http://tlsf.baisoku.org -** -** Based on the original documentation by Miguel Masmano: -** http://www.gii.upv.es/tlsf/main/docs -** -** This implementation was written to the specification -** of the document, therefore no GPL restrictions apply. -** -** Copyright (c) 2006-2016, Matthew Conte -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** * Neither the name of the copyright holder nor the -** names of its contributors may be used to endorse or promote products -** derived from this software without specific prior written permission. -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -** DISCLAIMED. IN NO EVENT SHALL MATTHEW CONTE BE LIABLE FOR ANY -** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -enum tlsf_config -{ - /* log2 of number of linear subdivisions of block sizes. Larger - ** values require more memory in the control structure. Values of - ** 4 or 5 are typical, 3 is for very small pools. - */ - SL_INDEX_COUNT_LOG2_MIN = 3, - - /* All allocation sizes and addresses are aligned to 4 bytes. */ - ALIGN_SIZE_LOG2 = 2, - ALIGN_SIZE = (1 << ALIGN_SIZE_LOG2), - - /* - ** We support allocations of sizes up to (1 << FL_INDEX_MAX) bits. - ** However, because we linearly subdivide the second-level lists, and - ** our minimum size granularity is 4 bytes, it doesn't make sense to - ** create first-level lists for sizes smaller than SL_INDEX_COUNT * 4, - ** or (1 << (SL_INDEX_COUNT_LOG2 + 2)) bytes, as there we will be - ** trying to split size ranges into more slots than we have available. - ** Instead, we calculate the minimum threshold size, and place all - ** blocks below that size into the 0th first-level list. - ** Values below are the absolute minimum to accept a pool addition - */ - FL_INDEX_MAX_MIN = 14, // For a less than 16kB pool - SL_INDEX_COUNT_MIN = (1 << SL_INDEX_COUNT_LOG2_MIN), - FL_INDEX_COUNT_MIN = (FL_INDEX_MAX_MIN - (SL_INDEX_COUNT_LOG2_MIN + ALIGN_SIZE_LOG2) + 1), -}; diff --git a/components/heap/heap_trace_standalone.c b/components/heap/heap_trace_standalone.c deleted file mode 100644 index 138a8cd3..00000000 --- a/components/heap/heap_trace_standalone.c +++ /dev/null @@ -1,255 +0,0 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -#include -#include - -#define HEAP_TRACE_SRCFILE /* don't warn on inclusion here */ -#include "esp_heap_trace.h" -#undef HEAP_TRACE_SRCFILE - -#include "esp_attr.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" - - -#define STACK_DEPTH CONFIG_HEAP_TRACING_STACK_DEPTH - -#if CONFIG_HEAP_TRACING_STANDALONE - -static portMUX_TYPE trace_mux = portMUX_INITIALIZER_UNLOCKED; -static bool tracing; -static heap_trace_mode_t mode; - -/* Buffer used for records, starting at offset 0 -*/ -static heap_trace_record_t *buffer; -static size_t total_records; - -/* Count of entries logged in the buffer. - - Maximum total_records -*/ -static size_t count; - -/* Actual number of allocations logged */ -static size_t total_allocations; - -/* Actual number of frees logged */ -static size_t total_frees; - -/* Has the buffer overflowed and lost trace entries? */ -static bool has_overflowed = false; - -esp_err_t heap_trace_init_standalone(heap_trace_record_t *record_buffer, size_t num_records) -{ - if (tracing) { - return ESP_ERR_INVALID_STATE; - } - buffer = record_buffer; - total_records = num_records; - memset(buffer, 0, num_records * sizeof(heap_trace_record_t)); - return ESP_OK; -} - -esp_err_t heap_trace_start(heap_trace_mode_t mode_param) -{ - if (buffer == NULL || total_records == 0) { - return ESP_ERR_INVALID_STATE; - } - - portENTER_CRITICAL(&trace_mux); - - tracing = false; - mode = mode_param; - count = 0; - total_allocations = 0; - total_frees = 0; - has_overflowed = false; - heap_trace_resume(); - - portEXIT_CRITICAL(&trace_mux); - return ESP_OK; -} - -static esp_err_t set_tracing(bool enable) -{ - if (tracing == enable) { - return ESP_ERR_INVALID_STATE; - } - tracing = enable; - return ESP_OK; -} - -esp_err_t heap_trace_stop(void) -{ - return set_tracing(false); -} - -esp_err_t heap_trace_resume(void) -{ - return set_tracing(true); -} - -size_t heap_trace_get_count(void) -{ - return count; -} - -esp_err_t heap_trace_get(size_t index, heap_trace_record_t *record) -{ - if (record == NULL) { - return ESP_ERR_INVALID_STATE; - } - esp_err_t result = ESP_OK; - - portENTER_CRITICAL(&trace_mux); - if (index >= count) { - result = ESP_ERR_INVALID_ARG; /* out of range for 'count' */ - } else { - memcpy(record, &buffer[index], sizeof(heap_trace_record_t)); - } - portEXIT_CRITICAL(&trace_mux); - return result; -} - - -void heap_trace_dump(void) -{ - size_t delta_size = 0; - size_t delta_allocs = 0; - printf("%u allocations trace (%u entry buffer)\n", - count, total_records); - size_t start_count = count; - for (int i = 0; i < count; i++) { - heap_trace_record_t *rec = &buffer[i]; - - if (rec->address != NULL) { - printf("%d bytes (@ %p) allocated CPU %d ccount 0x%08x caller ", - rec->size, rec->address, rec->ccount & 1, rec->ccount & ~3); - for (int j = 0; j < STACK_DEPTH && rec->alloced_by[j] != 0; j++) { - printf("%p%s", rec->alloced_by[j], - (j < STACK_DEPTH - 1) ? ":" : ""); - } - - if (mode != HEAP_TRACE_ALL || STACK_DEPTH == 0 || rec->freed_by[0] == NULL) { - delta_size += rec->size; - delta_allocs++; - printf("\n"); - } else { - printf("\nfreed by "); - for (int j = 0; j < STACK_DEPTH; j++) { - printf("%p%s", rec->freed_by[j], - (j < STACK_DEPTH - 1) ? ":" : "\n"); - } - } - } - } - if (mode == HEAP_TRACE_ALL) { - printf("%u bytes alive in trace (%u/%u allocations)\n", - delta_size, delta_allocs, heap_trace_get_count()); - } else { - printf("%u bytes 'leaked' in trace (%u allocations)\n", delta_size, delta_allocs); - } - printf("total allocations %u total frees %u\n", total_allocations, total_frees); - if (start_count != count) { // only a problem if trace isn't stopped before dumping - printf("(NB: New entries were traced while dumping, so trace dump may have duplicate entries.)\n"); - } - if (has_overflowed) { - printf("(NB: Buffer has overflowed, so trace data is incomplete.)\n"); - } -} - -/* Add a new allocation to the heap trace records */ -static IRAM_ATTR void record_allocation(const heap_trace_record_t *record) -{ - if (!tracing || record->address == NULL) { - return; - } - - portENTER_CRITICAL(&trace_mux); - if (tracing) { - if (count == total_records) { - has_overflowed = true; - /* Move the whole buffer back one slot. - - This is a bit slow, compared to treating this buffer as a ringbuffer and rotating a head pointer. - - However, ringbuffer code gets tricky when we remove elements in mid-buffer (for leak trace mode) while - trying to keep track of an item count that may overflow. - */ - memmove(&buffer[0], &buffer[1], sizeof(heap_trace_record_t) * (total_records -1)); - count--; - } - // Copy new record into place - memcpy(&buffer[count], record, sizeof(heap_trace_record_t)); - count++; - total_allocations++; - } - portEXIT_CRITICAL(&trace_mux); -} - -// remove a record, used when freeing -static void remove_record(int index); - -/* record a free event in the heap trace log - - For HEAP_TRACE_ALL, this means filling in the freed_by pointer. - For HEAP_TRACE_LEAKS, this means removing the record from the log. -*/ -static IRAM_ATTR void record_free(void *p, void **callers) -{ - if (!tracing || p == NULL) { - return; - } - - portENTER_CRITICAL(&trace_mux); - if (tracing && count > 0) { - total_frees++; - /* search backwards for the allocation record matching this free */ - int i; - for (i = count - 1; i >= 0; i--) { - if (buffer[i].address == p) { - break; - } - } - - if (i >= 0) { - if (mode == HEAP_TRACE_ALL) { - memcpy(buffer[i].freed_by, callers, sizeof(void *) * STACK_DEPTH); - } else { // HEAP_TRACE_LEAKS - // Leak trace mode, once an allocation is freed we remove it from the list - remove_record(i); - } - } - } - portEXIT_CRITICAL(&trace_mux); -} - -/* remove the entry at 'index' from the ringbuffer of saved records */ -static IRAM_ATTR void remove_record(int index) -{ - if (index < count - 1) { - // Remove the buffer entry from the list - memmove(&buffer[index], &buffer[index+1], - sizeof(heap_trace_record_t) * (total_records - index - 1)); - } else { - // For last element, just zero it out to avoid ambiguity - memset(&buffer[index], 0, sizeof(heap_trace_record_t)); - } - count--; -} - -#include "heap_trace.inc" - -#endif /*CONFIG_HEAP_TRACING_STANDALONE*/ diff --git a/components/heap/include/esp_heap_caps.h b/components/heap/include/esp_heap_caps.h deleted file mode 100644 index bed3d710..00000000 --- a/components/heap/include/esp_heap_caps.h +++ /dev/null @@ -1,402 +0,0 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -#pragma once - -#include -#include -#include "multi_heap.h" -#include -#include "esp_err.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief Flags to indicate the capabilities of the various memory systems - */ -#define MALLOC_CAP_EXEC (1<<0) ///< Memory must be able to run executable code -#define MALLOC_CAP_32BIT (1<<1) ///< Memory must allow for aligned 32-bit data accesses -#define MALLOC_CAP_8BIT (1<<2) ///< Memory must allow for 8/16/...-bit data accesses -#define MALLOC_CAP_DMA (1<<3) ///< Memory must be able to accessed by DMA -#define MALLOC_CAP_PID2 (1<<4) ///< Memory must be mapped to PID2 memory space (PIDs are not currently used) -#define MALLOC_CAP_PID3 (1<<5) ///< Memory must be mapped to PID3 memory space (PIDs are not currently used) -#define MALLOC_CAP_PID4 (1<<6) ///< Memory must be mapped to PID4 memory space (PIDs are not currently used) -#define MALLOC_CAP_PID5 (1<<7) ///< Memory must be mapped to PID5 memory space (PIDs are not currently used) -#define MALLOC_CAP_PID6 (1<<8) ///< Memory must be mapped to PID6 memory space (PIDs are not currently used) -#define MALLOC_CAP_PID7 (1<<9) ///< Memory must be mapped to PID7 memory space (PIDs are not currently used) -#define MALLOC_CAP_SPIRAM (1<<10) ///< Memory must be in SPI RAM -#define MALLOC_CAP_INTERNAL (1<<11) ///< Memory must be internal; specifically it should not disappear when flash/spiram cache is switched off -#define MALLOC_CAP_DEFAULT (1<<12) ///< Memory can be returned in a non-capability-specific memory allocation (e.g. malloc(), calloc()) call -#define MALLOC_CAP_IRAM_8BIT (1<<13) ///< Memory must be in IRAM and allow unaligned access -#define MALLOC_CAP_RETENTION (1<<14) - -#define MALLOC_CAP_INVALID (1<<31) ///< Memory can't be used / list end marker - -/** - * @brief callback called when a allocation operation fails, if registered - * @param size in bytes of failed allocation - * @param caps capabillites requested of failed allocation - * @param function_name function which generated the failure - */ -typedef void (*esp_alloc_failed_hook_t) (size_t size, uint32_t caps, const char * function_name); - -/** - * @brief registers a callback function to be invoked if a memory allocation operation fails - * @param callback caller defined callback to be invoked - * @return ESP_OK if callback was registered. - */ -esp_err_t heap_caps_register_failed_alloc_callback(esp_alloc_failed_hook_t callback); - -/** - * @brief Allocate a chunk of memory which has the given capabilities - * - * Equivalent semantics to libc malloc(), for capability-aware memory. - * - * In IDF, ``malloc(p)`` is equivalent to ``heap_caps_malloc(p, MALLOC_CAP_8BIT)``. - * - * @param size Size, in bytes, of the amount of memory to allocate - * @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type - * of memory to be returned - * - * @return A pointer to the memory allocated on success, NULL on failure - */ -void *heap_caps_malloc(size_t size, uint32_t caps); - - -/** - * @brief Free memory previously allocated via heap_caps_malloc() or heap_caps_realloc(). - * - * Equivalent semantics to libc free(), for capability-aware memory. - * - * In IDF, ``free(p)`` is equivalent to ``heap_caps_free(p)``. - * - * @param ptr Pointer to memory previously returned from heap_caps_malloc() or heap_caps_realloc(). Can be NULL. - */ -void heap_caps_free( void *ptr); - -/** - * @brief Reallocate memory previously allocated via heap_caps_malloc() or heap_caps_realloc(). - * - * Equivalent semantics to libc realloc(), for capability-aware memory. - * - * In IDF, ``realloc(p, s)`` is equivalent to ``heap_caps_realloc(p, s, MALLOC_CAP_8BIT)``. - * - * 'caps' parameter can be different to the capabilities that any original 'ptr' was allocated with. In this way, - * realloc can be used to "move" a buffer if necessary to ensure it meets a new set of capabilities. - * - * @param ptr Pointer to previously allocated memory, or NULL for a new allocation. - * @param size Size of the new buffer requested, or 0 to free the buffer. - * @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type - * of memory desired for the new allocation. - * - * @return Pointer to a new buffer of size 'size' with capabilities 'caps', or NULL if allocation failed. - */ -void *heap_caps_realloc( void *ptr, size_t size, uint32_t caps); - -/** - * @brief Allocate a aligned chunk of memory which has the given capabilities - * - * Equivalent semantics to libc aligned_alloc(), for capability-aware memory. - * @param alignment How the pointer received needs to be aligned - * must be a power of two - * @param size Size, in bytes, of the amount of memory to allocate - * @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type - * of memory to be returned - * - * @return A pointer to the memory allocated on success, NULL on failure - * - * - */ -void *heap_caps_aligned_alloc(size_t alignment, size_t size, uint32_t caps); - -/** - * @brief Used to deallocate memory previously allocated with heap_caps_aligned_alloc - * - * @param ptr Pointer to the memory allocated - * @note This function is deprecated, plase consider using heap_caps_free() instead - */ -void __attribute__((deprecated)) heap_caps_aligned_free(void *ptr); - -/** - * @brief Allocate a aligned chunk of memory which has the given capabilities. The initialized value in the memory is set to zero. - * - * @param alignment How the pointer received needs to be aligned - * must be a power of two - * @param n Number of continuing chunks of memory to allocate - * @param size Size, in bytes, of a chunk of memory to allocate - * @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type - * of memory to be returned - * - * @return A pointer to the memory allocated on success, NULL on failure - * - */ -void *heap_caps_aligned_calloc(size_t alignment, size_t n, size_t size, uint32_t caps); - - -/** - * @brief Allocate a chunk of memory which has the given capabilities. The initialized value in the memory is set to zero. - * - * Equivalent semantics to libc calloc(), for capability-aware memory. - * - * In IDF, ``calloc(p)`` is equivalent to ``heap_caps_calloc(p, MALLOC_CAP_8BIT)``. - * - * @param n Number of continuing chunks of memory to allocate - * @param size Size, in bytes, of a chunk of memory to allocate - * @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type - * of memory to be returned - * - * @return A pointer to the memory allocated on success, NULL on failure - */ -void *heap_caps_calloc(size_t n, size_t size, uint32_t caps); - -/** - * @brief Get the total size of all the regions that have the given capabilities - * - * This function takes all regions capable of having the given capabilities allocated in them - * and adds up the total space they have. - * - * @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type - * of memory - * - * @return total size in bytes - */ - -size_t heap_caps_get_total_size(uint32_t caps); - -/** - * @brief Get the total free size of all the regions that have the given capabilities - * - * This function takes all regions capable of having the given capabilities allocated in them - * and adds up the free space they have. - * - * Note that because of heap fragmentation it is probably not possible to allocate a single block of memory - * of this size. Use heap_caps_get_largest_free_block() for this purpose. - - * @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type - * of memory - * - * @return Amount of free bytes in the regions - */ -size_t heap_caps_get_free_size( uint32_t caps ); - - -/** - * @brief Get the total minimum free memory of all regions with the given capabilities - * - * This adds all the low water marks of the regions capable of delivering the memory - * with the given capabilities. - * - * Note the result may be less than the global all-time minimum available heap of this kind, as "low water marks" are - * tracked per-region. Individual regions' heaps may have reached their "low water marks" at different points in time. However - * this result still gives a "worst case" indication for all-time minimum free heap. - * - * @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type - * of memory - * - * @return Amount of free bytes in the regions - */ -size_t heap_caps_get_minimum_free_size( uint32_t caps ); - -/** - * @brief Get the largest free block of memory able to be allocated with the given capabilities. - * - * Returns the largest value of ``s`` for which ``heap_caps_malloc(s, caps)`` will succeed. - * - * @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type - * of memory - * - * @return Size of largest free block in bytes. - */ -size_t heap_caps_get_largest_free_block( uint32_t caps ); - - -/** - * @brief Get heap info for all regions with the given capabilities. - * - * Calls multi_heap_info() on all heaps which share the given capabilities. The information returned is an aggregate - * across all matching heaps. The meanings of fields are the same as defined for multi_heap_info_t, except that - * ``minimum_free_bytes`` has the same caveats described in heap_caps_get_minimum_free_size(). - * - * @param info Pointer to a structure which will be filled with relevant - * heap metadata. - * @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type - * of memory - * - */ -void heap_caps_get_info( multi_heap_info_t *info, uint32_t caps ); - - -/** - * @brief Print a summary of all memory with the given capabilities. - * - * Calls multi_heap_info on all heaps which share the given capabilities, and - * prints a two-line summary for each, then a total summary. - * - * @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type - * of memory - * - */ -void heap_caps_print_heap_info( uint32_t caps ); - -/** - * @brief Check integrity of all heap memory in the system. - * - * Calls multi_heap_check on all heaps. Optionally print errors if heaps are corrupt. - * - * Calling this function is equivalent to calling heap_caps_check_integrity - * with the caps argument set to MALLOC_CAP_INVALID. - * - * @param print_errors Print specific errors if heap corruption is found. - * - * @return True if all heaps are valid, False if at least one heap is corrupt. - */ -bool heap_caps_check_integrity_all(bool print_errors); - -/** - * @brief Check integrity of all heaps with the given capabilities. - * - * Calls multi_heap_check on all heaps which share the given capabilities. Optionally - * print errors if the heaps are corrupt. - * - * See also heap_caps_check_integrity_all to check all heap memory - * in the system and heap_caps_check_integrity_addr to check memory - * around a single address. - * - * @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type - * of memory - * @param print_errors Print specific errors if heap corruption is found. - * - * @return True if all heaps are valid, False if at least one heap is corrupt. - */ -bool heap_caps_check_integrity(uint32_t caps, bool print_errors); - -/** - * @brief Check integrity of heap memory around a given address. - * - * This function can be used to check the integrity of a single region of heap memory, - * which contains the given address. - * - * This can be useful if debugging heap integrity for corruption at a known address, - * as it has a lower overhead than checking all heap regions. Note that if the corrupt - * address moves around between runs (due to timing or other factors) then this approach - * won't work and you should call heap_caps_check_integrity or - * heap_caps_check_integrity_all instead. - * - * @note The entire heap region around the address is checked, not only the adjacent - * heap blocks. - * - * @param addr Address in memory. Check for corruption in region containing this address. - * @param print_errors Print specific errors if heap corruption is found. - * - * @return True if the heap containing the specified address is valid, - * False if at least one heap is corrupt or the address doesn't belong to a heap region. - */ -bool heap_caps_check_integrity_addr(intptr_t addr, bool print_errors); - -/** - * @brief Enable malloc() in external memory and set limit below which - * malloc() attempts are placed in internal memory. - * - * When external memory is in use, the allocation strategy is to initially try to - * satisfy smaller allocation requests with internal memory and larger requests - * with external memory. This sets the limit between the two, as well as generally - * enabling allocation in external memory. - * - * @param limit Limit, in bytes. - */ -void heap_caps_malloc_extmem_enable(size_t limit); - -/** - * @brief Allocate a chunk of memory as preference in decreasing order. - * - * @attention The variable parameters are bitwise OR of MALLOC_CAP_* flags indicating the type of memory. - * This API prefers to allocate memory with the first parameter. If failed, allocate memory with - * the next parameter. It will try in this order until allocating a chunk of memory successfully - * or fail to allocate memories with any of the parameters. - * - * @param size Size, in bytes, of the amount of memory to allocate - * @param num Number of variable paramters - * - * @return A pointer to the memory allocated on success, NULL on failure - */ -void *heap_caps_malloc_prefer( size_t size, size_t num, ... ); - -/** - * @brief Allocate a chunk of memory as preference in decreasing order. - * - * @param ptr Pointer to previously allocated memory, or NULL for a new allocation. - * @param size Size of the new buffer requested, or 0 to free the buffer. - * @param num Number of variable paramters - * - * @return Pointer to a new buffer of size 'size', or NULL if allocation failed. - */ -void *heap_caps_realloc_prefer( void *ptr, size_t size, size_t num, ... ); - -/** - * @brief Allocate a chunk of memory as preference in decreasing order. - * - * @param n Number of continuing chunks of memory to allocate - * @param size Size, in bytes, of a chunk of memory to allocate - * @param num Number of variable paramters - * - * @return A pointer to the memory allocated on success, NULL on failure - */ -void *heap_caps_calloc_prefer( size_t n, size_t size, size_t num, ... ); - -/** - * @brief Dump the full structure of all heaps with matching capabilities. - * - * Prints a large amount of output to serial (because of locking limitations, - * the output bypasses stdout/stderr). For each (variable sized) block - * in each matching heap, the following output is printed on a single line: - * - * - Block address (the data buffer returned by malloc is 4 bytes after this - * if heap debugging is set to Basic, or 8 bytes otherwise). - * - Data size (the data size may be larger than the size requested by malloc, - * either due to heap fragmentation or because of heap debugging level). - * - Address of next block in the heap. - * - If the block is free, the address of the next free block is also printed. - * - * @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type - * of memory - */ -void heap_caps_dump(uint32_t caps); - -/** - * @brief Dump the full structure of all heaps. - * - * Covers all registered heaps. Prints a large amount of output to serial. - * - * Output is the same as for heap_caps_dump. - * - */ -void heap_caps_dump_all(void); - -/** - * @brief Return the size that a particular pointer was allocated with. - * - * @param ptr Pointer to currently allocated heap memory. Must be a pointer value previously - * returned by heap_caps_malloc,malloc,calloc, etc. and not yet freed. - * - * @note The app will crash with an assertion failure if the pointer is not valid. - * - * @return Size of the memory allocated at this block. - * - */ -size_t heap_caps_get_allocated_size( void *ptr ); - -#ifdef __cplusplus -} -#endif diff --git a/components/heap/include/esp_heap_caps_init.h b/components/heap/include/esp_heap_caps_init.h deleted file mode 100644 index 74e8cb90..00000000 --- a/components/heap/include/esp_heap_caps_init.h +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2017 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -#pragma once - -#include "esp_err.h" -#include "esp_heap_caps.h" -#include "soc/soc_memory_layout.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief Initialize the capability-aware heap allocator. - * - * This is called once in the IDF startup code. Do not call it - * at other times. - */ -void heap_caps_init(void); - -/** - * @brief Enable heap(s) in memory regions where the startup stacks are located. - * - * On startup, the pro/app CPUs have a certain memory region they use as stack, so we - * cannot do allocations in the regions these stack frames are. When FreeRTOS is - * completely started, they do not use that memory anymore and heap(s) there can - * be enabled. - */ -void heap_caps_enable_nonos_stack_heaps(void); - -/** - * @brief Add a region of memory to the collection of heaps at runtime. - * - * Most memory regions are defined in soc_memory_layout.c for the SoC, - * and are registered via heap_caps_init(). Some regions can't be used - * immediately and are later enabled via heap_caps_enable_nonos_stack_heaps(). - * - * Call this function to add a region of memory to the heap at some later time. - * - * This function does not consider any of the "reserved" regions or other data in soc_memory_layout, caller needs to - * consider this themselves. - * - * All memory within the region specified by start & end parameters must be otherwise unused. - * - * The capabilities of the newly registered memory will be determined by the start address, as looked up in the regions - * specified in soc_memory_layout.c. - * - * Use heap_caps_add_region_with_caps() to register a region with custom capabilities. - * - * @param start Start address of new region. - * @param end End address of new region. - * - * @return ESP_OK on success, ESP_ERR_INVALID_ARG if a parameter is invalid, ESP_ERR_NOT_FOUND if the - * specified start address doesn't reside in a known region, or any error returned by heap_caps_add_region_with_caps(). - */ -esp_err_t heap_caps_add_region(intptr_t start, intptr_t end); - - -/** - * @brief Add a region of memory to the collection of heaps at runtime, with custom capabilities. - * - * Similar to heap_caps_add_region(), only custom memory capabilities are specified by the caller. - * - * @param caps Ordered array of capability masks for the new region, in order of priority. Must have length - * SOC_MEMORY_TYPE_NO_PRIOS. Does not need to remain valid after the call returns. - * @param start Start address of new region. - * @param end End address of new region. - * - * @return - * - ESP_OK on success - * - ESP_ERR_INVALID_ARG if a parameter is invalid - * - ESP_ERR_NO_MEM if no memory to register new heap. - * - ESP_ERR_INVALID_SIZE if the memory region is too small to fit a heap - * - ESP_FAIL if region overlaps the start and/or end of an existing region - */ -esp_err_t heap_caps_add_region_with_caps(const uint32_t caps[], intptr_t start, intptr_t end); - - -#ifdef __cplusplus -} -#endif diff --git a/components/heap/include/esp_heap_task_info.h b/components/heap/include/esp_heap_task_info.h deleted file mode 100644 index fca9a43b..00000000 --- a/components/heap/include/esp_heap_task_info.h +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2018 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -#pragma once - -#ifdef CONFIG_HEAP_TASK_TRACKING - -#ifdef __cplusplus -extern "C" { -#endif - -// This macro controls how much space is provided for partitioning the per-task -// heap allocation info according to one or more sets of heap capabilities. -#define NUM_HEAP_TASK_CAPS 4 - -/** @brief Structure to collect per-task heap allocation totals partitioned by selected caps */ -typedef struct { - TaskHandle_t task; ///< Task to which these totals belong - size_t size[NUM_HEAP_TASK_CAPS]; ///< Total allocations partitioned by selected caps - size_t count[NUM_HEAP_TASK_CAPS]; ///< Number of blocks partitioned by selected caps -} heap_task_totals_t; - -/** @brief Structure providing details about a block allocated by a task */ -typedef struct { - TaskHandle_t task; ///< Task that allocated the block - void *address; ///< User address of allocated block - uint32_t size; ///< Size of the allocated block -} heap_task_block_t; - -/** @brief Structure to provide parameters to heap_caps_get_per_task_info - * - * The 'caps' and 'mask' arrays allow partitioning the per-task heap allocation - * totals by selected sets of heap region capabilities so that totals for - * multiple regions can be accumulated in one scan. The capabilities flags for - * each region ANDed with mask[i] are compared to caps[i] in order; the - * allocations in that region are added to totals->size[i] and totals->count[i] - * for the first i that matches. To collect the totals without any - * partitioning, set mask[0] and caps[0] both to zero. The allocation totals - * are returned in the 'totals' array of heap_task_totals_t structs. To allow - * easily comparing the totals array between consecutive calls, that array can - * be left populated from one call to the next so the order of tasks is the - * same even if some tasks have freed their blocks or have been deleted. The - * number of blocks prepopulated is given by num_totals, which is updated upon - * return. If there are more tasks with allocations than the capacity of the - * totals array (given by max_totals), information for the excess tasks will be - * not be collected. The totals array pointer can be NULL if the totals are - * not desired. - * - * The 'tasks' array holds a list of handles for tasks whose block details are - * to be returned in the 'blocks' array of heap_task_block_t structs. If the - * tasks array pointer is NULL, block details for all tasks will be returned up - * to the capacity of the buffer array, given by max_blocks. The function - * return value tells the number of blocks filled into the array. The blocks - * array pointer can be NULL if block details are not desired, or max_blocks - * can be set to zero. - */ -typedef struct { - int32_t caps[NUM_HEAP_TASK_CAPS]; ///< Array of caps for partitioning task totals - int32_t mask[NUM_HEAP_TASK_CAPS]; ///< Array of masks under which caps must match - TaskHandle_t *tasks; ///< Array of tasks whose block info is returned - size_t num_tasks; ///< Length of tasks array - heap_task_totals_t *totals; ///< Array of structs to collect task totals - size_t *num_totals; ///< Number of task structs currently in array - size_t max_totals; ///< Capacity of array of task totals structs - heap_task_block_t *blocks; ///< Array of task block details structs - size_t max_blocks; ///< Capacity of array of task block info structs -} heap_task_info_params_t; - -/** - * @brief Return per-task heap allocation totals and lists of blocks. - * - * For each task that has allocated memory from the heap, return totals for - * allocations within regions matching one or more sets of capabilities. - * - * Optionally also return an array of structs providing details about each - * block allocated by one or more requested tasks, or by all tasks. - * - * @param params Structure to hold all the parameters for the function - * (@see heap_task_info_params_t). - * @return Number of block detail structs returned (@see heap_task_block_t). - */ -extern size_t heap_caps_get_per_task_info(heap_task_info_params_t *params); - -#ifdef __cplusplus -} -#endif - -#endif // CONFIG_HEAP_TASK_TRACKING diff --git a/components/heap/include/esp_heap_trace.h b/components/heap/include/esp_heap_trace.h deleted file mode 100644 index a71f9636..00000000 --- a/components/heap/include/esp_heap_trace.h +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -#pragma once - -#include "sdkconfig.h" -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#if !defined(CONFIG_HEAP_TRACING) && !defined(HEAP_TRACE_SRCFILE) -#warning "esp_heap_trace.h is included but heap tracing is disabled in menuconfig, functions are no-ops" -#endif - -#ifndef CONFIG_HEAP_TRACING_STACK_DEPTH -#define CONFIG_HEAP_TRACING_STACK_DEPTH 0 -#endif - -typedef enum { - HEAP_TRACE_ALL, - HEAP_TRACE_LEAKS, -} heap_trace_mode_t; - -/** - * @brief Trace record data type. Stores information about an allocated region of memory. - */ -typedef struct { - uint32_t ccount; ///< CCOUNT of the CPU when the allocation was made. LSB (bit value 1) is the CPU number (0 or 1). - void *address; ///< Address which was allocated - size_t size; ///< Size of the allocation - void *alloced_by[CONFIG_HEAP_TRACING_STACK_DEPTH]; ///< Call stack of the caller which allocated the memory. - void *freed_by[CONFIG_HEAP_TRACING_STACK_DEPTH]; ///< Call stack of the caller which freed the memory (all zero if not freed.) -} heap_trace_record_t; - -/** - * @brief Initialise heap tracing in standalone mode. - * - * This function must be called before any other heap tracing functions. - * - * To disable heap tracing and allow the buffer to be freed, stop tracing and then call heap_trace_init_standalone(NULL, 0); - * - * @param record_buffer Provide a buffer to use for heap trace data. Must remain valid any time heap tracing is enabled, meaning - * it must be allocated from internal memory not in PSRAM. - * @param num_records Size of the heap trace buffer, as number of record structures. - * @return - * - ESP_ERR_NOT_SUPPORTED Project was compiled without heap tracing enabled in menuconfig. - * - ESP_ERR_INVALID_STATE Heap tracing is currently in progress. - * - ESP_OK Heap tracing initialised successfully. - */ -esp_err_t heap_trace_init_standalone(heap_trace_record_t *record_buffer, size_t num_records); - -/** - * @brief Initialise heap tracing in host-based mode. - * - * This function must be called before any other heap tracing functions. - * - * @return - * - ESP_ERR_INVALID_STATE Heap tracing is currently in progress. - * - ESP_OK Heap tracing initialised successfully. - */ -esp_err_t heap_trace_init_tohost(void); - -/** - * @brief Start heap tracing. All heap allocations & frees will be traced, until heap_trace_stop() is called. - * - * @note heap_trace_init_standalone() must be called to provide a valid buffer, before this function is called. - * - * @note Calling this function while heap tracing is running will reset the heap trace state and continue tracing. - * - * @param mode Mode for tracing. - * - HEAP_TRACE_ALL means all heap allocations and frees are traced. - * - HEAP_TRACE_LEAKS means only suspected memory leaks are traced. (When memory is freed, the record is removed from the trace buffer.) - * @return - * - ESP_ERR_NOT_SUPPORTED Project was compiled without heap tracing enabled in menuconfig. - * - ESP_ERR_INVALID_STATE A non-zero-length buffer has not been set via heap_trace_init_standalone(). - * - ESP_OK Tracing is started. - */ -esp_err_t heap_trace_start(heap_trace_mode_t mode); - -/** - * @brief Stop heap tracing. - * - * @return - * - ESP_ERR_NOT_SUPPORTED Project was compiled without heap tracing enabled in menuconfig. - * - ESP_ERR_INVALID_STATE Heap tracing was not in progress. - * - ESP_OK Heap tracing stopped.. - */ -esp_err_t heap_trace_stop(void); - -/** - * @brief Resume heap tracing which was previously stopped. - * - * Unlike heap_trace_start(), this function does not clear the - * buffer of any pre-existing trace records. - * - * The heap trace mode is the same as when heap_trace_start() was - * last called (or HEAP_TRACE_ALL if heap_trace_start() was never called). - * - * @return - * - ESP_ERR_NOT_SUPPORTED Project was compiled without heap tracing enabled in menuconfig. - * - ESP_ERR_INVALID_STATE Heap tracing was already started. - * - ESP_OK Heap tracing resumed. - */ -esp_err_t heap_trace_resume(void); - -/** - * @brief Return number of records in the heap trace buffer - * - * It is safe to call this function while heap tracing is running. - */ -size_t heap_trace_get_count(void); - -/** - * @brief Return a raw record from the heap trace buffer - * - * @note It is safe to call this function while heap tracing is running, however in HEAP_TRACE_LEAK mode record indexing may - * skip entries unless heap tracing is stopped first. - * - * @param index Index (zero-based) of the record to return. - * @param[out] record Record where the heap trace record will be copied. - * @return - * - ESP_ERR_NOT_SUPPORTED Project was compiled without heap tracing enabled in menuconfig. - * - ESP_ERR_INVALID_STATE Heap tracing was not initialised. - * - ESP_ERR_INVALID_ARG Index is out of bounds for current heap trace record count. - * - ESP_OK Record returned successfully. - */ -esp_err_t heap_trace_get(size_t index, heap_trace_record_t *record); - -/** - * @brief Dump heap trace record data to stdout - * - * @note It is safe to call this function while heap tracing is running, however in HEAP_TRACE_LEAK mode the dump may skip - * entries unless heap tracing is stopped first. - * - * - */ -void heap_trace_dump(void); - -#ifdef __cplusplus -} -#endif diff --git a/components/heap/include/heap_trace.inc b/components/heap/include/heap_trace.inc deleted file mode 100644 index 6f789285..00000000 --- a/components/heap/include/heap_trace.inc +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -#include -#include -#include "soc/soc_memory_layout.h" -#include "esp_attr.h" - -/* Encode the CPU ID in the LSB of the ccount value */ -inline static uint32_t get_ccount(void) -{ - uint32_t ccount = cpu_hal_get_cycle_count() & ~3; -#ifndef CONFIG_FREERTOS_UNICORE - ccount |= xPortGetCoreID(); -#endif - return ccount; -} - -/* Architecture-specific return value of __builtin_return_address which - * should be interpreted as an invalid address. - */ -#ifdef __XTENSA__ -#define HEAP_ARCH_INVALID_PC 0x40000000 -#else -#define HEAP_ARCH_INVALID_PC 0x00000000 -#endif - -// Caller is 2 stack frames deeper than we care about -#define STACK_OFFSET 2 - -#define TEST_STACK(N) do { \ - if (STACK_DEPTH == N) { \ - return; \ - } \ - callers[N] = __builtin_return_address(N+STACK_OFFSET); \ - if (!esp_ptr_executable(callers[N]) \ - || callers[N] == (void*) HEAP_ARCH_INVALID_PC) { \ - callers[N] = 0; \ - return; \ - } \ - } while(0) - -/* Static function to read the call stack for a traced heap call. - - Calls to __builtin_return_address are "unrolled" via TEST_STACK macro as gcc requires the - argument to be a compile-time constant. -*/ -static IRAM_ATTR __attribute__((noinline)) void get_call_stack(void **callers) -{ - bzero(callers, sizeof(void *) * STACK_DEPTH); - TEST_STACK(0); - TEST_STACK(1); - TEST_STACK(2); - TEST_STACK(3); - TEST_STACK(4); - TEST_STACK(5); - TEST_STACK(6); - TEST_STACK(7); - TEST_STACK(8); - TEST_STACK(9); -} - -_Static_assert(STACK_DEPTH >= 0 && STACK_DEPTH <= 10, "CONFIG_HEAP_TRACING_STACK_DEPTH must be in range 0-10"); - - -typedef enum { - TRACE_MALLOC_CAPS, - TRACE_MALLOC_DEFAULT -} trace_malloc_mode_t; - - -void *__real_heap_caps_malloc(size_t size, uint32_t caps); -void *__real_heap_caps_malloc_default( size_t size ); -void *__real_heap_caps_realloc_default( void *ptr, size_t size ); - -/* trace any 'malloc' event */ -static IRAM_ATTR __attribute__((noinline)) void *trace_malloc(size_t size, uint32_t caps, trace_malloc_mode_t mode) -{ - uint32_t ccount = get_ccount(); - void *p; - - if ( mode == TRACE_MALLOC_CAPS ) { - p = __real_heap_caps_malloc(size, caps); - } else { //TRACE_MALLOC_DEFAULT - p = __real_heap_caps_malloc_default(size); - } - - heap_trace_record_t rec = { - .address = p, - .ccount = ccount, - .size = size, - }; - get_call_stack(rec.alloced_by); - record_allocation(&rec); - return p; -} - -void __real_heap_caps_free(void *p); - -/* trace any 'free' event */ -static IRAM_ATTR __attribute__((noinline)) void trace_free(void *p) -{ - void *callers[STACK_DEPTH]; - get_call_stack(callers); - record_free(p, callers); - - __real_heap_caps_free(p); -} - -void * __real_heap_caps_realloc(void *p, size_t size, uint32_t caps); - -/* trace any 'realloc' event */ -static IRAM_ATTR __attribute__((noinline)) void *trace_realloc(void *p, size_t size, uint32_t caps, trace_malloc_mode_t mode) -{ - void *callers[STACK_DEPTH]; - uint32_t ccount = get_ccount(); - void *r; - - /* trace realloc as free-then-alloc */ - get_call_stack(callers); - record_free(p, callers); - - if (mode == TRACE_MALLOC_CAPS ) { - r = __real_heap_caps_realloc(p, size, caps); - } else { //TRACE_MALLOC_DEFAULT - r = __real_heap_caps_realloc_default(p, size); - } - /* realloc with zero size is a free */ - if (size != 0) { - heap_trace_record_t rec = { - .address = r, - .ccount = ccount, - .size = size, - }; - memcpy(rec.alloced_by, callers, sizeof(void *) * STACK_DEPTH); - record_allocation(&rec); - } - return r; -} - -/* Note: this changes the behaviour of libc malloc/realloc/free a bit, - as they no longer go via the libc functions in ROM. But more or less - the same in the end. */ - -IRAM_ATTR void *__wrap_malloc(size_t size) -{ - return trace_malloc(size, 0, TRACE_MALLOC_DEFAULT); -} - -IRAM_ATTR void __wrap_free(void *p) -{ - trace_free(p); -} - -IRAM_ATTR void *__wrap_realloc(void *p, size_t size) -{ - return trace_realloc(p, size, 0, TRACE_MALLOC_DEFAULT); -} - -IRAM_ATTR void *__wrap_calloc(size_t nmemb, size_t size) -{ - size = size * nmemb; - void *result = trace_malloc(size, 0, TRACE_MALLOC_DEFAULT); - if (result != NULL) { - memset(result, 0, size); - } - return result; -} - -IRAM_ATTR void *__wrap_heap_caps_malloc(size_t size, uint32_t caps) -{ - return trace_malloc(size, caps, TRACE_MALLOC_CAPS); -} - -void __wrap_heap_caps_free(void *p) __attribute__((alias("__wrap_free"))); - -IRAM_ATTR void *__wrap_heap_caps_realloc(void *p, size_t size, uint32_t caps) -{ - return trace_realloc(p, size, caps, TRACE_MALLOC_CAPS); -} - -IRAM_ATTR void *__wrap_heap_caps_malloc_default( size_t size ) -{ - return trace_malloc(size, 0, TRACE_MALLOC_DEFAULT); -} - -IRAM_ATTR void *__wrap_heap_caps_realloc_default( void *ptr, size_t size ) -{ - return trace_realloc(ptr, size, 0, TRACE_MALLOC_DEFAULT); -} diff --git a/components/heap/include/multi_heap.h b/components/heap/include/multi_heap.h deleted file mode 100644 index 622191fe..00000000 --- a/components/heap/include/multi_heap.h +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -#pragma once -#include -#include -#include - -/* multi_heap is a heap implementation for handling multiple - heterogenous heaps in a single program. - - Any contiguous block of memory can be registered as a heap. -*/ - -#ifdef __cplusplus -extern "C" { -#endif - -/** @brief Opaque handle to a registered heap */ -typedef struct multi_heap_info *multi_heap_handle_t; - -/** - * @brief allocate a chunk of memory with specific alignment - * - * @param heap Handle to a registered heap. - * @param size size in bytes of memory chunk - * @param alignment how the memory must be aligned - * - * @return pointer to the memory allocated, NULL on failure - */ -void *multi_heap_aligned_alloc(multi_heap_handle_t heap, size_t size, size_t alignment); - -/** @brief malloc() a buffer in a given heap - * - * Semantics are the same as standard malloc(), only the returned buffer will be allocated in the specified heap. - * - * @param heap Handle to a registered heap. - * @param size Size of desired buffer. - * - * @return Pointer to new memory, or NULL if allocation fails. - */ -void *multi_heap_malloc(multi_heap_handle_t heap, size_t size); - -/** @brief free() a buffer aligned in a given heap. - * - * @param heap Handle to a registered heap. - * @param p NULL, or a pointer previously returned from multi_heap_aligned_alloc() for the same heap. - * @note This function is deprecated, consider using multi_heap_free() instead - */ -void __attribute__((deprecated)) multi_heap_aligned_free(multi_heap_handle_t heap, void *p); - -/** @brief free() a buffer in a given heap. - * - * Semantics are the same as standard free(), only the argument 'p' must be NULL or have been allocated in the specified heap. - * - * @param heap Handle to a registered heap. - * @param p NULL, or a pointer previously returned from multi_heap_malloc() or multi_heap_realloc() for the same heap. - */ -void multi_heap_free(multi_heap_handle_t heap, void *p); - -/** @brief realloc() a buffer in a given heap. - * - * Semantics are the same as standard realloc(), only the argument 'p' must be NULL or have been allocated in the specified heap. - * - * @param heap Handle to a registered heap. - * @param p NULL, or a pointer previously returned from multi_heap_malloc() or multi_heap_realloc() for the same heap. - * @param size Desired new size for buffer. - * - * @return New buffer of 'size' containing contents of 'p', or NULL if reallocation failed. - */ -void *multi_heap_realloc(multi_heap_handle_t heap, void *p, size_t size); - - -/** @brief Return the size that a particular pointer was allocated with. - * - * @param heap Handle to a registered heap. - * @param p Pointer, must have been previously returned from multi_heap_malloc() or multi_heap_realloc() for the same heap. - * - * @return Size of the memory allocated at this block. May be more than the original size argument, due - * to padding and minimum block sizes. - */ -size_t multi_heap_get_allocated_size(multi_heap_handle_t heap, void *p); - - -/** @brief Register a new heap for use - * - * This function initialises a heap at the specified address, and returns a handle for future heap operations. - * - * There is no equivalent function for deregistering a heap - if all blocks in the heap are free, you can immediately start using the memory for other purposes. - * - * @param start Start address of the memory to use for a new heap. - * @param size Size (in bytes) of the new heap. - * - * @return Handle of a new heap ready for use, or NULL if the heap region was too small to be initialised. - */ -multi_heap_handle_t multi_heap_register(void *start, size_t size); - - -/** @brief Associate a private lock pointer with a heap - * - * The lock argument is supplied to the MULTI_HEAP_LOCK() and MULTI_HEAP_UNLOCK() macros, defined in multi_heap_platform.h. - * - * The lock in question must be recursive. - * - * When the heap is first registered, the associated lock is NULL. - * - * @param heap Handle to a registered heap. - * @param lock Optional pointer to a locking structure to associate with this heap. - */ -void multi_heap_set_lock(multi_heap_handle_t heap, void* lock); - -/** @brief Dump heap information to stdout - * - * For debugging purposes, this function dumps information about every block in the heap to stdout. - * - * @param heap Handle to a registered heap. - */ -void multi_heap_dump(multi_heap_handle_t heap); - -/** @brief Check heap integrity - * - * Walks the heap and checks all heap data structures are valid. If any errors are detected, an error-specific message - * can be optionally printed to stderr. Print behaviour can be overriden at compile time by defining - * MULTI_CHECK_FAIL_PRINTF in multi_heap_platform.h. - * - * @param heap Handle to a registered heap. - * @param print_errors If true, errors will be printed to stderr. - * @return true if heap is valid, false otherwise. - */ -bool multi_heap_check(multi_heap_handle_t heap, bool print_errors); - -/** @brief Return free heap size - * - * Returns the number of bytes available in the heap. - * - * Equivalent to the total_free_bytes member returned by multi_heap_get_heap_info(). - * - * Note that the heap may be fragmented, so the actual maximum size for a single malloc() may be lower. To know this - * size, see the largest_free_block member returned by multi_heap_get_heap_info(). - * - * @param heap Handle to a registered heap. - * @return Number of free bytes. - */ -size_t multi_heap_free_size(multi_heap_handle_t heap); - -/** @brief Return the lifetime minimum free heap size - * - * Equivalent to the minimum_free_bytes member returned by multi_heap_get_info(). - * - * Returns the lifetime "low water mark" of possible values returned from multi_free_heap_size(), for the specified - * heap. - * - * @param heap Handle to a registered heap. - * @return Number of free bytes. - */ -size_t multi_heap_minimum_free_size(multi_heap_handle_t heap); - -/** @brief Structure to access heap metadata via multi_heap_get_info */ -typedef struct { - size_t total_free_bytes; ///< Total free bytes in the heap. Equivalent to multi_free_heap_size(). - size_t total_allocated_bytes; ///< Total bytes allocated to data in the heap. - size_t largest_free_block; ///< Size of largest free block in the heap. This is the largest malloc-able size. - size_t minimum_free_bytes; ///< Lifetime minimum free heap size. Equivalent to multi_minimum_free_heap_size(). - size_t allocated_blocks; ///< Number of (variable size) blocks allocated in the heap. - size_t free_blocks; ///< Number of (variable size) free blocks in the heap. - size_t total_blocks; ///< Total number of (variable size) blocks in the heap. -} multi_heap_info_t; - -/** @brief Return metadata about a given heap - * - * Fills a multi_heap_info_t structure with information about the specified heap. - * - * @param heap Handle to a registered heap. - * @param info Pointer to a structure to fill with heap metadata. - */ -void multi_heap_get_info(multi_heap_handle_t heap, multi_heap_info_t *info); - -#ifdef __cplusplus -} -#endif diff --git a/components/heap/linker.lf b/components/heap/linker.lf deleted file mode 100644 index a2be01e1..00000000 --- a/components/heap/linker.lf +++ /dev/null @@ -1,7 +0,0 @@ -[mapping:heap] -archive: libheap.a -entries: - heap_tlsf (noflash) - multi_heap (noflash) - if HEAP_POISONING_DISABLED = n: - multi_heap_poisoning (noflash) diff --git a/components/heap/multi_heap.c b/components/heap/multi_heap.c deleted file mode 100644 index 3ba3d3c0..00000000 --- a/components/heap/multi_heap.c +++ /dev/null @@ -1,376 +0,0 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -#include -#include -#include -#include -#include -#include -#include -#include -#include "heap_tlsf.h" -#include -#include "multi_heap_internal.h" - -/* Note: Keep platform-specific parts in this header, this source - file should depend on libc only */ -#include "multi_heap_platform.h" - -/* Defines compile-time configuration macros */ -#include "multi_heap_config.h" - -#ifndef MULTI_HEAP_POISONING -/* if no heap poisoning, public API aliases directly to these implementations */ -void *multi_heap_malloc(multi_heap_handle_t heap, size_t size) - __attribute__((alias("multi_heap_malloc_impl"))); - -void *multi_heap_aligned_alloc(multi_heap_handle_t heap, size_t size, size_t alignment) - __attribute__((alias("multi_heap_aligned_alloc_impl"))); - -void multi_heap_aligned_free(multi_heap_handle_t heap, void *p) - __attribute__((alias("multi_heap_free_impl"))); - -void multi_heap_free(multi_heap_handle_t heap, void *p) - __attribute__((alias("multi_heap_free_impl"))); - -void *multi_heap_realloc(multi_heap_handle_t heap, void *p, size_t size) - __attribute__((alias("multi_heap_realloc_impl"))); - -size_t multi_heap_get_allocated_size(multi_heap_handle_t heap, void *p) - __attribute__((alias("multi_heap_get_allocated_size_impl"))); - -multi_heap_handle_t multi_heap_register(void *start, size_t size) - __attribute__((alias("multi_heap_register_impl"))); - -void multi_heap_get_info(multi_heap_handle_t heap, multi_heap_info_t *info) - __attribute__((alias("multi_heap_get_info_impl"))); - -size_t multi_heap_free_size(multi_heap_handle_t heap) - __attribute__((alias("multi_heap_free_size_impl"))); - -size_t multi_heap_minimum_free_size(multi_heap_handle_t heap) - __attribute__((alias("multi_heap_minimum_free_size_impl"))); - -void *multi_heap_get_block_address(multi_heap_block_handle_t block) - __attribute__((alias("multi_heap_get_block_address_impl"))); - -void *multi_heap_get_block_owner(multi_heap_block_handle_t block) -{ - return NULL; -} - -#endif - -#define ALIGN(X) ((X) & ~(sizeof(void *)-1)) -#define ALIGN_UP(X) ALIGN((X)+sizeof(void *)-1) -#define ALIGN_UP_BY(num, align) (((num) + ((align) - 1)) & ~((align) - 1)) - - -typedef struct multi_heap_info { - void *lock; - size_t free_bytes; - size_t minimum_free_bytes; - size_t pool_size; - tlsf_t heap_data; -} heap_t; - -/* Return true if this block is free. */ -static inline bool is_free(const block_header_t *block) -{ - return ((block->size & 0x01) != 0); -} - -/* Data size of the block (excludes this block's header) */ -static inline size_t block_data_size(const block_header_t *block) -{ - return (block->size & ~0x03); -} - -/* Check a block is valid for this heap. Used to verify parameters. */ -static void assert_valid_block(const heap_t *heap, const block_header_t *block) -{ - pool_t pool = tlsf_get_pool(heap->heap_data); - void *ptr = block_to_ptr(block); - - MULTI_HEAP_ASSERT((ptr >= pool) && - (ptr < pool + heap->pool_size), - (uintptr_t)ptr); -} - -void *multi_heap_get_block_address_impl(multi_heap_block_handle_t block) -{ - void *ptr = block_to_ptr(block); - return (ptr); -} - -size_t multi_heap_get_allocated_size_impl(multi_heap_handle_t heap, void *p) -{ - return tlsf_block_size(p); -} - -multi_heap_handle_t multi_heap_register_impl(void *start_ptr, size_t size) -{ - assert(start_ptr); - if(size < (tlsf_size(NULL) + tlsf_block_size_min() + sizeof(heap_t))) { - //Region too small to be a heap. - return NULL; - } - - heap_t *result = (heap_t *)start_ptr; - size -= sizeof(heap_t); - - result->heap_data = tlsf_create_with_pool(start_ptr + sizeof(heap_t), size, 0); - if(!result->heap_data) { - return NULL; - } - - result->lock = NULL; - result->free_bytes = size - tlsf_size(result->heap_data); - result->pool_size = size; - result->minimum_free_bytes = result->free_bytes; - return result; -} - -void multi_heap_set_lock(multi_heap_handle_t heap, void *lock) -{ - heap->lock = lock; -} - -void inline multi_heap_internal_lock(multi_heap_handle_t heap) -{ - MULTI_HEAP_LOCK(heap->lock); -} - -void inline multi_heap_internal_unlock(multi_heap_handle_t heap) -{ - MULTI_HEAP_UNLOCK(heap->lock); -} - -multi_heap_block_handle_t multi_heap_get_first_block(multi_heap_handle_t heap) -{ - assert(heap != NULL); - pool_t pool = tlsf_get_pool(heap->heap_data); - block_header_t* block = offset_to_block(pool, -(int)block_header_overhead); - - return (multi_heap_block_handle_t)block; -} - -multi_heap_block_handle_t multi_heap_get_next_block(multi_heap_handle_t heap, multi_heap_block_handle_t block) -{ - assert(heap != NULL); - assert_valid_block(heap, block); - block_header_t* next = block_next(block); - - if(block_data_size(next) == 0) { - //Last block: - return NULL; - } else { - return (multi_heap_block_handle_t)next; - } - -} - -bool multi_heap_is_free(multi_heap_block_handle_t block) -{ - return is_free(block); -} - -void *multi_heap_malloc_impl(multi_heap_handle_t heap, size_t size) -{ - if (size == 0 || heap == NULL) { - return NULL; - } - - - multi_heap_internal_lock(heap); - void *result = tlsf_malloc(heap->heap_data, size); - if(result) { - heap->free_bytes -= tlsf_block_size(result); - if (heap->free_bytes < heap->minimum_free_bytes) { - heap->minimum_free_bytes = heap->free_bytes; - } - } - multi_heap_internal_unlock(heap); - - return result; -} - -void multi_heap_free_impl(multi_heap_handle_t heap, void *p) -{ - - if (heap == NULL || p == NULL) { - return; - } - - assert_valid_block(heap, p); - - multi_heap_internal_lock(heap); - heap->free_bytes += tlsf_block_size(p); - tlsf_free(heap->heap_data, p); - multi_heap_internal_unlock(heap); -} - -void *multi_heap_realloc_impl(multi_heap_handle_t heap, void *p, size_t size) -{ - assert(heap != NULL); - - if (p == NULL) { - return multi_heap_malloc_impl(heap, size); - } - - assert_valid_block(heap, p); - - if (heap == NULL) { - return NULL; - } - - multi_heap_internal_lock(heap); - size_t previous_block_size = tlsf_block_size(p); - void *result = tlsf_realloc(heap->heap_data, p, size); - if(result) { - heap->free_bytes += previous_block_size; - heap->free_bytes -= tlsf_block_size(result); - if (heap->free_bytes < heap->minimum_free_bytes) { - heap->minimum_free_bytes = heap->free_bytes; - } - } - - multi_heap_internal_unlock(heap); - - return result; -} - -void *multi_heap_aligned_alloc_impl_offs(multi_heap_handle_t heap, size_t size, size_t alignment, size_t offset) -{ - if(heap == NULL) { - return NULL; - } - - if(!size) { - return NULL; - } - - //Alignment must be a power of two: - if(((alignment & (alignment - 1)) != 0) ||(!alignment)) { - return NULL; - } - - multi_heap_internal_lock(heap); - void *result = tlsf_memalign_offs(heap->heap_data, alignment, size, offset); - if(result) { - heap->free_bytes -= tlsf_block_size(result); - if(heap->free_bytes < heap->minimum_free_bytes) { - heap->minimum_free_bytes = heap->free_bytes; - } - } - multi_heap_internal_unlock(heap); - - return result; -} - - -void *multi_heap_aligned_alloc_impl(multi_heap_handle_t heap, size_t size, size_t alignment) -{ - return multi_heap_aligned_alloc_impl_offs(heap, size, alignment, 0); -} - -bool multi_heap_check(multi_heap_handle_t heap, bool print_errors) -{ - (void)print_errors; - bool valid = true; - assert(heap != NULL); - - multi_heap_internal_lock(heap); - if(tlsf_check(heap->heap_data)) { - valid = false; - } - - if(tlsf_check_pool(tlsf_get_pool(heap->heap_data))) { - valid = false; - } - - multi_heap_internal_unlock(heap); - return valid; -} - -static void multi_heap_dump_tlsf(void* ptr, size_t size, int used, void* user) -{ - (void)user; - MULTI_HEAP_STDERR_PRINTF("Block %p data, size: %d bytes, Free: %s \n", - (void *)ptr, - size, - used ? "No" : "Yes"); -} - -void multi_heap_dump(multi_heap_handle_t heap) -{ - assert(heap != NULL); - - multi_heap_internal_lock(heap); - MULTI_HEAP_STDERR_PRINTF("Showing data for heap: %p \n", (void *)heap); - tlsf_walk_pool(tlsf_get_pool(heap->heap_data), multi_heap_dump_tlsf, NULL); - multi_heap_internal_unlock(heap); -} - -size_t multi_heap_free_size_impl(multi_heap_handle_t heap) -{ - if (heap == NULL) { - return 0; - } - - return heap->free_bytes; -} - -size_t multi_heap_minimum_free_size_impl(multi_heap_handle_t heap) -{ - if (heap == NULL) { - return 0; - } - - return heap->minimum_free_bytes; -} - -static void multi_heap_get_info_tlsf(void* ptr, size_t size, int used, void* user) -{ - multi_heap_info_t *info = user; - - if(used) { - info->allocated_blocks++; - } else { - info->free_blocks++; - - if(size > info->largest_free_block ) { - info->largest_free_block = size; - } - } - - info->total_blocks++; -} - -void multi_heap_get_info_impl(multi_heap_handle_t heap, multi_heap_info_t *info) -{ - memset(info, 0, sizeof(multi_heap_info_t)); - - if (heap == NULL) { - return; - } - - multi_heap_internal_lock(heap); - tlsf_walk_pool(tlsf_get_pool(heap->heap_data), multi_heap_get_info_tlsf, info); - info->total_allocated_bytes = (heap->pool_size - tlsf_size(heap->heap_data)) - heap->free_bytes; - info->minimum_free_bytes = heap->minimum_free_bytes; - info->total_free_bytes = heap->free_bytes; - info->largest_free_block = tlsf_fit_size(heap->heap_data, info->largest_free_block); - multi_heap_internal_unlock(heap); -} diff --git a/components/heap/multi_heap_config.h b/components/heap/multi_heap_config.h deleted file mode 100644 index 13d30cfd..00000000 --- a/components/heap/multi_heap_config.h +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -#pragma once - -#ifdef ESP_PLATFORM -#include "sdkconfig.h" -#include "soc/soc.h" -#include "soc/soc_caps.h" -#endif - -/* Configuration macros for multi-heap */ - -#ifdef CONFIG_HEAP_POISONING_LIGHT -#define MULTI_HEAP_POISONING -#endif - -#ifdef CONFIG_HEAP_POISONING_COMPREHENSIVE -#define MULTI_HEAP_POISONING -#define MULTI_HEAP_POISONING_SLOW -#endif diff --git a/components/heap/multi_heap_internal.h b/components/heap/multi_heap_internal.h deleted file mode 100644 index 5be4dee6..00000000 --- a/components/heap/multi_heap_internal.h +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -#pragma once - -/* Opaque handle to a heap block */ -typedef const struct block_header_t *multi_heap_block_handle_t; - -/* Internal definitions for the "implementation" of the multi_heap API, - as defined in multi_heap.c. - - If heap poisioning is disabled, these are aliased directly to the public API. - - If heap poisoning is enabled, wrapper functions call each of these. -*/ - -void *multi_heap_malloc_impl(multi_heap_handle_t heap, size_t size); - -/* Allocate a memory region of minimum `size` bytes, aligned on `alignment`. */ -void *multi_heap_aligned_alloc_impl(multi_heap_handle_t heap, size_t size, size_t alignment); - -/* Allocate a memory region of minimum `size` bytes, where memory's `offset` is aligned on `alignment`. */ -void *multi_heap_aligned_alloc_impl_offs(multi_heap_handle_t heap, size_t size, size_t alignment, size_t offset); - -void multi_heap_free_impl(multi_heap_handle_t heap, void *p); -void *multi_heap_realloc_impl(multi_heap_handle_t heap, void *p, size_t size); -multi_heap_handle_t multi_heap_register_impl(void *start, size_t size); -void multi_heap_get_info_impl(multi_heap_handle_t heap, multi_heap_info_t *info); -size_t multi_heap_free_size_impl(multi_heap_handle_t heap); -size_t multi_heap_minimum_free_size_impl(multi_heap_handle_t heap); -size_t multi_heap_get_allocated_size_impl(multi_heap_handle_t heap, void *p); -void *multi_heap_get_block_address_impl(multi_heap_block_handle_t block); - -/* Some internal functions for heap poisoning use */ - -/* Check an allocated block's poison bytes are correct. Called by multi_heap_check(). */ -bool multi_heap_internal_check_block_poisoning(void *start, size_t size, bool is_free, bool print_errors); - -/* Fill a region of memory with the free or malloced pattern. - Called when merging blocks, to overwrite the old block header. -*/ -void multi_heap_internal_poison_fill_region(void *start, size_t size, bool is_free); - -/* Allow heap poisoning to lock/unlock the heap to avoid race conditions - if multi_heap_check() is running concurrently. -*/ -void multi_heap_internal_lock(multi_heap_handle_t heap); - -void multi_heap_internal_unlock(multi_heap_handle_t heap); - -/* Some internal functions for heap debugging code to use */ - -/* Get the handle to the first (fixed free) block in a heap */ -multi_heap_block_handle_t multi_heap_get_first_block(multi_heap_handle_t heap); - -/* Get the handle to the next block in a heap, with validation */ -multi_heap_block_handle_t multi_heap_get_next_block(multi_heap_handle_t heap, multi_heap_block_handle_t block); - -/* Test if a heap block is free */ -bool multi_heap_is_free(const multi_heap_block_handle_t block); - -/* Get the data address of a heap block */ -void *multi_heap_get_block_address(multi_heap_block_handle_t block); - -/* Get the owner identification for a heap block */ -void *multi_heap_get_block_owner(multi_heap_block_handle_t block); diff --git a/components/heap/multi_heap_platform.h b/components/heap/multi_heap_platform.h deleted file mode 100644 index e6114a5e..00000000 --- a/components/heap/multi_heap_platform.h +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -#pragma once - -#ifdef MULTI_HEAP_FREERTOS - -#include "freertos/FreeRTOS.h" - -#include "sdkconfig.h" -#include "esp_rom_sys.h" -#if CONFIG_IDF_TARGET_ESP32 -#include "esp32/rom/ets_sys.h" // will be removed in idf v5.0 -#elif CONFIG_IDF_TARGET_ESP32S2 -#include "esp32s2/rom/ets_sys.h" -#endif -#include - -typedef portMUX_TYPE multi_heap_lock_t; - -/* Because malloc/free can happen inside an ISR context, - we need to use portmux spinlocks here not RTOS mutexes */ -#define MULTI_HEAP_LOCK(PLOCK) do { \ - if((PLOCK) != NULL) { \ - portENTER_CRITICAL((PLOCK)); \ - } \ - } while(0) - - -#define MULTI_HEAP_UNLOCK(PLOCK) do { \ - if ((PLOCK) != NULL) { \ - portEXIT_CRITICAL((PLOCK)); \ - } \ - } while(0) - -#define MULTI_HEAP_LOCK_INIT(PLOCK) do { \ - vPortCPUInitializeMutex((PLOCK)); \ - } while(0) - -#define MULTI_HEAP_LOCK_STATIC_INITIALIZER portMUX_INITIALIZER_UNLOCKED - -/* Not safe to use std i/o while in a portmux critical section, - can deadlock, so we use the ROM equivalent functions. */ - -#define MULTI_HEAP_PRINTF esp_rom_printf -#define MULTI_HEAP_STDERR_PRINTF(MSG, ...) esp_rom_printf(MSG, __VA_ARGS__) - -inline static void multi_heap_assert(bool condition, const char *format, int line, intptr_t address) -{ - /* Can't use libc assert() here as it calls printf() which can cause another malloc() for a newlib lock. - - Also, it's useful to be able to print the memory address where corruption was detected. - */ -#ifndef NDEBUG - if(!condition) { -#ifndef CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT - esp_rom_printf(format, line, address); -#endif // CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT - abort(); - } -#else // NDEBUG - (void) condition; -#endif // NDEBUG -} - -#define MULTI_HEAP_ASSERT(CONDITION, ADDRESS) \ - multi_heap_assert((CONDITION), "CORRUPT HEAP: multi_heap.c:%d detected at 0x%08x\n", \ - __LINE__, (intptr_t)(ADDRESS)) - -#ifdef CONFIG_HEAP_TASK_TRACKING -#include -#define MULTI_HEAP_BLOCK_OWNER TaskHandle_t task; -#define MULTI_HEAP_SET_BLOCK_OWNER(HEAD) (HEAD)->task = xTaskGetCurrentTaskHandle() -#define MULTI_HEAP_GET_BLOCK_OWNER(HEAD) ((HEAD)->task) -#else -#define MULTI_HEAP_BLOCK_OWNER -#define MULTI_HEAP_SET_BLOCK_OWNER(HEAD) -#define MULTI_HEAP_GET_BLOCK_OWNER(HEAD) (NULL) -#endif - -#else // MULTI_HEAP_FREERTOS - -#include - -#define MULTI_HEAP_PRINTF printf -#define MULTI_HEAP_STDERR_PRINTF(MSG, ...) fprintf(stderr, MSG, __VA_ARGS__) -#define MULTI_HEAP_LOCK(PLOCK) (void) (PLOCK) -#define MULTI_HEAP_UNLOCK(PLOCK) (void) (PLOCK) -#define MULTI_HEAP_LOCK_INIT(PLOCK) (void) (PLOCK) -#define MULTI_HEAP_LOCK_STATIC_INITIALIZER 0 - -#define MULTI_HEAP_ASSERT(CONDITION, ADDRESS) assert((CONDITION) && "Heap corrupt") - -#define MULTI_HEAP_BLOCK_OWNER -#define MULTI_HEAP_SET_BLOCK_OWNER(HEAD) -#define MULTI_HEAP_GET_BLOCK_OWNER(HEAD) (NULL) - -#endif // MULTI_HEAP_FREERTOS diff --git a/components/heap/multi_heap_poisoning.c b/components/heap/multi_heap_poisoning.c deleted file mode 100644 index ca27f3f2..00000000 --- a/components/heap/multi_heap_poisoning.c +++ /dev/null @@ -1,426 +0,0 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "multi_heap_internal.h" - -/* Note: Keep platform-specific parts in this header, this source - file should depend on libc only */ -#include "multi_heap_platform.h" - -/* Defines compile-time configuration macros */ -#include "multi_heap_config.h" - -#ifdef MULTI_HEAP_POISONING - -/* Alias MULTI_HEAP_POISONING_SLOW to SLOW for better readabilty */ -#ifdef SLOW -#error "external header has defined SLOW" -#endif -#ifdef MULTI_HEAP_POISONING_SLOW -#define SLOW 1 -#endif - -#define MALLOC_FILL_PATTERN 0xce -#define FREE_FILL_PATTERN 0xfe - -#define HEAD_CANARY_PATTERN 0xABBA1234 -#define TAIL_CANARY_PATTERN 0xBAAD5678 - - -#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1)) - -typedef struct { - uint32_t head_canary; - MULTI_HEAP_BLOCK_OWNER - size_t alloc_size; -} poison_head_t; - -typedef struct { - uint32_t tail_canary; -} poison_tail_t; - -#define POISON_OVERHEAD (sizeof(poison_head_t) + sizeof(poison_tail_t)) - -/* Given a "poisoned" region with pre-data header 'head', and actual data size 'alloc_size', fill in the head and tail - region checks. - - Returns the pointer to the actual usable data buffer (ie after 'head') -*/ -static uint8_t *poison_allocated_region(poison_head_t *head, size_t alloc_size) -{ - uint8_t *data = (uint8_t *)(&head[1]); /* start of data ie 'real' allocated buffer */ - poison_tail_t *tail = (poison_tail_t *)(data + alloc_size); - head->alloc_size = alloc_size; - head->head_canary = HEAD_CANARY_PATTERN; - MULTI_HEAP_SET_BLOCK_OWNER(head); - - uint32_t tail_canary = TAIL_CANARY_PATTERN; - if ((intptr_t)tail % sizeof(void *) == 0) { - tail->tail_canary = tail_canary; - } else { - /* unaligned tail_canary */ - memcpy(&tail->tail_canary, &tail_canary, sizeof(uint32_t)); - } - - return data; -} - -/* Given a pointer to some allocated data, check the head & tail poison structures (before & after it) that were - previously injected by poison_allocated_region(). - - Returns a pointer to the poison header structure, or NULL if the poison structures are corrupt. -*/ -static poison_head_t *verify_allocated_region(void *data, bool print_errors) -{ - poison_head_t *head = (poison_head_t *)((intptr_t)data - sizeof(poison_head_t)); - poison_tail_t *tail = (poison_tail_t *)((intptr_t)data + head->alloc_size); - - /* check if the beginning of the data was overwritten */ - if (head->head_canary != HEAD_CANARY_PATTERN) { - if (print_errors) { - MULTI_HEAP_STDERR_PRINTF("CORRUPT HEAP: Bad head at %p. Expected 0x%08x got 0x%08x\n", &head->head_canary, - HEAD_CANARY_PATTERN, head->head_canary); - } - return NULL; - } - - /* check if the end of the data was overrun */ - uint32_t canary; - if ((intptr_t)tail % sizeof(void *) == 0) { - canary = tail->tail_canary; - } else { - /* tail is unaligned */ - memcpy(&canary, &tail->tail_canary, sizeof(canary)); - } - if (canary != TAIL_CANARY_PATTERN) { - if (print_errors) { - MULTI_HEAP_STDERR_PRINTF("CORRUPT HEAP: Bad tail at %p. Expected 0x%08x got 0x%08x\n", &tail->tail_canary, - TAIL_CANARY_PATTERN, canary); - } - return NULL; - } - - return head; -} - -#ifdef SLOW -/* Go through a region that should have the specified fill byte 'pattern', - verify it. - - if expect_free is true, expect FREE_FILL_PATTERN otherwise MALLOC_FILL_PATTERN. - - if swap_pattern is true, swap patterns in the buffer (ie replace MALLOC_FILL_PATTERN with FREE_FILL_PATTERN, and vice versa.) - - Returns true if verification checks out. -*/ -static bool verify_fill_pattern(void *data, size_t size, bool print_errors, bool expect_free, bool swap_pattern) -{ - const uint32_t FREE_FILL_WORD = (FREE_FILL_PATTERN << 24) | (FREE_FILL_PATTERN << 16) | (FREE_FILL_PATTERN << 8) | FREE_FILL_PATTERN; - const uint32_t MALLOC_FILL_WORD = (MALLOC_FILL_PATTERN << 24) | (MALLOC_FILL_PATTERN << 16) | (MALLOC_FILL_PATTERN << 8) | MALLOC_FILL_PATTERN; - - const uint32_t EXPECT_WORD = expect_free ? FREE_FILL_WORD : MALLOC_FILL_WORD; - const uint32_t REPLACE_WORD = expect_free ? MALLOC_FILL_WORD : FREE_FILL_WORD; - bool valid = true; - - /* Use 4-byte operations as much as possible */ - if ((intptr_t)data % 4 == 0) { - uint32_t *p = data; - while (size >= 4) { - if (*p != EXPECT_WORD) { - if (print_errors) { - MULTI_HEAP_STDERR_PRINTF("CORRUPT HEAP: Invalid data at %p. Expected 0x%08x got 0x%08x\n", p, EXPECT_WORD, *p); - } - valid = false; -#ifndef NDEBUG - /* If an assertion is going to fail as soon as we're done verifying the pattern, leave the rest of the - buffer contents as-is for better post-mortem analysis - */ - swap_pattern = false; -#endif - } - if (swap_pattern) { - *p = REPLACE_WORD; - } - p++; - size -= 4; - } - data = p; - } - - uint8_t *p = data; - for (size_t i = 0; i < size; i++) { - if (p[i] != (uint8_t)EXPECT_WORD) { - if (print_errors) { - MULTI_HEAP_STDERR_PRINTF("CORRUPT HEAP: Invalid data at %p. Expected 0x%02x got 0x%02x\n", p, (uint8_t)EXPECT_WORD, *p); - } - valid = false; -#ifndef NDEBUG - swap_pattern = false; // same as above -#endif - } - if (swap_pattern) { - p[i] = (uint8_t)REPLACE_WORD; - } - } - return valid; -} -#endif - -void *multi_heap_aligned_alloc(multi_heap_handle_t heap, size_t size, size_t alignment) -{ - if (!size) { - return NULL; - } - - if (size > SIZE_MAX - POISON_OVERHEAD) { - return NULL; - } - - multi_heap_internal_lock(heap); - poison_head_t *head = multi_heap_aligned_alloc_impl_offs(heap, size + POISON_OVERHEAD, - alignment, sizeof(poison_head_t)); - uint8_t *data = NULL; - if (head != NULL) { - data = poison_allocated_region(head, size); -#ifdef SLOW - /* check everything we got back is FREE_FILL_PATTERN & swap for MALLOC_FILL_PATTERN */ - bool ret = verify_fill_pattern(data, size, true, true, true); - assert( ret ); -#endif - } else { - multi_heap_internal_unlock(heap); - return NULL; - } - - multi_heap_internal_unlock(heap); - - return data; -} - -void *multi_heap_malloc(multi_heap_handle_t heap, size_t size) -{ - if (!size) { - return NULL; - } - - if(size > SIZE_MAX - POISON_OVERHEAD) { - return NULL; - } - - multi_heap_internal_lock(heap); - poison_head_t *head = multi_heap_malloc_impl(heap, size + POISON_OVERHEAD); - uint8_t *data = NULL; - if (head != NULL) { - data = poison_allocated_region(head, size); -#ifdef SLOW - /* check everything we got back is FREE_FILL_PATTERN & swap for MALLOC_FILL_PATTERN */ - bool ret = verify_fill_pattern(data, size, true, true, true); - assert( ret ); -#endif - } - - multi_heap_internal_unlock(heap); - return data; -} - -void multi_heap_free(multi_heap_handle_t heap, void *p) -{ - if (p == NULL) { - return; - } - multi_heap_internal_lock(heap); - - poison_head_t *head = verify_allocated_region(p, true); - assert(head != NULL); - - #ifdef SLOW - /* replace everything with FREE_FILL_PATTERN, including the poison head/tail */ - memset(head, FREE_FILL_PATTERN, - head->alloc_size + POISON_OVERHEAD); - #endif - multi_heap_free_impl(heap, head); - - multi_heap_internal_unlock(heap); -} - -void multi_heap_aligned_free(multi_heap_handle_t heap, void *p) -{ - multi_heap_free(heap, p); -} - -void *multi_heap_realloc(multi_heap_handle_t heap, void *p, size_t size) -{ - poison_head_t *head = NULL; - poison_head_t *new_head; - void *result = NULL; - - if(size > SIZE_MAX - POISON_OVERHEAD) { - return NULL; - } - if (p == NULL) { - return multi_heap_malloc(heap, size); - } - if (size == 0) { - multi_heap_free(heap, p); - return NULL; - } - - /* p != NULL, size != 0 */ - head = verify_allocated_region(p, true); - assert(head != NULL); - - multi_heap_internal_lock(heap); - -#ifndef SLOW - new_head = multi_heap_realloc_impl(heap, head, size + POISON_OVERHEAD); - if (new_head != NULL) { - /* For "fast" poisoning, we only overwrite the head/tail of the new block so it's safe - to poison, so no problem doing this even if realloc resized in place. - */ - result = poison_allocated_region(new_head, size); - } -#else // SLOW - /* When slow poisoning is enabled, it becomes very fiddly to try and correctly fill memory when resizing in place - (where the buffer may be moved (including to an overlapping address with the old buffer), grown, or shrunk in - place.) - - For now we just malloc a new buffer, copy, and free. :| - - Note: If this ever changes, multi_heap defrag realloc test should be enabled. - */ - size_t orig_alloc_size = head->alloc_size; - - new_head = multi_heap_malloc_impl(heap, size + POISON_OVERHEAD); - if (new_head != NULL) { - result = poison_allocated_region(new_head, size); - memcpy(result, p, MIN(size, orig_alloc_size)); - multi_heap_free(heap, p); - } -#endif - - multi_heap_internal_unlock(heap); - - return result; -} - -void *multi_heap_get_block_address(multi_heap_block_handle_t block) -{ - char *head = multi_heap_get_block_address_impl(block); - return head + sizeof(poison_head_t); -} - -void *multi_heap_get_block_owner(multi_heap_block_handle_t block) -{ - return MULTI_HEAP_GET_BLOCK_OWNER((poison_head_t*)multi_heap_get_block_address_impl(block)); -} - -multi_heap_handle_t multi_heap_register(void *start, size_t size) -{ -#ifdef SLOW - if (start != NULL) { - memset(start, FREE_FILL_PATTERN, size); - } -#endif - return multi_heap_register_impl(start, size); -} - -static inline void subtract_poison_overhead(size_t *arg) { - if (*arg > POISON_OVERHEAD) { - *arg -= POISON_OVERHEAD; - } else { - *arg = 0; - } -} - -size_t multi_heap_get_allocated_size(multi_heap_handle_t heap, void *p) -{ - poison_head_t *head = verify_allocated_region(p, true); - assert(head != NULL); - size_t result = multi_heap_get_allocated_size_impl(heap, head); - return result; -} - -void multi_heap_get_info(multi_heap_handle_t heap, multi_heap_info_t *info) -{ - multi_heap_get_info_impl(heap, info); - /* don't count the heap poison head & tail overhead in the allocated bytes size */ - info->total_allocated_bytes -= info->allocated_blocks * POISON_OVERHEAD; - /* trim largest_free_block to account for poison overhead */ - subtract_poison_overhead(&info->largest_free_block); - /* similarly, trim total_free_bytes so there's no suggestion that - a block this big may be available. */ - subtract_poison_overhead(&info->total_free_bytes); - subtract_poison_overhead(&info->minimum_free_bytes); -} - -size_t multi_heap_free_size(multi_heap_handle_t heap) -{ - size_t r = multi_heap_free_size_impl(heap); - subtract_poison_overhead(&r); - return r; -} - -size_t multi_heap_minimum_free_size(multi_heap_handle_t heap) -{ - size_t r = multi_heap_minimum_free_size_impl(heap); - subtract_poison_overhead(&r); - return r; -} - -/* Internal hooks used by multi_heap to manage poisoning, while keeping some modularity */ - -bool multi_heap_internal_check_block_poisoning(void *start, size_t size, bool is_free, bool print_errors) -{ - if (is_free) { -#ifdef SLOW - return verify_fill_pattern(start, size, print_errors, true, false); -#else - return true; /* can only verify empty blocks in SLOW mode */ -#endif - } else { - void *data = (void *)((intptr_t)start + sizeof(poison_head_t)); - poison_head_t *head = verify_allocated_region(data, print_errors); - if (head != NULL && head->alloc_size > size - POISON_OVERHEAD) { - /* block can be bigger than alloc_size, for reasons of alignment & fragmentation, - but block can never be smaller than head->alloc_size... */ - if (print_errors) { - MULTI_HEAP_STDERR_PRINTF("CORRUPT HEAP: Size at %p expected <=0x%08x got 0x%08x\n", &head->alloc_size, - size - POISON_OVERHEAD, head->alloc_size); - } - return false; - } - return head != NULL; - } -} - -void multi_heap_internal_poison_fill_region(void *start, size_t size, bool is_free) -{ - memset(start, is_free ? FREE_FILL_PATTERN : MALLOC_FILL_PATTERN, size); -} - -#else // !MULTI_HEAP_POISONING - -#ifdef MULTI_HEAP_POISONING_SLOW -#error "MULTI_HEAP_POISONING_SLOW requires MULTI_HEAP_POISONING" -#endif - -#endif // MULTI_HEAP_POISONING diff --git a/components/heap/test/CMakeLists.txt b/components/heap/test/CMakeLists.txt deleted file mode 100644 index 6da69a0c..00000000 --- a/components/heap/test/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -idf_component_register(SRC_DIRS "." - PRIV_INCLUDE_DIRS "." - PRIV_REQUIRES cmock test_utils heap) diff --git a/components/heap/test/component.mk b/components/heap/test/component.mk deleted file mode 100644 index 5dd172bd..00000000 --- a/components/heap/test/component.mk +++ /dev/null @@ -1,5 +0,0 @@ -# -#Component Makefile -# - -COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive diff --git a/components/heap/test/test_aligned_alloc_caps.c b/components/heap/test/test_aligned_alloc_caps.c deleted file mode 100644 index e6648815..00000000 --- a/components/heap/test/test_aligned_alloc_caps.c +++ /dev/null @@ -1,147 +0,0 @@ -/* - Tests for the capabilities-based memory allocator. -*/ - -#include -#include -#include "unity.h" -#include "esp_attr.h" -#include "esp_heap_caps.h" -#include "esp_spi_flash.h" -#include -#include -#include -#include - -TEST_CASE("Capabilities aligned allocator test", "[heap]") -{ - uint32_t alignments = 0; - - printf("[ALIGNED_ALLOC] Allocating from default CAP: \n"); - - for(;alignments <= 1024; alignments++) { - uint8_t *buf = (uint8_t *)memalign(alignments, (alignments + 137)); - if(((alignments & (alignments - 1)) != 0) || (!alignments)) { - TEST_ASSERT( buf == NULL ); - //printf("[ALIGNED_ALLOC] alignment: %u is not a power of two, don't allow allocation \n", aligments); - } else { - TEST_ASSERT( buf != NULL ); - printf("[ALIGNED_ALLOC] alignment required: %u \n", alignments); - printf("[ALIGNED_ALLOC] address of allocated memory: %p \n\n", (void *)buf); - //Address of obtained block must be aligned with selected value - TEST_ASSERT(((intptr_t)buf & (alignments - 1)) == 0); - - //Write some data, if it corrupts memory probably the heap - //canary verification will fail: - memset(buf, 0xA5, (alignments + 137)); - - free(buf); - } - } - - //Alloc from a non permitted area: - uint32_t *not_permitted_buf = (uint32_t *)heap_caps_aligned_alloc(alignments, (alignments + 137), MALLOC_CAP_EXEC | MALLOC_CAP_32BIT); - TEST_ASSERT( not_permitted_buf == NULL ); - -#if CONFIG_ESP32_SPIRAM_SUPPORT || CONFIG_ESP32S2_SPIRAM_SUPPORT - alignments = 0; - printf("[ALIGNED_ALLOC] Allocating from external memory: \n"); - - for(;alignments <= 1024 * 1024; alignments++) { - //Now try to take aligned memory from IRAM: - uint8_t *buf = (uint8_t *)heap_caps_aligned_alloc(alignments, 10*1024, MALLOC_CAP_SPIRAM); - if(((alignments & (alignments - 1)) != 0) || (!alignments)) { - TEST_ASSERT( buf == NULL ); - //printf("[ALIGNED_ALLOC] alignment: %u is not a power of two, don't allow allocation \n", aligments); - } else { - TEST_ASSERT( buf != NULL ); - printf("[ALIGNED_ALLOC] alignment required: %u \n", alignments); - printf("[ALIGNED_ALLOC] address of allocated memory: %p \n\n", (void *)buf); - //Address of obtained block must be aligned with selected value - TEST_ASSERT(((intptr_t)buf & (alignments - 1)) == 0); - - //Write some data, if it corrupts memory probably the heap - //canary verification will fail: - memset(buf, 0xA5, (10*1024)); - heap_caps_free(buf); - } - } -#endif - -} - -TEST_CASE("Capabilities aligned calloc test", "[heap]") -{ - uint32_t alignments = 0; - - printf("[ALIGNED_ALLOC] Allocating from default CAP: \n"); - - for(;alignments <= 1024; alignments++) { - uint8_t *buf = (uint8_t *)heap_caps_aligned_calloc(alignments, 1, (alignments + 137), MALLOC_CAP_DEFAULT); - if(((alignments & (alignments - 1)) != 0) || (!alignments)) { - TEST_ASSERT( buf == NULL ); - //printf("[ALIGNED_ALLOC] alignment: %u is not a power of two, don't allow allocation \n", aligments); - } else { - TEST_ASSERT( buf != NULL ); - printf("[ALIGNED_ALLOC] alignment required: %u \n", alignments); - printf("[ALIGNED_ALLOC] address of allocated memory: %p \n\n", (void *)buf); - //Address of obtained block must be aligned with selected value - TEST_ASSERT(((intptr_t)buf & (alignments - 1)) == 0); - - //Write some data, if it corrupts memory probably the heap - //canary verification will fail: - memset(buf, 0xA5, (alignments + 137)); - - heap_caps_free(buf); - } - } - - //Check if memory is initialized with zero: - uint8_t byte_array[1024]; - memset(&byte_array, 0, sizeof(byte_array)); - uint8_t *buf = (uint8_t *)heap_caps_aligned_calloc(1024, 1, 1024, MALLOC_CAP_DEFAULT); - TEST_ASSERT(memcmp(byte_array, buf, sizeof(byte_array)) == 0); - heap_caps_free(buf); - - //Same size, but different chunk: - buf = (uint8_t *)heap_caps_aligned_calloc(1024, 1024, 1, MALLOC_CAP_DEFAULT); - TEST_ASSERT(memcmp(byte_array, buf, sizeof(byte_array)) == 0); - heap_caps_free(buf); - - //Alloc from a non permitted area: - uint32_t *not_permitted_buf = (uint32_t *)heap_caps_aligned_calloc(alignments, 1, (alignments + 137), MALLOC_CAP_32BIT); - TEST_ASSERT( not_permitted_buf == NULL ); - -#if CONFIG_ESP32_SPIRAM_SUPPORT || CONFIG_ESP32S2_SPIRAM_SUPPORT - alignments = 0; - printf("[ALIGNED_ALLOC] Allocating from external memory: \n"); - - for(;alignments <= 1024 * 1024; alignments++) { - //Now try to take aligned memory from IRAM: - uint8_t *buf = (uint8_t *)(uint8_t *)heap_caps_aligned_calloc(alignments, 1, 10*1024, MALLOC_CAP_SPIRAM); - if(((alignments & (alignments - 1)) != 0) || (!alignments)) { - TEST_ASSERT( buf == NULL ); - //printf("[ALIGNED_ALLOC] alignment: %u is not a power of two, don't allow allocation \n", aligments); - } else { - TEST_ASSERT( buf != NULL ); - printf("[ALIGNED_ALLOC] alignment required: %u \n", alignments); - printf("[ALIGNED_ALLOC] address of allocated memory: %p \n\n", (void *)buf); - //Address of obtained block must be aligned with selected value - TEST_ASSERT(((intptr_t)buf & (alignments - 1)) == 0); - - //Write some data, if it corrupts memory probably the heap - //canary verification will fail: - memset(buf, 0xA5, (10*1024)); - heap_caps_free(buf); - } - } -#endif - -} - -TEST_CASE("aligned_alloc(0) should return a NULL pointer", "[heap]") -{ - void *p; - p = heap_caps_aligned_alloc(32, 0, MALLOC_CAP_DEFAULT); - TEST_ASSERT(p == NULL); -} diff --git a/components/heap/test/test_allocator_timings.c b/components/heap/test/test_allocator_timings.c deleted file mode 100644 index 0e130c95..00000000 --- a/components/heap/test/test_allocator_timings.c +++ /dev/null @@ -1,108 +0,0 @@ -#include "freertos/FreeRTOS.h" -#include -#include -#include "unity.h" -#include "esp_attr.h" -#include "esp_heap_caps.h" -#include -#include -#include -#include - -//This test only makes sense with poisoning disabled (light or comprehensive) -#if !defined(CONFIG_HEAP_POISONING_COMPREHENSIVE) && !defined(CONFIG_HEAP_POISONING_LIGHT) - -#define NUM_POINTERS 128 -#define ITERATIONS 10000 - -TEST_CASE("Heap many random allocations timings", "[heap]") -{ - void *p[NUM_POINTERS] = { 0 }; - size_t s[NUM_POINTERS] = { 0 }; - - uint32_t cycles_before; - uint64_t alloc_time_average = 0; - uint64_t free_time_average = 0; - uint64_t realloc_time_average = 0; - - for (int i = 0; i < ITERATIONS; i++) { - uint8_t n = esp_random() % NUM_POINTERS; - - if (esp_random() % 4 == 0) { - /* 1 in 4 iterations, try to realloc the buffer instead - of using malloc/free - */ - size_t new_size = esp_random() % 1024; - - cycles_before = portGET_RUN_TIME_COUNTER_VALUE(); - void *new_p = heap_caps_realloc(p[n], new_size, MALLOC_CAP_DEFAULT); - realloc_time_average = portGET_RUN_TIME_COUNTER_VALUE() - cycles_before; - - printf("realloc %p -> %p (%zu -> %zu) time spent cycles: %lld \n", p[n], new_p, s[n], new_size, realloc_time_average); - heap_caps_check_integrity(MALLOC_CAP_DEFAULT, true); - if (new_size == 0 || new_p != NULL) { - p[n] = new_p; - s[n] = new_size; - if (new_size > 0) { - memset(p[n], n, new_size); - } - } - continue; - } - - if (p[n] != NULL) { - if (s[n] > 0) { - /* Verify pre-existing contents of p[n] */ - uint8_t compare[s[n]]; - memset(compare, n, s[n]); - TEST_ASSERT(( memcmp(compare, p[n], s[n]) == 0 )); - } - TEST_ASSERT(heap_caps_check_integrity(MALLOC_CAP_DEFAULT, true)); - - cycles_before = portGET_RUN_TIME_COUNTER_VALUE(); - heap_caps_free(p[n]); - free_time_average = portGET_RUN_TIME_COUNTER_VALUE() - cycles_before; - - printf("freed %p (%zu) time spent cycles: %lld\n", p[n], s[n], free_time_average); - - if (!heap_caps_check_integrity(MALLOC_CAP_DEFAULT, true)) { - printf("FAILED iteration %d after freeing %p\n", i, p[n]); - heap_caps_dump(MALLOC_CAP_DEFAULT); - TEST_ASSERT(0); - } - } - - s[n] = rand() % 1024; - heap_caps_check_integrity(MALLOC_CAP_DEFAULT, true); - cycles_before = portGET_RUN_TIME_COUNTER_VALUE(); - p[n] = heap_caps_malloc(s[n], MALLOC_CAP_DEFAULT); - alloc_time_average = portGET_RUN_TIME_COUNTER_VALUE() - cycles_before; - - printf("malloc %p (%zu) time spent cycles: %lld \n", p[n], s[n], alloc_time_average); - - if (!heap_caps_check_integrity(MALLOC_CAP_DEFAULT, true)) { - printf("FAILED iteration %d after mallocing %p (%zu bytes)\n", i, p[n], s[n]); - heap_caps_dump(MALLOC_CAP_DEFAULT); - TEST_ASSERT(0); - } - - if (p[n] != NULL) { - memset(p[n], n, s[n]); - } - } - - for (int i = 0; i < NUM_POINTERS; i++) { - cycles_before = portGET_RUN_TIME_COUNTER_VALUE(); - heap_caps_free( p[i]); - free_time_average = portGET_RUN_TIME_COUNTER_VALUE() - cycles_before; - - if (!heap_caps_check_integrity(MALLOC_CAP_DEFAULT, true)) { - printf("FAILED during cleanup after freeing %p\n", p[i]); - heap_caps_dump(MALLOC_CAP_DEFAULT); - TEST_ASSERT(0); - } - } - - TEST_ASSERT(heap_caps_check_integrity(MALLOC_CAP_DEFAULT, true)); -} -#endif diff --git a/components/heap/test/test_diram.c b/components/heap/test/test_diram.c deleted file mode 100644 index 17966352..00000000 --- a/components/heap/test/test_diram.c +++ /dev/null @@ -1,74 +0,0 @@ -/* - Tests for D/IRAM support in heap capability allocator -*/ - -#include -#include -#include "unity.h" -#include "esp_heap_caps.h" -#include "soc/soc_memory_layout.h" - -#define ALLOC_SZ 1024 - -static void *malloc_block_diram(uint32_t caps) -{ - void *attempts[256] = { 0 }; // Allocate up to 256 ALLOC_SZ blocks to exhaust all non-D/IRAM memory temporarily - int count = 0; - void *result; - - while(count < sizeof(attempts)/sizeof(void *)) { - result = heap_caps_malloc(ALLOC_SZ, caps); - TEST_ASSERT_NOT_NULL_MESSAGE(result, "not enough free heap to perform test"); - - if (esp_ptr_in_diram_dram(result) || esp_ptr_in_diram_iram(result)) { - break; - } - - attempts[count] = result; - result = NULL; - count++; - } - - for (int i = 0; i < count; i++) { - free(attempts[i]); - } - - TEST_ASSERT_NOT_NULL_MESSAGE(result, "not enough D/IRAM memory is free"); - return result; -} - -TEST_CASE("Allocate D/IRAM as DRAM", "[heap]") -{ - uint32_t *dram = malloc_block_diram(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); - - for (int i = 0; i < ALLOC_SZ / sizeof(uint32_t); i++) { - uint32_t v = i + 0xAAAA; - dram[i] = v; - volatile uint32_t *iram = esp_ptr_diram_dram_to_iram(dram + i); - TEST_ASSERT_EQUAL(v, dram[i]); - TEST_ASSERT_EQUAL(v, *iram); - *iram = UINT32_MAX; - TEST_ASSERT_EQUAL(UINT32_MAX, *iram); - TEST_ASSERT_EQUAL(UINT32_MAX, dram[i]); - } - - free(dram); -} - -TEST_CASE("Allocate D/IRAM as IRAM", "[heap]") -{ - uint32_t *iram = malloc_block_diram(MALLOC_CAP_EXEC); - - for (int i = 0; i < ALLOC_SZ / sizeof(uint32_t); i++) { - uint32_t v = i + 0xEEE; - iram[i] = v; - volatile uint32_t *dram = esp_ptr_diram_iram_to_dram(iram + i); - TEST_ASSERT_EQUAL_HEX32(v, iram[i]); - TEST_ASSERT_EQUAL_HEX32(v, *dram); - *dram = UINT32_MAX; - TEST_ASSERT_EQUAL_HEX32(UINT32_MAX, *dram); - TEST_ASSERT_EQUAL_HEX32(UINT32_MAX, iram[i]); - } - - free(iram); -} diff --git a/components/heap/test/test_heap_trace.c b/components/heap/test/test_heap_trace.c deleted file mode 100644 index 909aa31b..00000000 --- a/components/heap/test/test_heap_trace.c +++ /dev/null @@ -1,164 +0,0 @@ -/* - Generic test for heap tracing support - - Only compiled in if CONFIG_HEAP_TRACING is set -*/ - -#include -#include -#include -#include -#include "sdkconfig.h" -#include "unity.h" - -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" - -#ifdef CONFIG_HEAP_TRACING -// only compile in heap tracing tests if tracing is enabled - -#include "esp_heap_trace.h" - -TEST_CASE("heap trace leak check", "[heap]") -{ - heap_trace_record_t recs[8]; - heap_trace_init_standalone(recs, 8); - - printf("Leak check test\n"); // Print something before trace starts, or stdout allocations skew total counts - fflush(stdout); - - heap_trace_start(HEAP_TRACE_LEAKS); - - void *a = malloc(64); - memset(a, '3', 64); - - void *b = malloc(96); - memset(b, '4', 11); - - printf("a.address %p vs %p b.address %p vs %p\n", a, recs[0].address, b, recs[1].address); - - heap_trace_dump(); - TEST_ASSERT_EQUAL(2, heap_trace_get_count()); - - heap_trace_record_t trace_a, trace_b; - heap_trace_get(0, &trace_a); - heap_trace_get(1, &trace_b); - - printf("trace_a.address %p trace_bb.address %p\n", trace_a.address, trace_b.address); - - TEST_ASSERT_EQUAL_PTR(a, trace_a.address); - TEST_ASSERT_EQUAL_PTR(b, trace_b.address); - - TEST_ASSERT_EQUAL_PTR(recs[0].address, trace_a.address); - TEST_ASSERT_EQUAL_PTR(recs[1].address, trace_b.address); - - free(a); - - TEST_ASSERT_EQUAL(1, heap_trace_get_count()); - - heap_trace_get(0, &trace_b); - TEST_ASSERT_EQUAL_PTR(b, trace_b.address); - - /* buffer deletes trace_a when freed, - so trace_b at head of buffer */ - TEST_ASSERT_EQUAL_PTR(recs[0].address, trace_b.address); - - heap_trace_stop(); -} - -TEST_CASE("heap trace wrapped buffer check", "[heap]") -{ - const size_t N = 8; - heap_trace_record_t recs[N]; - heap_trace_init_standalone(recs, N); - - heap_trace_start(HEAP_TRACE_LEAKS); - - void *ptrs[N+1]; - for (int i = 0; i < N+1; i++) { - ptrs[i] = malloc(i*3); - } - - // becuase other mallocs happen as part of this control flow, - // we can't guarantee N entries of ptrs[] are in the heap check buffer. - // but we should guarantee at least the last one is - bool saw_last_ptr = false; - for (int i = 0; i < N; i++) { - heap_trace_record_t rec; - heap_trace_get(i, &rec); - if (rec.address == ptrs[N-1]) { - saw_last_ptr = true; - } - } - TEST_ASSERT(saw_last_ptr); - - void *other = malloc(6); - - heap_trace_dump(); - - for (int i = 0; i < N+1; i++) { - free(ptrs[i]); - } - - heap_trace_dump(); - - bool saw_other = false; - - for (int i = 0; i < heap_trace_get_count(); i++) { - heap_trace_record_t rec; - heap_trace_get(i, &rec); - - // none of ptr[]s should be in the heap trace any more - for (int j = 0; j < N+1; j++) { - TEST_ASSERT_NOT_EQUAL(ptrs[j], rec.address); - } - if (rec.address == other) { - saw_other = true; - } - } - - // 'other' pointer should be somewhere in the leak dump - TEST_ASSERT(saw_other); - - heap_trace_stop(); -} - -static void print_floats_task(void *ignore) -{ - heap_trace_start(HEAP_TRACE_ALL); - char buf[16] = { }; - volatile float f = 12.3456; - sprintf(buf, "%.4f", f); - TEST_ASSERT_EQUAL_STRING("12.3456", buf); - heap_trace_stop(); - - vTaskDelete(NULL); -} - -TEST_CASE("can trace allocations made by newlib", "[heap]") -{ - const size_t N = 8; - heap_trace_record_t recs[N]; - heap_trace_init_standalone(recs, N); - - /* Verifying that newlib code performs an allocation is very fiddly: - - - Printing a float allocates data associated with the task, but only the - first time a task prints a float of this length. So we do it in a one-shot task - to avoid possibility it already happened. - - - If newlib is updated this test may start failing if the printf() implementation - changes. (This version passes for both nano & regular formatting in newlib 2.2.0) - - - We also do the tracing in the task so we only capture things directly related to it. - */ - - xTaskCreate(print_floats_task, "print_float", 4096, NULL, 5, NULL); - vTaskDelay(10); - - /* has to be at least a few as newlib allocates via multiple different function calls */ - TEST_ASSERT(heap_trace_get_count() > 3); -} - - -#endif diff --git a/components/heap/test/test_leak.c b/components/heap/test/test_leak.c deleted file mode 100644 index 0144cd93..00000000 --- a/components/heap/test/test_leak.c +++ /dev/null @@ -1,60 +0,0 @@ -/* - Tests for a leak tag -*/ - -#include -#include "unity.h" -#include "esp_heap_caps_init.h" -#include "esp_system.h" -#include - - -static char* check_calloc(int size) -{ - char *arr = calloc(size, sizeof(char)); - TEST_ASSERT_NOT_NULL(arr); - return arr; -} - -TEST_CASE("Check for leaks (no leak)", "[heap]") -{ - char *arr = check_calloc(1000); - free(arr); -} - -TEST_CASE("Check for leaks (leak)", "[heap][ignore]") -{ - check_calloc(1000); -} - -TEST_CASE("Not check for leaks", "[heap][leaks]") -{ - check_calloc(1000); -} - -TEST_CASE("Set a leak level = 7016", "[heap][leaks=7016]") -{ - check_calloc(7000); -} - -static void test_fn(void) -{ - check_calloc(1000); -} - -TEST_CASE_MULTIPLE_STAGES("Not check for leaks in MULTIPLE_STAGES mode", "[heap][leaks]", test_fn, test_fn, test_fn); - -TEST_CASE_MULTIPLE_STAGES("Check for leaks in MULTIPLE_STAGES mode (leak)", "[heap][ignore]", test_fn, test_fn, test_fn); - -static void test_fn2(void) -{ - check_calloc(1000); - esp_restart(); -} - -static void test_fn3(void) -{ - check_calloc(1000); -} - -TEST_CASE_MULTIPLE_STAGES("Check for leaks in MULTIPLE_STAGES mode (manual reset)", "[heap][leaks][reset=SW_CPU_RESET, SW_CPU_RESET]", test_fn2, test_fn2, test_fn3); diff --git a/components/heap/test/test_malloc.c b/components/heap/test/test_malloc.c deleted file mode 100644 index 6f4bbd87..00000000 --- a/components/heap/test/test_malloc.c +++ /dev/null @@ -1,134 +0,0 @@ -/* - Generic test for malloc/free -*/ - -#include -#include - -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/semphr.h" -#include "freertos/queue.h" -#include "unity.h" -#include "esp_heap_caps.h" - -#include "sdkconfig.h" - - -static int **allocatedMem; -static int noAllocated; - - -static int tryAllocMem(void) { - int i, j; - const int allocateMaxK=1024*5; //try to allocate a max of 5MiB - - allocatedMem=malloc(sizeof(int *)*allocateMaxK); - if (!allocatedMem) return 0; - - for (i=0; i 1024) -TEST_CASE("Check if reserved DMA pool still can allocate even when malloc()'ed memory is exhausted", "[heap]") -{ - char** dmaMem=malloc(sizeof(char*)*512); - assert(dmaMem); - int m=tryAllocMem(); - int i=0; - for (i=0; i<512; i++) { - dmaMem[i]=heap_caps_malloc(1024, MALLOC_CAP_DMA); - if (dmaMem[i]==NULL) break; - } - for (int j=0; j -#include -#include "unity.h" -#include "esp_attr.h" -#include "esp_heap_caps.h" -#include "esp_spi_flash.h" -#include -#include - -#ifndef CONFIG_ESP_SYSTEM_MEMPROT_FEATURE -TEST_CASE("Capabilities allocator test", "[heap]") -{ - char *m1, *m2[10]; - int x; - size_t free8start, free32start, free8, free32; - - /* It's important we printf() something before we take the empty heap sizes, - as the first printf() in a task allocates heap resources... */ - printf("Testing capabilities allocator...\n"); - - free8start = heap_caps_get_free_size(MALLOC_CAP_8BIT); - free32start = heap_caps_get_free_size(MALLOC_CAP_32BIT); - printf("Free 8bit-capable memory (start): %dK, 32-bit capable memory %dK\n", free8start, free32start); - TEST_ASSERT(free32start >= free8start); - - printf("Allocating 10K of 8-bit capable RAM\n"); - m1= heap_caps_malloc(10*1024, MALLOC_CAP_8BIT); - printf("--> %p\n", m1); - free8 = heap_caps_get_free_size(MALLOC_CAP_8BIT); - free32 = heap_caps_get_free_size(MALLOC_CAP_32BIT); - printf("Free 8bit-capable memory (both reduced): %dK, 32-bit capable memory %dK\n", free8, free32); - //Both should have gone down by 10K; 8bit capable ram is also 32-bit capable - TEST_ASSERT(free8<=(free8start-10*1024)); - TEST_ASSERT(free32<=(free32start-10*1024)); - //Assume we got DRAM back - TEST_ASSERT((((int)m1)&0xFF000000)==0x3F000000); - free(m1); - - //The goal here is to allocate from IRAM. Since there is no external IRAM (yet) - //the following gives size of IRAM-only (not D/IRAM) memory. - size_t free_iram = heap_caps_get_free_size(MALLOC_CAP_INTERNAL) - - heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); - size_t alloc32 = MIN(free_iram / 2, 10*1024) & (~3); - if(free_iram) { - printf("Freeing; allocating %u bytes of 32K-capable RAM\n", alloc32); - m1 = heap_caps_malloc(alloc32, MALLOC_CAP_32BIT); - printf("--> %p\n", m1); - //Check that we got IRAM back - TEST_ASSERT((((int)m1)&0xFF000000)==0x40000000); - free8 = heap_caps_get_free_size(MALLOC_CAP_8BIT); - free32 = heap_caps_get_free_size(MALLOC_CAP_32BIT); - printf("Free 8bit-capable memory (after 32-bit): %dK, 32-bit capable memory %dK\n", free8, free32); - //Only 32-bit should have gone down by alloc32: 32-bit isn't necessarily 8bit capable - TEST_ASSERT(free32<=(free32start-alloc32)); - TEST_ASSERT(free8==free8start); - free(m1); - } else { - printf("This platform has no 32-bit only capable RAM, jumping to next test \n"); - } - - printf("Allocating impossible caps\n"); - m1= heap_caps_malloc(10*1024, MALLOC_CAP_8BIT|MALLOC_CAP_EXEC); - printf("--> %p\n", m1); - TEST_ASSERT(m1==NULL); - - if(free_iram) { - printf("Testing changeover iram -> dram"); - // priorities will exhaust IRAM first, then start allocating from DRAM - for (x=0; x<10; x++) { - m2[x]= heap_caps_malloc(alloc32, MALLOC_CAP_32BIT); - printf("--> %p\n", m2[x]); - } - TEST_ASSERT((((int)m2[0])&0xFF000000)==0x40000000); - TEST_ASSERT((((int)m2[9])&0xFF000000)==0x3F000000); - - } else { - printf("This platform has no IRAM-only so changeover will never occur, jumping to next test\n"); - } - - printf("Test if allocating executable code still gives IRAM, even with dedicated IRAM region depleted\n"); - if(free_iram) { - // (the allocation should come from D/IRAM) - free_iram = heap_caps_get_free_size(MALLOC_CAP_EXEC); - m1= heap_caps_malloc(MIN(free_iram / 2, 10*1024), MALLOC_CAP_EXEC); - printf("--> %p\n", m1); - TEST_ASSERT((((int)m1)&0xFF000000)==0x40000000); - for (x=0; x<10; x++) free(m2[x]); - - } else { - // (the allocation should come from D/IRAM) - free_iram = heap_caps_get_free_size(MALLOC_CAP_EXEC); - m1= heap_caps_malloc(MIN(free_iram / 2, 10*1024), MALLOC_CAP_EXEC); - printf("--> %p\n", m1); - TEST_ASSERT((((int)m1)&0xFF000000)==0x40000000); - } - - free(m1); - printf("Done.\n"); -} -#endif - -#ifdef CONFIG_ESP32_IRAM_AS_8BIT_ACCESSIBLE_MEMORY -TEST_CASE("IRAM_8BIT capability test", "[heap]") -{ - uint8_t *ptr; - size_t free_size, free_size32, largest_free_size; - - /* need to print something as first printf allocates some heap */ - printf("IRAM_8BIT capability test\n"); - - free_size = heap_caps_get_free_size(MALLOC_CAP_IRAM_8BIT); - free_size32 = heap_caps_get_free_size(MALLOC_CAP_32BIT); - - largest_free_size = heap_caps_get_largest_free_block(MALLOC_CAP_IRAM_8BIT); - - ptr = heap_caps_malloc(largest_free_size, MALLOC_CAP_IRAM_8BIT); - - TEST_ASSERT((((int)ptr)&0xFF000000)==0x40000000); - - TEST_ASSERT(heap_caps_get_free_size(MALLOC_CAP_IRAM_8BIT) == (free_size - heap_caps_get_allocated_size(ptr))); - TEST_ASSERT(heap_caps_get_free_size(MALLOC_CAP_32BIT) == (free_size32 - heap_caps_get_allocated_size(ptr))); - - free(ptr); -} -#endif - -TEST_CASE("heap_caps metadata test", "[heap]") -{ - /* need to print something as first printf allocates some heap */ - printf("heap_caps metadata test\n"); - heap_caps_print_heap_info(MALLOC_CAP_8BIT); - - multi_heap_info_t original; - heap_caps_get_info(&original, MALLOC_CAP_8BIT); - - void *b = heap_caps_malloc(original.largest_free_block, MALLOC_CAP_8BIT); - TEST_ASSERT_NOT_NULL(b); - - printf("After allocating %d bytes:\n", original.largest_free_block); - heap_caps_print_heap_info(MALLOC_CAP_8BIT); - - multi_heap_info_t after; - heap_caps_get_info(&after, MALLOC_CAP_8BIT); - TEST_ASSERT(after.largest_free_block <= original.largest_free_block); - TEST_ASSERT(after.total_free_bytes <= original.total_free_bytes); - - free(b); - heap_caps_get_info(&after, MALLOC_CAP_8BIT); - - printf("\n\n After test, heap status:\n"); - heap_caps_print_heap_info(MALLOC_CAP_8BIT); - - /* Allow some leeway here, because LWIP sometimes allocates up to 144 bytes in the background - as part of timer management. - */ - TEST_ASSERT_INT32_WITHIN(200, after.total_free_bytes, original.total_free_bytes); - TEST_ASSERT_INT32_WITHIN(200, after.largest_free_block, original.largest_free_block); - TEST_ASSERT(after.minimum_free_bytes < original.total_free_bytes); -} - -/* Small function runs from IRAM to check that malloc/free/realloc - all work OK when cache is disabled... -*/ -static IRAM_ATTR __attribute__((noinline)) bool iram_malloc_test(void) -{ - spi_flash_guard_get()->start(); // Disables flash cache - - bool result = true; - void *x = heap_caps_malloc(64, MALLOC_CAP_EXEC); - result = result && (x != NULL); - void *y = heap_caps_realloc(x, 32, MALLOC_CAP_EXEC); - result = result && (y != NULL); - heap_caps_free(y); - - spi_flash_guard_get()->end(); // Re-enables flash cache - - return result; -} - - -TEST_CASE("heap_caps_xxx functions work with flash cache disabled", "[heap]") -{ - TEST_ASSERT( iram_malloc_test() ); -} - -#ifdef CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS -TEST_CASE("When enabled, allocation operation failure generates an abort", "[heap][reset=abort,SW_CPU_RESET]") -{ - const size_t stupid_allocation_size = (128 * 1024 * 1024); - void *ptr = heap_caps_malloc(stupid_allocation_size, MALLOC_CAP_DEFAULT); - (void)ptr; - TEST_FAIL_MESSAGE("should not be reached"); -} -#endif - -static bool called_user_failed_hook = false; - -void heap_caps_alloc_failed_hook(size_t requested_size, uint32_t caps, const char *function_name) -{ - printf("%s was called but failed to allocate %d bytes with 0x%X capabilities. \n",function_name, requested_size, caps); - called_user_failed_hook = true; -} - -TEST_CASE("user provided alloc failed hook must be called when allocation fails", "[heap]") -{ - TEST_ASSERT(heap_caps_register_failed_alloc_callback(heap_caps_alloc_failed_hook) == ESP_OK); - - const size_t stupid_allocation_size = (128 * 1024 * 1024); - void *ptr = heap_caps_malloc(stupid_allocation_size, MALLOC_CAP_DEFAULT); - TEST_ASSERT(called_user_failed_hook != false); - - called_user_failed_hook = false; - ptr = heap_caps_realloc(ptr, stupid_allocation_size, MALLOC_CAP_DEFAULT); - TEST_ASSERT(called_user_failed_hook != false); - - called_user_failed_hook = false; - ptr = heap_caps_aligned_alloc(0x200, stupid_allocation_size, MALLOC_CAP_DEFAULT); - TEST_ASSERT(called_user_failed_hook != false); - - (void)ptr; -} - -TEST_CASE("allocation with invalid capability should also trigger the alloc failed hook", "[heap]") -{ - const size_t allocation_size = 64; - const uint32_t invalid_cap = MALLOC_CAP_INVALID; - - TEST_ASSERT(heap_caps_register_failed_alloc_callback(heap_caps_alloc_failed_hook) == ESP_OK); - - called_user_failed_hook = false; - void *ptr = heap_caps_malloc(allocation_size, invalid_cap); - TEST_ASSERT(called_user_failed_hook != false); - - called_user_failed_hook = false; - ptr = heap_caps_realloc(ptr, allocation_size, invalid_cap); - TEST_ASSERT(called_user_failed_hook != false); - - called_user_failed_hook = false; - ptr = heap_caps_aligned_alloc(0x200, allocation_size, invalid_cap); - TEST_ASSERT(called_user_failed_hook != false); - - (void)ptr; -} diff --git a/components/heap/test/test_realloc.c b/components/heap/test/test_realloc.c deleted file mode 100644 index 60a7749f..00000000 --- a/components/heap/test/test_realloc.c +++ /dev/null @@ -1,67 +0,0 @@ -/* - Generic test for realloc -*/ - -#include -#include -#include "unity.h" -#include "sdkconfig.h" -#include "esp_heap_caps.h" -#include "soc/soc_memory_layout.h" - - -#ifndef CONFIG_HEAP_POISONING_COMPREHENSIVE -/* (can't realloc in place if comprehensive is enabled) */ - -TEST_CASE("realloc shrink buffer in place", "[heap]") -{ - void *x = malloc(64); - TEST_ASSERT(x); - void *y = realloc(x, 48); - TEST_ASSERT_EQUAL_PTR(x, y); -} - -#endif - -#ifndef CONFIG_ESP_SYSTEM_MEMPROT_FEATURE -TEST_CASE("realloc shrink buffer with EXEC CAPS", "[heap]") -{ - const size_t buffer_size = 64; - - void *x = heap_caps_malloc(buffer_size, MALLOC_CAP_EXEC); - TEST_ASSERT(x); - void *y = heap_caps_realloc(x, buffer_size - 16, MALLOC_CAP_EXEC); - TEST_ASSERT(y); - - //y needs to fall in a compatible memory area of IRAM: - TEST_ASSERT(esp_ptr_executable(y)|| esp_ptr_in_iram(y) || esp_ptr_in_diram_iram(y)); - - free(y); -} - -TEST_CASE("realloc move data to a new heap type", "[heap]") -{ - const char *test = "I am some test content to put in the heap"; - char buf[64]; - memset(buf, 0xEE, 64); - strlcpy(buf, test, 64); - - char *a = malloc(64); - memcpy(a, buf, 64); - // move data from 'a' to IRAM - char *b = heap_caps_realloc(a, 64, MALLOC_CAP_EXEC); - TEST_ASSERT_NOT_NULL(b); - TEST_ASSERT_NOT_EQUAL(a, b); - TEST_ASSERT(heap_caps_check_integrity(MALLOC_CAP_INVALID, true)); - TEST_ASSERT_EQUAL_HEX32_ARRAY(buf, b, 64 / sizeof(uint32_t)); - - // Move data back to DRAM - char *c = heap_caps_realloc(b, 48, MALLOC_CAP_8BIT); - TEST_ASSERT_NOT_NULL(c); - TEST_ASSERT_NOT_EQUAL(b, c); - TEST_ASSERT(heap_caps_check_integrity(MALLOC_CAP_INVALID, true)); - TEST_ASSERT_EQUAL_HEX8_ARRAY(buf, c, 48); - - free(c); -} -#endif diff --git a/components/heap/test/test_runtime_heap_reg.c b/components/heap/test/test_runtime_heap_reg.c deleted file mode 100644 index 92e81d8f..00000000 --- a/components/heap/test/test_runtime_heap_reg.c +++ /dev/null @@ -1,72 +0,0 @@ -/* - Tests for registering new heap memory at runtime -*/ - -#include -#include "unity.h" -#include "esp_heap_caps_init.h" -#include "esp_system.h" -#include - - -/* NOTE: This is not a well-formed unit test, it leaks memory */ -TEST_CASE("Allocate new heap at runtime", "[heap][ignore]") -{ - const size_t BUF_SZ = 1000; - const size_t HEAP_OVERHEAD_MAX = 200; - void *buffer = malloc(BUF_SZ); - TEST_ASSERT_NOT_NULL(buffer); - uint32_t before_free = esp_get_free_heap_size(); - TEST_ESP_OK( heap_caps_add_region((intptr_t)buffer, (intptr_t)buffer + BUF_SZ) ); - uint32_t after_free = esp_get_free_heap_size(); - printf("Before %u after %u\n", before_free, after_free); - /* allow for some 'heap overhead' from accounting structures */ - TEST_ASSERT(after_free >= before_free + BUF_SZ - HEAP_OVERHEAD_MAX); -} - -/* NOTE: This is not a well-formed unit test, it leaks memory and - may fail if run twice in a row without a reset. -*/ -TEST_CASE("Allocate new heap with new capability", "[heap][ignore]") -{ - const size_t BUF_SZ = 100; -#ifdef CONFIG_ESP_SYSTEM_MEMPROT_FEATURE - const size_t ALLOC_SZ = 32; -#else - const size_t ALLOC_SZ = 64; // More than half of BUF_SZ -#endif - const uint32_t MALLOC_CAP_INVENTED = (1 << 30); /* this must be unused in esp_heap_caps.h */ - - /* no memory exists to provide this capability */ - TEST_ASSERT_NULL( heap_caps_malloc(ALLOC_SZ, MALLOC_CAP_INVENTED) ); - - void *buffer = malloc(BUF_SZ); - TEST_ASSERT_NOT_NULL(buffer); - uint32_t caps[SOC_MEMORY_TYPE_NO_PRIOS] = { MALLOC_CAP_INVENTED }; - TEST_ESP_OK( heap_caps_add_region_with_caps(caps, (intptr_t)buffer, (intptr_t)buffer + BUF_SZ) ); - - /* ta-da, it's now possible! */ - TEST_ASSERT_NOT_NULL( heap_caps_malloc(ALLOC_SZ, MALLOC_CAP_INVENTED) ); -} - -/* NOTE: This is not a well-formed unit test. - * If run twice without a reset, it will failed. - */ - -TEST_CASE("Add .bss memory to heap region runtime", "[heap][ignore]") -{ -#define BUF_SZ 1000 -#define HEAP_OVERHEAD_MAX 200 - static uint8_t s_buffer[BUF_SZ]; - - printf("s_buffer start %08x end %08x\n", (intptr_t)s_buffer, (intptr_t)s_buffer + BUF_SZ); - uint32_t before_free = esp_get_free_heap_size(); - TEST_ESP_OK( heap_caps_add_region((intptr_t)s_buffer, (intptr_t)s_buffer + BUF_SZ) ); - uint32_t after_free = esp_get_free_heap_size(); - printf("Before %u after %u\n", before_free, after_free); - /* allow for some 'heap overhead' from accounting structures */ - TEST_ASSERT(after_free >= before_free + BUF_SZ - HEAP_OVERHEAD_MAX); - - /* Twice add must be failed */ - TEST_ASSERT( (heap_caps_add_region((intptr_t)s_buffer, (intptr_t)s_buffer + BUF_SZ) != ESP_OK) ); -} diff --git a/components/heap/test_multi_heap_host/Makefile b/components/heap/test_multi_heap_host/Makefile deleted file mode 100644 index 0827e346..00000000 --- a/components/heap/test_multi_heap_host/Makefile +++ /dev/null @@ -1,54 +0,0 @@ -TEST_PROGRAM=test_multi_heap -all: $(TEST_PROGRAM) - -ifneq ($(filter clean,$(MAKECMDGOALS)),) -.NOTPARALLEL: # prevent make clean racing the other targets -endif - -SOURCE_FILES = $(abspath \ - ../multi_heap.c \ - ../heap_tlsf.c \ - ../multi_heap_poisoning.c \ - test_multi_heap.cpp \ - main.cpp \ - ) - -INCLUDE_FLAGS = -I../include -I../../../tools/catch - -GCOV ?= gcov - -CPPFLAGS += $(INCLUDE_FLAGS) -D CONFIG_LOG_DEFAULT_LEVEL -g -fstack-protector-all -m32 -DCONFIG_HEAP_POISONING_COMPREHENSIVE -CFLAGS += -Wall -Werror -fprofile-arcs -ftest-coverage -CXXFLAGS += -std=c++11 -Wall -Werror -fprofile-arcs -ftest-coverage -LDFLAGS += -lstdc++ -fprofile-arcs -ftest-coverage -m32 - -OBJ_FILES = $(filter %.o, $(SOURCE_FILES:.cpp=.o) $(SOURCE_FILES:.c=.o)) - -COVERAGE_FILES = $(OBJ_FILES:.o=.gc*) - -$(TEST_PROGRAM): $(OBJ_FILES) - g++ $(LDFLAGS) -o $(TEST_PROGRAM) $(OBJ_FILES) - -$(OUTPUT_DIR): - mkdir -p $(OUTPUT_DIR) - -test: $(TEST_PROGRAM) - ./$(TEST_PROGRAM) - -$(COVERAGE_FILES): $(TEST_PROGRAM) test - -coverage.info: $(COVERAGE_FILES) - find ../ -name "*.gcno" -exec $(GCOV) -r -pb {} + - lcov --capture --directory $(abspath ../) --no-external --output-file coverage.info --gcov-tool $(GCOV) - -coverage_report: coverage.info - genhtml coverage.info --output-directory coverage_report - @echo "Coverage report is in coverage_report/index.html" - -clean: - rm -f $(OBJ_FILES) $(TEST_PROGRAM) - rm -f $(COVERAGE_FILES) *.gcov - rm -rf coverage_report/ - rm -f coverage.info - -.PHONY: clean all test diff --git a/components/heap/test_multi_heap_host/main.cpp b/components/heap/test_multi_heap_host/main.cpp deleted file mode 100644 index 0c7c351f..00000000 --- a/components/heap/test_multi_heap_host/main.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#define CATCH_CONFIG_MAIN -#include "catch.hpp" diff --git a/components/heap/test_multi_heap_host/test_all_configs.sh b/components/heap/test_multi_heap_host/test_all_configs.sh deleted file mode 100644 index ce144d0d..00000000 --- a/components/heap/test_multi_heap_host/test_all_configs.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash -# -# Run the test suite with all configurations enabled -# - -FAIL=0 - -for FLAGS in "CONFIG_HEAP_POISONING_NONE" "CONFIG_HEAP_POISONING_LIGHT" "CONFIG_HEAP_POISONING_COMPREHENSIVE" ; do - echo "==== Testing with config: ${FLAGS} ====" - CPPFLAGS="-D${FLAGS}" make clean test || FAIL=1 -done - -make clean - -if [ $FAIL == 0 ]; then - echo "All configurations passed" -else - echo "Some configurations failed, see log." - exit 1 -fi diff --git a/components/heap/test_multi_heap_host/test_multi_heap.cpp b/components/heap/test_multi_heap_host/test_multi_heap.cpp deleted file mode 100644 index 837d915b..00000000 --- a/components/heap/test_multi_heap_host/test_multi_heap.cpp +++ /dev/null @@ -1,508 +0,0 @@ -#include "catch.hpp" -#include "multi_heap.h" - -#include "../multi_heap_config.h" - -#include -#include - -static void *__malloc__(size_t bytes) -{ - return malloc(bytes); -} - -static void __free__(void *ptr) -{ - free(ptr); -} - -/* Insurance against accidentally using libc heap functions in tests */ -#undef free -#define free #error -#undef malloc -#define malloc #error -#undef calloc -#define calloc #error -#undef realloc -#define realloc #error - -TEST_CASE("multi_heap simple allocations", "[multi_heap]") -{ - uint8_t small_heap[4 * 1024]; - - multi_heap_handle_t heap = multi_heap_register(small_heap, sizeof(small_heap)); - - size_t test_alloc_size = (multi_heap_free_size(heap) + 4) / 2; - - printf("New heap:\n"); - multi_heap_dump(heap); - printf("*********************\n"); - - uint8_t *buf = (uint8_t *)multi_heap_malloc(heap, test_alloc_size); - - printf("small_heap %p buf %p\n", small_heap, buf); - REQUIRE( buf != NULL ); - REQUIRE((intptr_t)buf >= (intptr_t)small_heap); - REQUIRE( (intptr_t)buf < (intptr_t)(small_heap + sizeof(small_heap))); - - REQUIRE( multi_heap_get_allocated_size(heap, buf) >= test_alloc_size ); - REQUIRE( multi_heap_get_allocated_size(heap, buf) < test_alloc_size + 16); - - memset(buf, 0xEE, test_alloc_size); - - REQUIRE( multi_heap_malloc(heap, test_alloc_size) == NULL ); - - multi_heap_free(heap, buf); - - printf("Empty?\n"); - multi_heap_dump(heap); - printf("*********************\n"); - - /* Now there should be space for another allocation */ - buf = (uint8_t *)multi_heap_malloc(heap, test_alloc_size); - REQUIRE( buf != NULL ); - multi_heap_free(heap, buf); - - REQUIRE( multi_heap_free_size(heap) > multi_heap_minimum_free_size(heap) ); -} - - -TEST_CASE("multi_heap fragmentation", "[multi_heap]") -{ - uint8_t small_heap[4 * 1024]; - multi_heap_handle_t heap = multi_heap_register(small_heap, sizeof(small_heap)); - - const size_t alloc_size = 128; - - void *p[4]; - for (int i = 0; i < 4; i++) { - multi_heap_dump(heap); - REQUIRE( multi_heap_check(heap, true) ); - p[i] = multi_heap_malloc(heap, alloc_size); - printf("%d = %p ****->\n", i, p[i]); - multi_heap_dump(heap); - REQUIRE( p[i] != NULL ); - } - - printf("allocated %p %p %p %p\n", p[0], p[1], p[2], p[3]); - - REQUIRE( multi_heap_malloc(heap, alloc_size * 5) == NULL ); /* no room to allocate 5*alloc_size now */ - - printf("4 allocations:\n"); - multi_heap_dump(heap); - printf("****************\n"); - - multi_heap_free(heap, p[0]); - multi_heap_free(heap, p[1]); - multi_heap_free(heap, p[3]); - - printf("1 allocations:\n"); - multi_heap_dump(heap); - printf("****************\n"); - - void *big = multi_heap_malloc(heap, alloc_size * 3); - //Blocks in TLSF are organized in different form, so this makes no sense - multi_heap_free(heap, big); - - multi_heap_free(heap, p[2]); - - printf("0 allocations:\n"); - multi_heap_dump(heap); - printf("****************\n"); - - big = multi_heap_malloc(heap, alloc_size * 2); - //Blocks in TLSF are organized in different form, so this makes no sense - multi_heap_free(heap, big); -} - -/* Test that malloc/free does not leave free space fragmented */ -TEST_CASE("multi_heap defrag", "[multi_heap]") -{ - void *p[4]; - uint8_t small_heap[4 * 1024]; - multi_heap_info_t info, info2; - multi_heap_handle_t heap = multi_heap_register(small_heap, sizeof(small_heap)); - - printf("0 ---\n"); - multi_heap_dump(heap); - REQUIRE( multi_heap_check(heap, true) ); - multi_heap_get_info(heap, &info); - REQUIRE( 0 == info.allocated_blocks ); - REQUIRE( 1 == info.free_blocks ); - - printf("1 ---\n"); - p[0] = multi_heap_malloc(heap, 128); - p[1] = multi_heap_malloc(heap, 32); - multi_heap_dump(heap); - REQUIRE( multi_heap_check(heap, true) ); - - printf("2 ---\n"); - multi_heap_free(heap, p[0]); - p[2] = multi_heap_malloc(heap, 64); - multi_heap_dump(heap); - REQUIRE( p[2] == p[0] ); - REQUIRE( multi_heap_check(heap, true) ); - - printf("3 ---\n"); - multi_heap_free(heap, p[2]); - p[3] = multi_heap_malloc(heap, 32); - multi_heap_dump(heap); - REQUIRE( p[3] == p[0] ); - REQUIRE( multi_heap_check(heap, true) ); - - multi_heap_get_info(heap, &info2); - REQUIRE( 2 == info2.allocated_blocks ); - REQUIRE( 2 == info2.free_blocks ); - - multi_heap_free(heap, p[0]); - multi_heap_free(heap, p[1]); - multi_heap_get_info(heap, &info2); - REQUIRE( 0 == info2.allocated_blocks ); - REQUIRE( 1 == info2.free_blocks ); - REQUIRE( info.total_free_bytes == info2.total_free_bytes ); -} - -/* Test that malloc/free does not leave free space fragmented - Note: With fancy poisoning, realloc is implemented as malloc-copy-free and this test does not apply. - */ -#ifndef MULTI_HEAP_POISONING_SLOW -TEST_CASE("multi_heap defrag realloc", "[multi_heap]") -{ - void *p[4]; - uint8_t small_heap[4 * 1024]; - multi_heap_info_t info, info2; - multi_heap_handle_t heap = multi_heap_register(small_heap, sizeof(small_heap)); - - printf("0 ---\n"); - multi_heap_dump(heap); - REQUIRE( multi_heap_check(heap, true) ); - multi_heap_get_info(heap, &info); - REQUIRE( 0 == info.allocated_blocks ); - REQUIRE( 1 == info.free_blocks ); - - printf("1 ---\n"); - p[0] = multi_heap_malloc(heap, 128); - p[1] = multi_heap_malloc(heap, 32); - multi_heap_dump(heap); - REQUIRE( multi_heap_check(heap, true) ); - - printf("2 ---\n"); - p[2] = multi_heap_realloc(heap, p[0], 64); - multi_heap_dump(heap); - REQUIRE( p[2] == p[0] ); - REQUIRE( multi_heap_check(heap, true) ); - - printf("3 ---\n"); - p[3] = multi_heap_realloc(heap, p[2], 32); - multi_heap_dump(heap); - REQUIRE( p[3] == p[0] ); - REQUIRE( multi_heap_check(heap, true) ); - - multi_heap_get_info(heap, &info2); - REQUIRE( 2 == info2.allocated_blocks ); - REQUIRE( 2 == info2.free_blocks ); - - multi_heap_free(heap, p[0]); - multi_heap_free(heap, p[1]); - multi_heap_get_info(heap, &info2); - REQUIRE( 0 == info2.allocated_blocks ); - REQUIRE( 1 == info2.free_blocks ); - REQUIRE( info.total_free_bytes == info2.total_free_bytes ); -} -#endif - - -void multi_heap_allocation_impl(int heap_size) -{ - uint8_t *big_heap = (uint8_t *) __malloc__(2*heap_size); - const int NUM_POINTERS = 64; - - printf("Running multi-allocation test with heap_size %d...\n", heap_size); - - REQUIRE( big_heap ); - multi_heap_handle_t heap = multi_heap_register(big_heap, heap_size); - - void *p[NUM_POINTERS] = { 0 }; - size_t s[NUM_POINTERS] = { 0 }; - - const size_t initial_free = multi_heap_free_size(heap); - - const int ITERATIONS = 10000; - - for (int i = 0; i < ITERATIONS; i++) { - /* check all pointers allocated so far are valid inside big_heap */ - for (int j = 0; j < NUM_POINTERS; j++) { - if (p[j] != NULL) { - } - } - - uint8_t n = rand() % NUM_POINTERS; - - if (rand() % 4 == 0) { - /* 1 in 4 iterations, try to realloc the buffer instead - of using malloc/free - */ - size_t new_size = rand() % 1024; - void *new_p = multi_heap_realloc(heap, p[n], new_size); - printf("realloc %p -> %p (%zu -> %zu)\n", p[n], new_p, s[n], new_size); - multi_heap_check(heap, true); - if (new_size == 0 || new_p != NULL) { - p[n] = new_p; - s[n] = new_size; - if (new_size > 0) { - REQUIRE( p[n] >= big_heap ); - REQUIRE( p[n] < big_heap + heap_size ); - memset(p[n], n, new_size); - } - } - continue; - } - if (p[n] != NULL) { - if (s[n] > 0) { - /* Verify pre-existing contents of p[n] */ - uint8_t compare[s[n]]; - memset(compare, n, s[n]); - /*REQUIRE*/assert( memcmp(compare, p[n], s[n]) == 0 ); - } - REQUIRE( multi_heap_check(heap, true) ); - multi_heap_free(heap, p[n]); - printf("freed %p (%zu)\n", p[n], s[n]); - if (!multi_heap_check(heap, true)) { - printf("FAILED iteration %d after freeing %p\n", i, p[n]); - multi_heap_dump(heap); - REQUIRE(0); - } - } - - s[n] = rand() % 1024; - REQUIRE( multi_heap_check(heap, true) ); - p[n] = multi_heap_malloc(heap, s[n]); - printf("malloc %p (%zu)\n", p[n], s[n]); - if (p[n] != NULL) { - REQUIRE( p[n] >= big_heap ); - REQUIRE( p[n] < big_heap + heap_size ); - } - if (!multi_heap_check(heap, true)) { - printf("FAILED iteration %d after mallocing %p (%zu bytes)\n", i, p[n], s[n]); - multi_heap_dump(heap); - REQUIRE(0); - } - if (p[n] != NULL) { - memset(p[n], n, s[n]); - } - } - - for (int i = 0; i < NUM_POINTERS; i++) { - multi_heap_free(heap, p[i]); - if (!multi_heap_check(heap, true)) { - printf("FAILED during cleanup after freeing %p\n", p[i]); - multi_heap_dump(heap); - REQUIRE(0); - } - } - - REQUIRE( initial_free == multi_heap_free_size(heap) ); - __free__(big_heap); -} - -TEST_CASE("multi_heap many random allocations", "[multi_heap]") -{ - size_t poolsize[] = { 15, 255, 4095, 8191 }; - for (size_t i = 0; i < sizeof(poolsize)/sizeof(size_t); i++) { - multi_heap_allocation_impl(poolsize[i] * 1024); - } -} - -TEST_CASE("multi_heap_get_info() function", "[multi_heap]") -{ - uint8_t heapdata[4 * 1024]; - multi_heap_handle_t heap = multi_heap_register(heapdata, sizeof(heapdata)); - multi_heap_info_t before, after, freed; - - multi_heap_get_info(heap, &before); - printf("before: total_free_bytes %zu\ntotal_allocated_bytes %zu\nlargest_free_block %zu\nminimum_free_bytes %zu\nallocated_blocks %zu\nfree_blocks %zu\ntotal_blocks %zu\n", - before.total_free_bytes, - before.total_allocated_bytes, - before.largest_free_block, - before.minimum_free_bytes, - before.allocated_blocks, - before.free_blocks, - before.total_blocks); - - REQUIRE( 0 == before.allocated_blocks ); - REQUIRE( 0 == before.total_allocated_bytes ); - REQUIRE( before.total_free_bytes == before.minimum_free_bytes ); - - void *x = multi_heap_malloc(heap, 32); - multi_heap_get_info(heap, &after); - printf("after: total_free_bytes %zu\ntotal_allocated_bytes %zu\nlargest_free_block %zu\nminimum_free_bytes %zu\nallocated_blocks %zu\nfree_blocks %zu\ntotal_blocks %zu\n", - after.total_free_bytes, - after.total_allocated_bytes, - after.largest_free_block, - after.minimum_free_bytes, - after.allocated_blocks, - after.free_blocks, - after.total_blocks); - - REQUIRE( 1 == after.allocated_blocks ); - REQUIRE( 32 == after.total_allocated_bytes ); - REQUIRE( after.minimum_free_bytes < before.minimum_free_bytes); - REQUIRE( after.minimum_free_bytes > 0 ); - - multi_heap_free(heap, x); - multi_heap_get_info(heap, &freed); - printf("freed: total_free_bytes %zu\ntotal_allocated_bytes %zu\nlargest_free_block %zu\nminimum_free_bytes %zu\nallocated_blocks %zu\nfree_blocks %zu\ntotal_blocks %zu\n", - freed.total_free_bytes, - freed.total_allocated_bytes, - freed.largest_free_block, - freed.minimum_free_bytes, - freed.allocated_blocks, - freed.free_blocks, - freed.total_blocks); - - REQUIRE( 0 == freed.allocated_blocks ); - REQUIRE( 0 == freed.total_allocated_bytes ); - REQUIRE( before.total_free_bytes == freed.total_free_bytes ); - REQUIRE( after.minimum_free_bytes == freed.minimum_free_bytes ); -} - -TEST_CASE("multi_heap minimum-size allocations", "[multi_heap]") -{ - uint8_t heapdata[4096]; - void *p[sizeof(heapdata) / sizeof(void *)] = {NULL}; - const size_t NUM_P = sizeof(p) / sizeof(void *); - size_t allocated_size = 0; - multi_heap_handle_t heap = multi_heap_register(heapdata, sizeof(heapdata)); - size_t before_free = multi_heap_free_size(heap); - - size_t i; - for (i = 0; i < NUM_P; i++) { - //TLSF minimum block size is 4 bytes - p[i] = multi_heap_malloc(heap, 1); - if (p[i] == NULL) { - break; - } - } - - REQUIRE( i < NUM_P); // Should have run out of heap before we ran out of pointers - printf("Allocated %zu minimum size chunks\n", i); - - REQUIRE(multi_heap_free_size(heap) < before_free); - multi_heap_check(heap, true); - - /* Free in random order */ - bool has_allocations = true; - while (has_allocations) { - i = rand() % NUM_P; - multi_heap_free(heap, p[i]); - p[i] = NULL; - multi_heap_check(heap, true); - - has_allocations = false; - for (i = 0; i < NUM_P && !has_allocations; i++) { - has_allocations = (p[i] != NULL); - } - } - - /* all freed! */ - REQUIRE( before_free == multi_heap_free_size(heap) ); -} - -TEST_CASE("multi_heap_realloc()", "[multi_heap]") -{ - const uint32_t PATTERN = 0xABABDADA; - uint8_t small_heap[4 * 1024]; - multi_heap_handle_t heap = multi_heap_register(small_heap, sizeof(small_heap)); - - uint32_t *a = (uint32_t *)multi_heap_malloc(heap, 64); - uint32_t *b = (uint32_t *)multi_heap_malloc(heap, 32); - REQUIRE( a != NULL ); - REQUIRE( b != NULL ); - REQUIRE( b > a); /* 'b' takes the block after 'a' */ - - *a = PATTERN; - - uint32_t *c = (uint32_t *)multi_heap_realloc(heap, a, 72); - REQUIRE( multi_heap_check(heap, true)); - REQUIRE( c != NULL ); - REQUIRE( c > b ); /* 'a' moves, 'c' takes the block after 'b' */ - REQUIRE( *c == PATTERN ); - -#ifndef MULTI_HEAP_POISONING_SLOW - // "Slow" poisoning implementation doesn't reallocate in place, so these - // test will fail... - - uint32_t *d = (uint32_t *)multi_heap_realloc(heap, c, 36); - REQUIRE( multi_heap_check(heap, true) ); - REQUIRE( c == d ); /* 'c' block should be shrunk in-place */ - REQUIRE( *d == PATTERN); - - uint32_t *e = (uint32_t *)multi_heap_malloc(heap, 64); - REQUIRE( multi_heap_check(heap, true)); - REQUIRE( a == e ); /* 'e' takes the block formerly occupied by 'a' */ - - multi_heap_free(heap, d); - uint32_t *f = (uint32_t *)multi_heap_realloc(heap, b, 64); - REQUIRE( multi_heap_check(heap, true) ); - REQUIRE( f == b ); /* 'b' should be extended in-place, over space formerly occupied by 'd' */ - -#ifdef MULTI_HEAP_POISONING -#define TOO_MUCH 7420 + 1 -#else -#define TOO_MUCH 7420 + 1 -#endif - /* not enough contiguous space left in the heap */ - uint32_t *g = (uint32_t *)multi_heap_realloc(heap, e, TOO_MUCH); - REQUIRE( g == NULL ); - - multi_heap_free(heap, f); - /* try again */ - g = (uint32_t *)multi_heap_realloc(heap, e, 128); - REQUIRE( multi_heap_check(heap, true) ); - REQUIRE( e == g ); /* 'g' extends 'e' in place, into the space formerly held by 'f' */ -#endif -} - -// TLSF only accepts heaps aligned to 4-byte boundary so -// only aligned allocation tests make sense. -TEST_CASE("multi_heap aligned allocations", "[multi_heap]") -{ - uint8_t test_heap[4 * 1024]; - multi_heap_handle_t heap = multi_heap_register(test_heap, sizeof(test_heap)); - uint32_t aligments = 0; // starts from alignment by 4-byte boundary - size_t old_size = multi_heap_free_size(heap); - size_t leakage = 1024; - printf("[ALIGNED_ALLOC] heap_size before: %d \n", old_size); - - printf("New heap:\n"); - multi_heap_dump(heap); - printf("*********************\n"); - - for(;aligments <= 256; aligments++) { - - //Use some stupid size value to test correct alignment even in strange - //memory layout objects: - uint8_t *buf = (uint8_t *)multi_heap_aligned_alloc(heap, (aligments + 137), aligments ); - if(((aligments & (aligments - 1)) != 0) || (!aligments)) { - REQUIRE( buf == NULL ); - } else { - REQUIRE( buf != NULL ); - REQUIRE((intptr_t)buf >= (intptr_t)test_heap); - REQUIRE((intptr_t)buf < (intptr_t)(test_heap + sizeof(test_heap))); - - printf("[ALIGNED_ALLOC] alignment required: %u \n", aligments); - printf("[ALIGNED_ALLOC] address of allocated memory: %p \n\n", (void *)buf); - //Address of obtained block must be aligned with selected value - REQUIRE(((intptr_t)buf & (aligments - 1)) == 0); - - //Write some data, if it corrupts memory probably the heap - //canary verification will fail: - memset(buf, 0xA5, (aligments + 137)); - - multi_heap_free(heap, buf); - } - } - - printf("[ALIGNED_ALLOC] heap_size after: %d \n", multi_heap_free_size(heap)); - REQUIRE((old_size - multi_heap_free_size(heap)) <= leakage); -} diff --git a/components/platform_console/CMakeLists.txt b/components/platform_console/CMakeLists.txt index 5a315a3e..ae7652e7 100644 --- a/components/platform_console/CMakeLists.txt +++ b/components/platform_console/CMakeLists.txt @@ -9,8 +9,6 @@ idf_component_register( SRCS INCLUDE_DIRS . REQUIRES nvs_flash PRIV_REQUIRES console app_update tools services spi_flash platform_config vfs pthread wifi-manager platform_config newlib telnet display squeezelite tools) -target_link_libraries(${COMPONENT_LIB} "-Wl,--undefined=GDS_DrawPixelFast") -target_link_libraries(${COMPONENT_LIB} ${build_dir}/esp-idf/$/lib$.a ) set_source_files_properties(cmd_config.c PROPERTIES COMPILE_FLAGS diff --git a/components/platform_console/cmd_ota.c b/components/platform_console/cmd_ota.c index 634f120f..ed711d59 100644 --- a/components/platform_console/cmd_ota.c +++ b/components/platform_console/cmd_ota.c @@ -23,7 +23,6 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "soc/rtc_cntl_reg.h" -#include "esp32/rom/uart.h" #include "sdkconfig.h" #include "platform_console.h" #include "messaging.h" diff --git a/components/platform_console/cmd_system.c b/components/platform_console/cmd_system.c index 3d6cfb4a..45df5192 100644 --- a/components/platform_console/cmd_system.c +++ b/components/platform_console/cmd_system.c @@ -20,7 +20,7 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "soc/rtc_cntl_reg.h" -#include "esp32/rom/uart.h" +#include "esp_rom_uart.h" #include "cmd_system.h" #include "sdkconfig.h" #include "esp_partition.h" @@ -28,7 +28,6 @@ #include "platform_esp32.h" #include "platform_config.h" #include "esp_sleep.h" -#include "driver/uart.h" // for the uart driver access #include "messaging.h" #include "platform_console.h" #include "tools.h" @@ -791,7 +790,7 @@ static int light_sleep(int argc, char **argv) ESP_ERROR_CHECK( esp_sleep_enable_uart_wakeup(CONFIG_ESP_CONSOLE_UART_NUM) ); } fflush(stdout); - uart_tx_wait_idle(CONFIG_ESP_CONSOLE_UART_NUM); + esp_rom_uart_tx_wait_idle(CONFIG_ESP_CONSOLE_UART_NUM); esp_light_sleep_start(); esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); const char *cause_str; diff --git a/components/raop/dmap_parser.c b/components/raop/dmap_parser.c index d3d3b85d..67e43e8b 100644 --- a/components/raop/dmap_parser.c +++ b/components/raop/dmap_parser.c @@ -424,7 +424,7 @@ static int dmap_parse_internal(const dmap_settings *settings, const char *buf, s /* Make a best guess of the type */ field_type = DMAP_UNKNOWN; field_name = code; - +#ifdef DMAP_FULL if (field_len >= 8) { /* Look for a four char code followed by a length within the current field */ if (isalpha(p[0] & 0xff) && @@ -448,6 +448,7 @@ static int dmap_parse_internal(const dmap_settings *settings, const char *buf, s field_type = is_string ? DMAP_STR : DMAP_UINT; } +#endif } switch (field_type) { diff --git a/components/raop/raop.c b/components/raop/raop.c index 59124737..827c43c1 100644 --- a/components/raop/raop.c +++ b/components/raop/raop.c @@ -124,7 +124,7 @@ struct raop_ctx_s *raop_create(uint32_t host, char *name, "ss=16", "sr=44100", "vn=3", "txtvers=1", NULL }; #else - mdns_txt_item_t txt[] = { + const mdns_txt_item_t txt[] = { {"am", "airesp32"}, {"tp", "UDP"}, {"sm","false"}, @@ -765,7 +765,7 @@ static void search_remote(void *args) { /*----------------------------------------------------------------------------*/ static char *rsa_apply(unsigned char *input, int inlen, int *outlen, int mode) { - static char super_secret_key[] = + const static char super_secret_key[] = "-----BEGIN RSA PRIVATE KEY-----\n" "MIIEpQIBAAKCAQEA59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUt\n" "wC5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDRKSKv6kDqnw4U\n" @@ -853,7 +853,7 @@ static char *rsa_apply(unsigned char *input, int inlen, int *outlen, int mode) #define DECODE_ERROR 0xffffffff -static char base64_chars[] = +const static char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; /*----------------------------------------------------------------------------*/ diff --git a/components/spotify/CMakeLists.txt b/components/spotify/CMakeLists.txt index 1eaa59df..9b78f37a 100644 --- a/components/spotify/CMakeLists.txt +++ b/components/spotify/CMakeLists.txt @@ -17,6 +17,8 @@ set(BELL_DISABLE_SINKS ON) set(BELL_DISABLE_FMT ON) set(BELL_DISABLE_REGEX ON) set(BELL_ONLY_CJSON ON) +set(BELL_DISABLE_MQTT ON) +set(BELL_DISABLE_WEBSERVER ON) set(CSPOT_TARGET_ESP32 ON) # because CMake is so broken, the cache set below overrides a normal "set" for the first build diff --git a/components/spotify/Shim.cpp b/components/spotify/Shim.cpp index eb30f70c..014d2f0d 100644 --- a/components/spotify/Shim.cpp +++ b/components/spotify/Shim.cpp @@ -34,78 +34,6 @@ static class cspotPlayer *player; -/**************************************************************************************** - * Chunk manager class (task) - */ - -class chunkManager : public bell::Task { -public: - std::atomic isRunning = true; - std::atomic isPaused = true; - chunkManager(std::function trackHandler, std::function dataHandler); - size_t writePCM(uint8_t* data, size_t bytes, std::string_view trackId, size_t sequence); - void flush(); - void teardown(); - -private: - std::unique_ptr centralAudioBuffer; - std::function trackHandler; - std::function dataHandler; - std::mutex runningMutex; - - void runTask() override; -}; - -chunkManager::chunkManager(std::function trackHandler, std::function dataHandler) - : bell::Task("chunker", 4 * 1024, 0, 0) { - this->centralAudioBuffer = std::make_unique(32); - this->trackHandler = trackHandler; - this->dataHandler = dataHandler; - startTask(); -} - -size_t chunkManager::writePCM(uint8_t* data, size_t bytes, std::string_view trackId, size_t sequence) { - return centralAudioBuffer->writePCM(data, bytes, sequence); -} - -void chunkManager::teardown() { - isRunning = false; - std::scoped_lock lock(runningMutex); -} - -void chunkManager::flush() { - centralAudioBuffer->clearBuffer(); -} - -void chunkManager::runTask() { - std::scoped_lock lock(runningMutex); - size_t lastHash = 0; - - while (isRunning) { - - if (isPaused) { - BELL_SLEEP_MS(100); - continue; - } - - auto chunk = centralAudioBuffer->readChunk(); - - if (!chunk || chunk->pcmSize == 0) { - BELL_SLEEP_MS(50); - continue; - } - - // receiving first chunk of new track from Spotify server - if (lastHash != chunk->trackHash) { - CSPOT_LOG(info, "hash update %x => %x", lastHash, chunk->trackHash); - lastHash = chunk->trackHash; - trackHandler(); - } - - dataHandler(chunk->pcmData, chunk->pcmSize); - } -} - /**************************************************************************************** * Player's main class & task */ @@ -114,19 +42,21 @@ class cspotPlayer : public bell::Task { private: std::string name; bell::WrappedSemaphore clientConnected; - + std::atomic isPaused, isConnected; + int startOffset, volume = 0, bitrate = 160; httpd_handle_t serverHandle; int serverPort; cspot_cmd_cb_t cmdHandler; cspot_data_cb_t dataHandler; + std::string lastTrackId; std::shared_ptr blob; std::unique_ptr spirc; - std::unique_ptr chunker; void eventHandler(std::unique_ptr event); void trackHandler(void); + size_t pcmWrite(uint8_t *pcm, size_t bytes, std::string_view trackId); void runTask(); @@ -155,6 +85,17 @@ cspotPlayer::cspotPlayer(const char* name, httpd_handle_t server, int port, cspo if (bitrate != 96 && bitrate != 160 && bitrate != 320) bitrate = 160; } +size_t cspotPlayer::pcmWrite(uint8_t *pcm, size_t bytes, std::string_view trackId) { + if (lastTrackId != trackId) { + CSPOT_LOG(info, "new track started <%s> => <%s>", lastTrackId.c_str(), trackId.data()); + lastTrackId = trackId; + trackHandler(); + } + + dataHandler(pcm, bytes); + return bytes; +} + extern "C" { static esp_err_t handleGET(httpd_req_t *request) { return player->handleGET(request); @@ -233,8 +174,7 @@ esp_err_t cspotPlayer::handlePOST(httpd_req_t *request) { void cspotPlayer::eventHandler(std::unique_ptr event) { switch (event->eventType) { case cspot::SpircHandler::EventType::PLAYBACK_START: { - chunker->flush(); - + lastTrackId.clear(); // we are not playing anymore trackStatus = TRACK_INIT; // memorize position for when track's beginning will be detected @@ -247,13 +187,12 @@ void cspotPlayer::eventHandler(std::unique_ptr event break; } case cspot::SpircHandler::EventType::PLAY_PAUSE: { - bool pause = std::get(event->data); - cmdHandler(pause ? CSPOT_PAUSE : CSPOT_PLAY); - chunker->isPaused = pause; + isPaused = std::get(event->data); + cmdHandler(isPaused ? CSPOT_PAUSE : CSPOT_PLAY); break; } case cspot::SpircHandler::EventType::TRACK_INFO: { - auto trackInfo = std::get(event->data); + auto trackInfo = std::get(event->data); cmdHandler(CSPOT_TRACK_INFO, trackInfo.duration, startOffset, trackInfo.artist.c_str(), trackInfo.album.c_str(), trackInfo.name.c_str(), trackInfo.imageUrl.c_str()); spirc->updatePositionMs(startOffset); @@ -264,17 +203,14 @@ void cspotPlayer::eventHandler(std::unique_ptr event case cspot::SpircHandler::EventType::PREV: case cspot::SpircHandler::EventType::FLUSH: { // FLUSH is sent when there is no next, just clean everything - chunker->flush(); cmdHandler(CSPOT_FLUSH); break; } case cspot::SpircHandler::EventType::DISC: - chunker->flush(); cmdHandler(CSPOT_DISC); - chunker->teardown(); + isConnected = false; break; case cspot::SpircHandler::EventType::SEEK: { - chunker->flush(); cmdHandler(CSPOT_SEEK, std::get(event->data)); break; } @@ -293,10 +229,9 @@ void cspotPlayer::eventHandler(std::unique_ptr event void cspotPlayer::trackHandler(void) { // this is just informative - auto trackInfo = spirc->getTrackPlayer()->getCurrentTrackInfo(); uint32_t remains; cmdHandler(CSPOT_QUERY_REMAINING, &remains); - CSPOT_LOG(info, "next track <%s> will play in %d ms", trackInfo.name.c_str(), remains); + CSPOT_LOG(info, "next track will play in %d ms", remains); // inform sink of track beginning trackStatus = TRACK_NOTIFY; @@ -317,7 +252,8 @@ void cspotPlayer::command(cspot_event_t event) { break; // setPause comes back through cspot::event with PLAY/PAUSE case CSPOT_TOGGLE: - spirc->setPause(!chunker->isPaused); + isPaused = !isPaused; + spirc->setPause(isPaused); break; case CSPOT_STOP: case CSPOT_PAUSE: @@ -326,12 +262,11 @@ void cspotPlayer::command(cspot_event_t event) { case CSPOT_PLAY: spirc->setPause(false); break; - // calling spirc->disconnect() might have been logical but it does not - // generate any cspot::event, so we need to manually force exiting player - // loop through chunker which will eventually do the disconnect + /* Calling spirc->disconnect() might have been logical but it does not + * generate any cspot::event */ case CSPOT_DISC: cmdHandler(CSPOT_DISC); - chunker->teardown(); + isConnected = false; break; // spirc->setRemoteVolume does not generate a cspot::event so call cmdHandler case CSPOT_VOLUME_UP: @@ -391,20 +326,12 @@ void cspotPlayer::runTask() { // Auth successful if (token.size() > 0) { spirc = std::make_unique(ctx); + isConnected = true; - // Create a player, pass the track handler - chunker = std::make_unique( - [this](void) { - return trackHandler(); - }, - [this](const uint8_t* data, size_t bytes) { - return dataHandler(data, bytes); - }); - // set call back to calculate a hash on trackId spirc->getTrackPlayer()->setDataCallback( - [this](uint8_t* data, size_t bytes, std::string_view trackId, size_t sequence) { - return chunker->writePCM(data, bytes, trackId, sequence); + [this](uint8_t* data, size_t bytes, std::string_view trackId) { + return pcmWrite(data, bytes, trackId); }); // set event (PLAY, VOLUME...) handler @@ -420,7 +347,7 @@ void cspotPlayer::runTask() { cmdHandler(CSPOT_VOLUME, volume); // exit when player has stopped (received a DISC) - while (chunker->isRunning) { + while (isConnected) { ctx->session->handlePacket(); // low-accuracy polling events diff --git a/components/spotify/cspot/bell/.clangd b/components/spotify/cspot/bell/.clangd index ffa67d1f..33c8b9b7 100644 --- a/components/spotify/cspot/bell/.clangd +++ b/components/spotify/cspot/bell/.clangd @@ -1,2 +1,3 @@ + CompileFlags: CompilationDatabase: example/build # Search build/ directory for compile_commands.json diff --git a/components/spotify/cspot/bell/CMakeLists.txt b/components/spotify/cspot/bell/CMakeLists.txt index 80af580b..f2244bf5 100644 --- a/components/spotify/cspot/bell/CMakeLists.txt +++ b/components/spotify/cspot/bell/CMakeLists.txt @@ -7,6 +7,8 @@ project(bell) option(BELL_DISABLE_CODECS "Disable the entire audio codec wrapper" OFF) option(BELL_CODEC_AAC "Support libhelix-aac codec" ON) option(BELL_CODEC_MP3 "Support libhelix-mp3 codec" ON) +option(BELL_DISABLE_MQTT "Disable the built-in MQTT wrapper" OFF) +option(BELL_DISABLE_WEBSERVER "Disable the built-in Web server" OFF) option(BELL_CODEC_VORBIS "Support tremor Vorbis codec" ON) option(BELL_CODEC_ALAC "Support Apple ALAC codec" ON) option(BELL_CODEC_OPUS "Support Opus codec" ON) @@ -63,13 +65,15 @@ endif() message(STATUS " Use cJSON only: ${BELL_ONLY_CJSON}") message(STATUS " Disable Fmt: ${BELL_DISABLE_FMT}") +message(STATUS " Disable Mqtt: ${BELL_DISABLE_MQTT}") message(STATUS " Disable Regex: ${BELL_DISABLE_REGEX}") +message(STATUS " Disable Web server: ${BELL_DISABLE_WEBSERVER}") # Include nanoPB library set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/external/nanopb/extra") find_package(Nanopb REQUIRED) message(${NANOPB_INCLUDE_DIRS}) -list(APPEND EXTRA_INCLUDES ${NANOPB_INCLUDE_DIRS}) +list(APPEND EXTERNAL_INCLUDES ${NANOPB_INCLUDE_DIRS}) # CMake options set(CMAKE_CXX_STANDARD 20) @@ -84,7 +88,7 @@ set(IO_DIR "${CMAKE_CURRENT_SOURCE_DIR}/main/io") set(PLATFORM_DIR "${CMAKE_CURRENT_SOURCE_DIR}/main/platform") set(UTILITIES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/main/utilities") -add_definitions("-DUSE_DEFAULT_STDLIB=1") +add_definitions("-DUSE_DEFAULT_STDLIB=1 -DTARGET_OS_IPHONE=0") # Main library sources file(GLOB SOURCES @@ -93,8 +97,6 @@ file(GLOB SOURCES "main/io/*.cpp" "main/io/*.c" ) -list(REMOVE_ITEM SOURCES "${IO_DIR}/BellTar.cpp" "${IO_DIR}/BellHTTPServer.cpp") - list(APPEND EXTRA_INCLUDES "main/audio-codec/include") list(APPEND EXTRA_INCLUDES "main/audio-dsp/include") list(APPEND EXTRA_INCLUDES "main/audio-sinks/include") @@ -111,7 +113,7 @@ endif() if(APPLE) file(GLOB APPLE_PLATFORM_SOURCES "main/platform/apple/*.cpp" "main/platform/apple/*.c") list(APPEND SOURCES ${APPLE_PLATFORM_SOURCES}) - list(APPEND EXTRA_INCLUDES "/usr/local/opt/mbedtls@3/include") + list(APPEND EXTERNAL_INCLUDES "/usr/local/opt/mbedtls@3/include") endif() if(UNIX AND NOT APPLE) @@ -122,7 +124,7 @@ endif() if(WIN32) file(GLOB WIN32_PLATFORM_SOURCES "main/platform/win32/*.cpp" "main/platform/win32/*.c") list(APPEND SOURCES ${WIN32_PLATFORM_SOURCES}) - list(APPEND EXTRA_INCLUDES "main/platform/win32") + list(APPEND EXTERNAL_INCLUDES "main/platform/win32") endif() # A hack to make Opus keep quiet @@ -139,7 +141,7 @@ if(ESP_PLATFORM) else() find_package(Threads REQUIRED) find_package(MbedTLS REQUIRED) - list(APPEND EXTRA_INCLUDES ${MBEDTLS_INCLUDE_DIRS}) + list(APPEND EXTERNAL_INCLUDES ${MBEDTLS_INCLUDE_DIRS}) set(THREADS_PREFER_PTHREAD_FLAG ON) list(APPEND EXTRA_LIBS ${MBEDTLS_LIBRARIES} Threads::Threads) @@ -149,6 +151,14 @@ else() endif() endif() +if (NOT BELL_DISABLE_MQTT) + file(GLOB MQTT_SOURCES "external/mqtt/*.c") + list(APPEND SOURCES ${MQTT_SOURCES}) + list(APPEND EXTRA_INCLUDES "external/mqtt/include") +else() + list(REMOVE_ITEM SOURCES "${IO_DIR}/BellMQTTClient.cpp") +endif() + if(NOT BELL_DISABLE_CODECS) file(GLOB EXTRA_SOURCES "main/audio-containers/*.cpp" "main/audio-codec/*.cpp" "main/audio-codec/*.c" "main/audio-dsp/*.cpp" "main/audio-dsp/*.c") @@ -162,7 +172,7 @@ if(NOT BELL_DISABLE_CODECS) if(BELL_CODEC_AAC) file(GLOB LIBHELIX_AAC_SOURCES "external/libhelix-aac/*.c") list(APPEND LIBHELIX_SOURCES ${LIBHELIX_AAC_SOURCES}) - list(APPEND EXTRA_INCLUDES "external/libhelix-aac") + list(APPEND EXTERNAL_INCLUDES "external/libhelix-aac") list(APPEND SOURCES "${AUDIO_CODEC_DIR}/AACDecoder.cpp") list(APPEND CODEC_FLAGS "-DBELL_CODEC_AAC") endif() @@ -171,7 +181,7 @@ if(NOT BELL_DISABLE_CODECS) if(BELL_CODEC_MP3) file(GLOB LIBHELIX_MP3_SOURCES "external/libhelix-mp3/*.c") list(APPEND LIBHELIX_SOURCES ${LIBHELIX_MP3_SOURCES}) - list(APPEND EXTRA_INCLUDES "external/libhelix-mp3") + list(APPEND EXTERNAL_INCLUDES "external/libhelix-mp3") list(APPEND SOURCES "${AUDIO_CODEC_DIR}/MP3Decoder.cpp") list(APPEND CODEC_FLAGS "-DBELL_CODEC_MP3") endif() @@ -230,7 +240,7 @@ else() file(GLOB TREMOR_SOURCES "external/tremor/*.c") list(REMOVE_ITEM TREMOR_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/external/tremor/ivorbisfile_example.c") list(APPEND SOURCES ${TREMOR_SOURCES}) - list(APPEND EXTRA_INCLUDES "external/tremor") + list(APPEND EXTERNAL_INCLUDES "external/tremor") endif() if(NOT BELL_DISABLE_SINKS) @@ -247,7 +257,7 @@ if(NOT BELL_DISABLE_SINKS) # Find ALSA if required, else remove the sink if(BELL_SINK_ALSA) find_package(ALSA REQUIRED) - list(APPEND EXTRA_INCLUDES ${ALSA_INCLUDE_DIRS}) + list(APPEND EXTERNAL_INCLUDES ${ALSA_INCLUDE_DIRS}) list(APPEND EXTRA_LIBS ${ALSA_LIBRARIES}) else() list(REMOVE_ITEM SINK_SOURCES "${AUDIO_SINKS_DIR}/unix/ALSAAudioSink.cpp") @@ -256,7 +266,7 @@ if(NOT BELL_DISABLE_SINKS) # Find PortAudio if required, else remove the sink if(BELL_SINK_PORTAUDIO) find_package(Portaudio REQUIRED) - list(APPEND EXTRA_INCLUDES ${PORTAUDIO_INCLUDE_DIRS}) + list(APPEND EXTERNAL_INCLUDES ${PORTAUDIO_INCLUDE_DIRS}) list(APPEND EXTRA_LIBS ${PORTAUDIO_LIBRARIES}) else() list(REMOVE_ITEM SINK_SOURCES "${AUDIO_SINKS_DIR}/unix/PortAudioSink.cpp") @@ -266,6 +276,7 @@ if(NOT BELL_DISABLE_SINKS) endif() if(NOT BELL_ONLY_CJSON) + set(JSON_SystemInclude ON CACHE INTERNAL "") add_subdirectory(external/nlohmann_json) list(APPEND EXTRA_LIBS nlohmann_json::nlohmann_json) endif() @@ -274,22 +285,25 @@ if(BELL_EXTERNAL_CJSON) list(APPEND EXTRA_LIBS ${BELL_EXTERNAL_CJSON}) else() list(APPEND SOURCES "external/cJSON/cJSON.c") - list(APPEND EXTRA_INCLUDES "external/cJSON") + list(APPEND EXTERNAL_INCLUDES "external/cJSON") endif() if (NOT BELL_DISABLE_FMT) - list(APPEND EXTRA_INCLUDES "external/fmt/include") + list(APPEND EXTERNAL_INCLUDES "external/fmt/include") endif() if(WIN32 OR UNIX) list(APPEND SOURCES "external/mdnssvc/mdns.c" "external/mdnssvc/mdnsd.c") - list(APPEND EXTRA_INCLUDES "external/mdnssvc") + list(APPEND EXTERNAL_INCLUDES "external/mdnssvc") endif() -# file(GLOB CIVET_SRC "external/civetweb/*.c" "external/civetweb/*.inl" "external/civetweb/*.cpp") - -# list(APPEND SOURCES ${CIVET_SRC}) -# list(APPEND EXTRA_INCLUDES "external/civetweb/include") +if(NOT BELL_DISABLE_WEBSERVER) + file(GLOB CIVET_SRC "external/civetweb/*.c" "external/civetweb/*.inl" "external/civetweb/*.cpp") + list(APPEND SOURCES ${CIVET_SRC}) + list(APPEND EXTRA_INCLUDES "external/civetweb/include") +else() + list(REMOVE_ITEM SOURCES "${IO_DIR}/BellHTTPServer.cpp") +endif() add_library(bell STATIC ${SOURCES}) @@ -305,6 +319,7 @@ endif() # PUBLIC to propagate esp-idf includes to bell dependents target_link_libraries(bell PUBLIC ${EXTRA_LIBS}) target_include_directories(bell PUBLIC ${EXTRA_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR}) +target_include_directories(bell SYSTEM PUBLIC ${EXTERNAL_INCLUDES}) target_compile_definitions(bell PUBLIC PB_ENABLE_MALLOC FMT_HEADER_ONLY) if(BELL_DISABLE_CODECS) diff --git a/components/spotify/cspot/bell/example/CMakeLists.txt b/components/spotify/cspot/bell/example/CMakeLists.txt index 4e1466fc..29fee79b 100644 --- a/components/spotify/cspot/bell/example/CMakeLists.txt +++ b/components/spotify/cspot/bell/example/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.18) set(CMAKE_CXX_STANDARD 20) set(CMAKE_BUILD_TYPE Debug) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - +set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../ ${CMAKE_CURRENT_BINARY_DIR}/bell) file(GLOB SOURCES "*.cpp") diff --git a/components/spotify/cspot/bell/example/main.cpp b/components/spotify/cspot/bell/example/main.cpp index 6d3a6129..0f043125 100644 --- a/components/spotify/cspot/bell/example/main.cpp +++ b/components/spotify/cspot/bell/example/main.cpp @@ -1,26 +1,14 @@ -#include #include -#include -#include -#include -#include #include -#include -#include "AudioCodecs.h" -#include "AudioContainers.h" -#include "BellHTTPServer.h" -#include "BellTar.h" +#include +#include + #include "BellTask.h" #include "CentralAudioBuffer.h" -#include "Compressor.h" -#include "DecoderGlobals.h" -#include "EncodedAudioStream.h" -#include "HTTPClient.h" #include "PortAudioSink.h" -#define DEBUG_LEVEL 4 -#include "X509Bundle.h" -#include "mbedtls/debug.h" +#include "StreamInfo.h" +#define DEBUG_LEVEL 4 #include #include @@ -58,13 +46,8 @@ class AudioPlayer : bell::Task { int main() { bell::setDefaultLogger(); - std::fstream file("system.tar", std::ios::in | std::ios::binary); - if (!file.is_open()) { - std::cout << "file not open" << std::endl; - return 1; - } - BellTar::reader reader(file); - reader.extract_all_files("./dupa2"); + BELL_LOG(info, "cock", "Published?"); + return 0; } diff --git a/components/spotify/cspot/bell/external/nanopb/extra/FindNanopb.cmake b/components/spotify/cspot/bell/external/nanopb/extra/FindNanopb.cmake index 3f82b7b8..d8373926 100644 --- a/components/spotify/cspot/bell/external/nanopb/extra/FindNanopb.cmake +++ b/components/spotify/cspot/bell/external/nanopb/extra/FindNanopb.cmake @@ -118,12 +118,15 @@ # #============================================================================= - function(NANOPB_GENERATE_CPP SRCS HDRS) cmake_parse_arguments(NANOPB_GENERATE_CPP "" "RELPATH" "" ${ARGN}) if(NOT NANOPB_GENERATE_CPP_UNPARSED_ARGUMENTS) return() endif() + + if(MSVC) + set(CUSTOM_COMMAND_PREFIX call) + endif() if(NANOPB_GENERATE_CPP_APPEND_PATH) # Create an include path for each file specified @@ -184,7 +187,7 @@ function(NANOPB_GENERATE_CPP SRCS HDRS) set(GENERATOR_CORE_PYTHON_SRC ${GENERATOR_CORE_PYTHON_SRC} ${output}) add_custom_command( OUTPUT ${output} - COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} + COMMAND ${CUSTOM_COMMAND_PREFIX} ${PROTOBUF_PROTOC_EXECUTABLE} ARGS -I${GENERATOR_PATH}/proto --python_out=${GENERATOR_CORE_DIR} ${ABS_FIL} DEPENDS ${ABS_FIL} @@ -276,7 +279,7 @@ function(NANOPB_GENERATE_CPP SRCS HDRS) add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${FIL_PATH_REL}/${FIL_WE}.pb.c" "${CMAKE_CURRENT_BINARY_DIR}/${FIL_PATH_REL}/${FIL_WE}.pb.h" - COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} + COMMAND ${CUSTOM_COMMAND_PREFIX} ${PROTOBUF_PROTOC_EXECUTABLE} ARGS -I${GENERATOR_PATH} -I${GENERATOR_CORE_DIR} -I${CMAKE_CURRENT_BINARY_DIR} ${_nanopb_include_path} --plugin=protoc-gen-nanopb=${NANOPB_GENERATOR_PLUGIN} @@ -292,6 +295,10 @@ function(NANOPB_GENERATE_CPP SRCS HDRS) set_source_files_properties(${${SRCS}} ${${HDRS}} PROPERTIES GENERATED TRUE) set(${SRCS} ${${SRCS}} ${NANOPB_SRCS} PARENT_SCOPE) set(${HDRS} ${${HDRS}} ${NANOPB_HDRS} PARENT_SCOPE) + + if(MSVC) + unset(CUSTOM_COMMAND_PREFIX) + endif() endfunction() diff --git a/components/spotify/cspot/bell/main/audio-codec/AACDecoder.cpp b/components/spotify/cspot/bell/main/audio-codec/AACDecoder.cpp index bf8d9d25..b6565b97 100644 --- a/components/spotify/cspot/bell/main/audio-codec/AACDecoder.cpp +++ b/components/spotify/cspot/bell/main/audio-codec/AACDecoder.cpp @@ -1,5 +1,12 @@ #include "AACDecoder.h" -#include + +#include // for free, malloc + +#include "CodecType.h" // for bell + +namespace bell { +class AudioContainer; +} // namespace bell using namespace bell; diff --git a/components/spotify/cspot/bell/main/audio-codec/AudioCodecs.cpp b/components/spotify/cspot/bell/main/audio-codec/AudioCodecs.cpp index 71215997..a21e513a 100644 --- a/components/spotify/cspot/bell/main/audio-codec/AudioCodecs.cpp +++ b/components/spotify/cspot/bell/main/audio-codec/AudioCodecs.cpp @@ -1,27 +1,37 @@ #include "AudioCodecs.h" -#include -#include -#include + +#include // for map, operator!=, map<>::iterator, map<>:... +#include // for remove_extent_t + +#include "AudioContainer.h" // for AudioContainer + +namespace bell { +class BaseCodec; +} // namespace bell using namespace bell; #ifdef BELL_CODEC_AAC -#include "AACDecoder.h" +#include "AACDecoder.h" // for AACDecoder + static std::shared_ptr codecAac; #endif #ifdef BELL_CODEC_MP3 -#include "MP3Decoder.h" +#include "MP3Decoder.h" // for MP3Decoder + static std::shared_ptr codecMp3; #endif #ifdef BELL_CODEC_VORBIS -#include "VorbisDecoder.h" +#include "VorbisDecoder.h" // for VorbisDecoder + static std::shared_ptr codecVorbis; #endif #ifdef BELL_CODEC_OPUS -#include "OPUSDecoder.h" +#include "OPUSDecoder.h" // for OPUSDecoder + static std::shared_ptr codecOpus; #endif diff --git a/components/spotify/cspot/bell/main/audio-codec/BaseCodec.cpp b/components/spotify/cspot/bell/main/audio-codec/BaseCodec.cpp index 12c38a71..46d5f8ad 100644 --- a/components/spotify/cspot/bell/main/audio-codec/BaseCodec.cpp +++ b/components/spotify/cspot/bell/main/audio-codec/BaseCodec.cpp @@ -1,5 +1,7 @@ #include "BaseCodec.h" -#include + +#include "AudioContainer.h" // for AudioContainer +#include "CodecType.h" // for bell using namespace bell; diff --git a/components/spotify/cspot/bell/main/audio-codec/DecoderGlobals.cpp b/components/spotify/cspot/bell/main/audio-codec/DecoderGlobals.cpp index f7c04e5e..037a5a30 100644 --- a/components/spotify/cspot/bell/main/audio-codec/DecoderGlobals.cpp +++ b/components/spotify/cspot/bell/main/audio-codec/DecoderGlobals.cpp @@ -2,7 +2,6 @@ bell::DecodersInstance* bell::decodersInstance; -void bell::createDecoders() -{ - bell::decodersInstance = new bell::DecodersInstance(); +void bell::createDecoders() { + bell::decodersInstance = new bell::DecodersInstance(); } \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-codec/MP3Decoder.cpp b/components/spotify/cspot/bell/main/audio-codec/MP3Decoder.cpp index 7a5713a4..5293d0ef 100644 --- a/components/spotify/cspot/bell/main/audio-codec/MP3Decoder.cpp +++ b/components/spotify/cspot/bell/main/audio-codec/MP3Decoder.cpp @@ -1,5 +1,13 @@ #include "MP3Decoder.h" +#include // for free, malloc + +#include "CodecType.h" // for bell + +namespace bell { +class AudioContainer; +} // namespace bell + using namespace bell; MP3Decoder::MP3Decoder() { diff --git a/components/spotify/cspot/bell/main/audio-codec/OPUSDecoder.cpp b/components/spotify/cspot/bell/main/audio-codec/OPUSDecoder.cpp index 5c3d77d9..0bd3529b 100644 --- a/components/spotify/cspot/bell/main/audio-codec/OPUSDecoder.cpp +++ b/components/spotify/cspot/bell/main/audio-codec/OPUSDecoder.cpp @@ -1,5 +1,9 @@ #include "OPUSDecoder.h" -#include "opus.h" + +#include // for free, malloc + +#include "CodecType.h" // for bell +#include "opus.h" // for opus_decoder_destroy, opus_decode, opus_decod... using namespace bell; diff --git a/components/spotify/cspot/bell/main/audio-codec/VorbisDecoder.cpp b/components/spotify/cspot/bell/main/audio-codec/VorbisDecoder.cpp index a2fc685d..a4902fad 100644 --- a/components/spotify/cspot/bell/main/audio-codec/VorbisDecoder.cpp +++ b/components/spotify/cspot/bell/main/audio-codec/VorbisDecoder.cpp @@ -1,5 +1,13 @@ #include "VorbisDecoder.h" -#include "AudioCodecs.h" + +#include // for free, malloc + +#include "CodecType.h" // for bell +#include "config_types.h" // for ogg_int16_t + +namespace bell { +class AudioContainer; +} // namespace bell using namespace bell; diff --git a/components/spotify/cspot/bell/main/audio-codec/include/AACDecoder.h b/components/spotify/cspot/bell/main/audio-codec/include/AACDecoder.h index 6c1c7eab..e44b101c 100644 --- a/components/spotify/cspot/bell/main/audio-codec/include/AACDecoder.h +++ b/components/spotify/cspot/bell/main/audio-codec/include/AACDecoder.h @@ -1,9 +1,12 @@ #pragma once -#include "BaseCodec.h" -#include "aacdec.h" +#include // for uint8_t, uint32_t, int16_t + +#include "BaseCodec.h" // for BaseCodec +#include "aacdec.h" // for AACFrameInfo, HAACDecoder namespace bell { +class AudioContainer; class AACDecoder : public BaseCodec { private: diff --git a/components/spotify/cspot/bell/main/audio-codec/include/AudioCodecs.h b/components/spotify/cspot/bell/main/audio-codec/include/AudioCodecs.h index a230cb1a..2d5de6a7 100644 --- a/components/spotify/cspot/bell/main/audio-codec/include/AudioCodecs.h +++ b/components/spotify/cspot/bell/main/audio-codec/include/AudioCodecs.h @@ -1,11 +1,12 @@ #pragma once -#include -#include "BaseCodec.h" -#include "AudioContainer.h" +#include // for shared_ptr + +#include "AudioContainer.h" // for AudioContainer +#include "BaseCodec.h" // for BaseCodec +#include "CodecType.h" // for AudioCodec namespace bell { - class AudioCodecs { public: static std::shared_ptr getCodec(AudioCodec type); diff --git a/components/spotify/cspot/bell/main/audio-codec/include/BaseCodec.h b/components/spotify/cspot/bell/main/audio-codec/include/BaseCodec.h index 59d533ab..8d6270bf 100644 --- a/components/spotify/cspot/bell/main/audio-codec/include/BaseCodec.h +++ b/components/spotify/cspot/bell/main/audio-codec/include/BaseCodec.h @@ -1,8 +1,9 @@ #pragma once -#include "AudioContainer.h" +#include // for uint32_t, uint8_t namespace bell { +class AudioContainer; class BaseCodec { private: diff --git a/components/spotify/cspot/bell/main/audio-codec/include/DecoderGlobals.h b/components/spotify/cspot/bell/main/audio-codec/include/DecoderGlobals.h index 5a03a3a5..f8335a25 100644 --- a/components/spotify/cspot/bell/main/audio-codec/include/DecoderGlobals.h +++ b/components/spotify/cspot/bell/main/audio-codec/include/DecoderGlobals.h @@ -5,48 +5,40 @@ #define AAC_READBUF_SIZE (4 * AAC_MAINBUF_SIZE * AAC_MAX_NCHANS) #define MP3_READBUF_SIZE (2 * 1024); -#include -#include -#include -#include "aacdec.h" -#include "mp3dec.h" +#include // for NULL -namespace bell -{ - class DecodersInstance - { - public: - DecodersInstance(){}; - ~DecodersInstance() - { - MP3FreeDecoder(mp3Decoder); - AACFreeDecoder(aacDecoder); - }; +#include "aacdec.h" // for AACFreeDecoder, AACInitDecoder, HAACDecoder +#include "mp3dec.h" // for MP3FreeDecoder, MP3InitDecoder, HMP3Decoder - HAACDecoder aacDecoder = NULL; - HMP3Decoder mp3Decoder = NULL; +namespace bell { +class DecodersInstance { + public: + DecodersInstance(){}; + ~DecodersInstance() { + MP3FreeDecoder(mp3Decoder); + AACFreeDecoder(aacDecoder); + }; - void ensureAAC() - { - if (aacDecoder == NULL) - { - aacDecoder = AACInitDecoder(); - } - } + HAACDecoder aacDecoder = NULL; + HMP3Decoder mp3Decoder = NULL; - void ensureMP3() - { - if (mp3Decoder == NULL) - { - mp3Decoder = MP3InitDecoder(); - } - } - }; + void ensureAAC() { + if (aacDecoder == NULL) { + aacDecoder = AACInitDecoder(); + } + } - extern bell::DecodersInstance* decodersInstance; + void ensureMP3() { + if (mp3Decoder == NULL) { + mp3Decoder = MP3InitDecoder(); + } + } +}; - void createDecoders(); -} +extern bell::DecodersInstance* decodersInstance; + +void createDecoders(); +} // namespace bell #endif #endif diff --git a/components/spotify/cspot/bell/main/audio-codec/include/MP3Decoder.h b/components/spotify/cspot/bell/main/audio-codec/include/MP3Decoder.h index a9509c57..f38df171 100644 --- a/components/spotify/cspot/bell/main/audio-codec/include/MP3Decoder.h +++ b/components/spotify/cspot/bell/main/audio-codec/include/MP3Decoder.h @@ -1,9 +1,13 @@ #pragma once -#include "BaseCodec.h" -#include "mp3dec.h" +#include // for uint8_t, uint32_t, int16_t + +#include "BaseCodec.h" // for BaseCodec +#include "mp3dec.h" // for HMP3Decoder, MP3FrameInfo namespace bell { +class AudioContainer; + class MP3Decoder : public BaseCodec { private: HMP3Decoder mp3; diff --git a/components/spotify/cspot/bell/main/audio-codec/include/OPUSDecoder.h b/components/spotify/cspot/bell/main/audio-codec/include/OPUSDecoder.h index 13780d20..49c78ca7 100644 --- a/components/spotify/cspot/bell/main/audio-codec/include/OPUSDecoder.h +++ b/components/spotify/cspot/bell/main/audio-codec/include/OPUSDecoder.h @@ -1,6 +1,8 @@ #pragma once -#include "BaseCodec.h" +#include // for uint8_t, uint32_t, int16_t + +#include "BaseCodec.h" // for BaseCodec struct OpusDecoder; diff --git a/components/spotify/cspot/bell/main/audio-codec/include/VorbisDecoder.h b/components/spotify/cspot/bell/main/audio-codec/include/VorbisDecoder.h index 05a8d030..f7ed6285 100644 --- a/components/spotify/cspot/bell/main/audio-codec/include/VorbisDecoder.h +++ b/components/spotify/cspot/bell/main/audio-codec/include/VorbisDecoder.h @@ -1,9 +1,14 @@ #pragma once -#include "BaseCodec.h" -#include "ivorbiscodec.h" +#include // for uint8_t, uint32_t, int16_t + +#include "BaseCodec.h" // for BaseCodec +#include "ivorbiscodec.h" // for vorbis_comment, vorbis_dsp_state, vorbis_info +#include "ogg.h" // for ogg_packet namespace bell { +class AudioContainer; + class VorbisDecoder : public BaseCodec { private: vorbis_info* vi = nullptr; diff --git a/components/spotify/cspot/bell/main/audio-containers/AACContainer.cpp b/components/spotify/cspot/bell/main/audio-containers/AACContainer.cpp index b4c8f18f..314fd999 100644 --- a/components/spotify/cspot/bell/main/audio-containers/AACContainer.cpp +++ b/components/spotify/cspot/bell/main/audio-containers/AACContainer.cpp @@ -1,5 +1,10 @@ #include "AACContainer.h" -#include "iostream" + +#include // for memmove + +#include "StreamInfo.h" // for BitWidth, BitWidth::BW_16, SampleRate, Sampl... +#include "aacdec.h" // for AACFindSyncWord + using namespace bell; #define SYNC_WORLD_LEN 4 diff --git a/components/spotify/cspot/bell/main/audio-containers/AudioContainers.cpp b/components/spotify/cspot/bell/main/audio-containers/AudioContainers.cpp index 68940a51..bd8f45b5 100644 --- a/components/spotify/cspot/bell/main/audio-containers/AudioContainers.cpp +++ b/components/spotify/cspot/bell/main/audio-containers/AudioContainers.cpp @@ -1,5 +1,16 @@ #include "AudioContainers.h" +#include // for memcmp +#include // for byte + +#include "AACContainer.h" // for AACContainer +#include "CodecType.h" // for bell +#include "MP3Container.h" // for MP3Container + +namespace bell { +class AudioContainer; +} // namespace bell + using namespace bell; std::unique_ptr AudioContainers::guessAudioContainer( @@ -7,8 +18,7 @@ std::unique_ptr AudioContainers::guessAudioContainer( std::byte tmp[14]; istr.read((char*)tmp, sizeof(tmp)); - if (memcmp(tmp, "\xFF\xF1", 2) == 0 || - memcmp(tmp, "\xFF\xF9", 2) == 0) { + if (memcmp(tmp, "\xFF\xF1", 2) == 0 || memcmp(tmp, "\xFF\xF9", 2) == 0) { // AAC found std::cout << "AAC" << std::endl; return std::make_unique(istr); diff --git a/components/spotify/cspot/bell/main/audio-containers/MP3Container.cpp b/components/spotify/cspot/bell/main/audio-containers/MP3Container.cpp index 08629464..98bb6b66 100644 --- a/components/spotify/cspot/bell/main/audio-containers/MP3Container.cpp +++ b/components/spotify/cspot/bell/main/audio-containers/MP3Container.cpp @@ -1,5 +1,10 @@ #include "MP3Container.h" +#include // for memmove + +#include "StreamInfo.h" // for BitWidth, BitWidth::BW_16, SampleRate, Sampl... +#include "mp3dec.h" // for MP3FindSyncWord + using namespace bell; MP3Container::MP3Container(std::istream& istr) : bell::AudioContainer(istr) {} diff --git a/components/spotify/cspot/bell/main/audio-containers/include/AACContainer.h b/components/spotify/cspot/bell/main/audio-containers/include/AACContainer.h index d283c0c1..da8d9443 100644 --- a/components/spotify/cspot/bell/main/audio-containers/include/AACContainer.h +++ b/components/spotify/cspot/bell/main/audio-containers/include/AACContainer.h @@ -1,10 +1,12 @@ #pragma once -#include -#include -#include -#include "AudioContainer.h" -#include "aacdec.h" +#include // for uint32_t +#include // for byte, size_t +#include // for istream +#include // for vector + +#include "AudioContainer.h" // for AudioContainer +#include "CodecType.h" // for AudioCodec, AudioCodec::AAC namespace bell { class AACContainer : public AudioContainer { diff --git a/components/spotify/cspot/bell/main/audio-containers/include/AudioContainer.h b/components/spotify/cspot/bell/main/audio-containers/include/AudioContainer.h index 38648a0e..2c25cb96 100644 --- a/components/spotify/cspot/bell/main/audio-containers/include/AudioContainer.h +++ b/components/spotify/cspot/bell/main/audio-containers/include/AudioContainer.h @@ -1,8 +1,8 @@ #pragma once #include -#include #include +#include #include "CodecType.h" #include "StreamInfo.h" diff --git a/components/spotify/cspot/bell/main/audio-containers/include/AudioContainers.h b/components/spotify/cspot/bell/main/audio-containers/include/AudioContainers.h index a4c0e3af..a6369e1f 100644 --- a/components/spotify/cspot/bell/main/audio-containers/include/AudioContainers.h +++ b/components/spotify/cspot/bell/main/audio-containers/include/AudioContainers.h @@ -1,10 +1,11 @@ #pragma once -#include -#include -#include "AACContainer.h" -#include "AudioContainer.h" -#include "MP3Container.h" +#include // for istream +#include // for unique_ptr + +namespace bell { +class AudioContainer; +} // namespace bell namespace bell::AudioContainers { std::unique_ptr guessAudioContainer(std::istream& istr); diff --git a/components/spotify/cspot/bell/main/audio-containers/include/MP3Container.h b/components/spotify/cspot/bell/main/audio-containers/include/MP3Container.h index 028e17b6..d4bbfd0e 100644 --- a/components/spotify/cspot/bell/main/audio-containers/include/MP3Container.h +++ b/components/spotify/cspot/bell/main/audio-containers/include/MP3Container.h @@ -1,10 +1,12 @@ #pragma once -#include -#include -#include -#include "AudioContainer.h" -#include "mp3dec.h" +#include // for uint32_t +#include // for byte, size_t +#include // for istream +#include // for vector + +#include "AudioContainer.h" // for AudioContainer +#include "CodecType.h" // for AudioCodec, AudioCodec::MP3 namespace bell { class MP3Container : public AudioContainer { diff --git a/components/spotify/cspot/bell/main/audio-dsp/AudioMixer.cpp b/components/spotify/cspot/bell/main/audio-dsp/AudioMixer.cpp index 1c0c486a..fd0f7e4d 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/AudioMixer.cpp +++ b/components/spotify/cspot/bell/main/audio-dsp/AudioMixer.cpp @@ -1,39 +1,44 @@ #include "AudioMixer.h" +#include // for scoped_lock + using namespace bell; -AudioMixer::AudioMixer() { -} +AudioMixer::AudioMixer() {} -std::unique_ptr AudioMixer::process(std::unique_ptr info) { - std::scoped_lock lock(this->accessMutex); - if (info->numChannels != from) { - throw std::runtime_error("AudioMixer: Input channel count does not match configuration"); - } - info->numChannels = to; +std::unique_ptr AudioMixer::process( + std::unique_ptr info) { + std::scoped_lock lock(this->accessMutex); + if (info->numChannels != from) { + throw std::runtime_error( + "AudioMixer: Input channel count does not match configuration"); + } + info->numChannels = to; - for (auto &singleConf : mixerConfig) { - if (singleConf.source.size() == 1) { - if (singleConf.source[0] == singleConf.destination) { - continue; - } - // Copy channel - for (int i = 0; i < info->numSamples; i++) { - info->data[singleConf.destination][i] = info->data[singleConf.source[0]][i]; - } - } else { - // Mix channels - float sample = 0.0f; - for (int i = 0; i < info->numSamples; i++) { - sample = 0.0; - for (auto &source : singleConf.source) { - sample += info->data[source][i]; - } - - info->data[singleConf.destination][i] = sample / (float) singleConf.source.size(); - } + for (auto& singleConf : mixerConfig) { + if (singleConf.source.size() == 1) { + if (singleConf.source[0] == singleConf.destination) { + continue; + } + // Copy channel + for (int i = 0; i < info->numSamples; i++) { + info->data[singleConf.destination][i] = + info->data[singleConf.source[0]][i]; + } + } else { + // Mix channels + float sample = 0.0f; + for (int i = 0; i < info->numSamples; i++) { + sample = 0.0; + for (auto& source : singleConf.source) { + sample += info->data[source][i]; } - } - return info; + info->data[singleConf.destination][i] = + sample / (float)singleConf.source.size(); + } + } + } + + return info; } \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-dsp/AudioPipeline.cpp b/components/spotify/cspot/bell/main/audio-dsp/AudioPipeline.cpp index 23b39fc5..853e1f2b 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/AudioPipeline.cpp +++ b/components/spotify/cspot/bell/main/audio-dsp/AudioPipeline.cpp @@ -1,47 +1,53 @@ #include "AudioPipeline.h" -#include -#include "BellLogger.h" + +#include // for remove_extent_t +#include // for move + +#include "AudioTransform.h" // for AudioTransform +#include "BellLogger.h" // for AbstractLogger, BELL_LOG +#include "TransformConfig.h" // for TransformConfig using namespace bell; -AudioPipeline::AudioPipeline() { +AudioPipeline::AudioPipeline(){ // this->headroomGainTransform = std::make_shared(Channels::LEFT_RIGHT); // this->transforms.push_back(this->headroomGainTransform); }; void AudioPipeline::addTransform(std::shared_ptr transform) { - transforms.push_back(transform); - recalculateHeadroom(); + transforms.push_back(transform); + recalculateHeadroom(); } void AudioPipeline::recalculateHeadroom() { - float headroom = 0.0f; + float headroom = 0.0f; - // Find largest headroom required by any transform down the chain, and apply it - for (auto transform : transforms) { - if (headroom < transform->calculateHeadroom()) { - headroom = transform->calculateHeadroom(); - } + // Find largest headroom required by any transform down the chain, and apply it + for (auto transform : transforms) { + if (headroom < transform->calculateHeadroom()) { + headroom = transform->calculateHeadroom(); } + } - // headroomGainTransform->configure(-headroom); + // headroomGainTransform->configure(-headroom); } void AudioPipeline::volumeUpdated(int volume) { - BELL_LOG(debug, "AudioPipeline", "Requested"); - std::scoped_lock lock(this->accessMutex); - for (auto transform : transforms) { - transform->config->currentVolume = volume; - transform->reconfigure(); - } - BELL_LOG(debug, "AudioPipeline", "Volume applied, DSP reconfigured"); + BELL_LOG(debug, "AudioPipeline", "Requested"); + std::scoped_lock lock(this->accessMutex); + for (auto transform : transforms) { + transform->config->currentVolume = volume; + transform->reconfigure(); + } + BELL_LOG(debug, "AudioPipeline", "Volume applied, DSP reconfigured"); } -std::unique_ptr AudioPipeline::process(std::unique_ptr data) { - std::scoped_lock lock(this->accessMutex); - for (auto &transform : transforms) { - data = transform->process(std::move(data)); - } +std::unique_ptr AudioPipeline::process( + std::unique_ptr data) { + std::scoped_lock lock(this->accessMutex); + for (auto& transform : transforms) { + data = transform->process(std::move(data)); + } - return data; + return data; } \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-dsp/BellDSP.cpp b/components/spotify/cspot/bell/main/audio-dsp/BellDSP.cpp index 56d1d3ac..d6f04272 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/BellDSP.cpp +++ b/components/spotify/cspot/bell/main/audio-dsp/BellDSP.cpp @@ -1,6 +1,10 @@ #include "BellDSP.h" -#include -#include "CentralAudioBuffer.h" + +#include // for remove_extent_t +#include // for move + +#include "AudioPipeline.h" // for CentralAudioBuffer +#include "CentralAudioBuffer.h" // for CentralAudioBuffer using namespace bell; diff --git a/components/spotify/cspot/bell/main/audio-dsp/Biquad.cpp b/components/spotify/cspot/bell/main/audio-dsp/Biquad.cpp index 20391fa0..858593bd 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/Biquad.cpp +++ b/components/spotify/cspot/bell/main/audio-dsp/Biquad.cpp @@ -1,466 +1,439 @@ #include "Biquad.h" +#include // for pow, cosf, sinf, M_PI, sqrtf, tanf, logf, sinh + using namespace bell; -Biquad::Biquad() -{ - this->filterType = "biquad"; +Biquad::Biquad() { + this->filterType = "biquad"; } -void Biquad::sampleRateChanged(uint32_t sampleRate) -{ - this->sampleRate = sampleRate; - //this->configure(this->type, this->currentConfig); +void Biquad::sampleRateChanged(uint32_t sampleRate) { + this->sampleRate = sampleRate; + //this->configure(this->type, this->currentConfig); } -void Biquad::configure(Type type, std::map &newConf) -{ - this->type = type; - this->currentConfig = newConf; +void Biquad::configure(Type type, std::map& newConf) { + this->type = type; + this->currentConfig = newConf; - switch (type) - { + switch (type) { case Type::Free: - coeffs[0] = newConf["a1"]; - coeffs[1] = newConf["a2"]; - coeffs[2] = newConf["b0"]; - coeffs[3] = newConf["b1"]; - coeffs[4] = newConf["b2"]; - break; + coeffs[0] = newConf["a1"]; + coeffs[1] = newConf["a2"]; + coeffs[2] = newConf["b0"]; + coeffs[3] = newConf["b1"]; + coeffs[4] = newConf["b2"]; + break; case Type::Highpass: - highPassCoEffs(newConf["freq"], newConf["q"]); - break; + highPassCoEffs(newConf["freq"], newConf["q"]); + break; case Type::HighpassFO: - highPassFOCoEffs(newConf["freq"]); - break; + highPassFOCoEffs(newConf["freq"]); + break; case Type::Lowpass: - lowPassCoEffs(newConf["freq"], newConf["q"]); - break; + lowPassCoEffs(newConf["freq"], newConf["q"]); + break; case Type::LowpassFO: - lowPassFOCoEffs(newConf["freq"]); - break; + lowPassFOCoEffs(newConf["freq"]); + break; case Type::Highshelf: - // check if config has slope key - if (newConf.find("slope") != newConf.end()) - { - highShelfCoEffsSlope(newConf["freq"], newConf["gain"], newConf["slope"]); - } - else - { - highShelfCoEffs(newConf["freq"], newConf["gain"], newConf["q"]); - } - break; + // check if config has slope key + if (newConf.find("slope") != newConf.end()) { + highShelfCoEffsSlope(newConf["freq"], newConf["gain"], + newConf["slope"]); + } else { + highShelfCoEffs(newConf["freq"], newConf["gain"], newConf["q"]); + } + break; case Type::HighshelfFO: - highShelfFOCoEffs(newConf["freq"], newConf["gain"]); - break; + highShelfFOCoEffs(newConf["freq"], newConf["gain"]); + break; case Type::Lowshelf: - // check if config has slope key - if (newConf.find("slope") != newConf.end()) - { - lowShelfCoEffsSlope(newConf["freq"], newConf["gain"], newConf["slope"]); - } - else - { - lowShelfCoEffs(newConf["freq"], newConf["gain"], newConf["q"]); - } - break; + // check if config has slope key + if (newConf.find("slope") != newConf.end()) { + lowShelfCoEffsSlope(newConf["freq"], newConf["gain"], newConf["slope"]); + } else { + lowShelfCoEffs(newConf["freq"], newConf["gain"], newConf["q"]); + } + break; case Type::LowshelfFO: - lowShelfFOCoEffs(newConf["freq"], newConf["gain"]); - break; + lowShelfFOCoEffs(newConf["freq"], newConf["gain"]); + break; case Type::Peaking: - // check if config has bandwidth key - if (newConf.find("bandwidth") != newConf.end()) - { - peakCoEffsBandwidth(newConf["freq"], newConf["gain"], newConf["bandwidth"]); - } - else - { - peakCoEffs(newConf["freq"], newConf["gain"], newConf["q"]); - } - break; + // check if config has bandwidth key + if (newConf.find("bandwidth") != newConf.end()) { + peakCoEffsBandwidth(newConf["freq"], newConf["gain"], + newConf["bandwidth"]); + } else { + peakCoEffs(newConf["freq"], newConf["gain"], newConf["q"]); + } + break; case Type::Notch: - if (newConf.find("bandwidth") != newConf.end()) - { - notchCoEffsBandwidth(newConf["freq"], newConf["gain"], newConf["bandwidth"]); - } - else - { - notchCoEffs(newConf["freq"], newConf["gain"], newConf["q"]); - } - break; + if (newConf.find("bandwidth") != newConf.end()) { + notchCoEffsBandwidth(newConf["freq"], newConf["gain"], + newConf["bandwidth"]); + } else { + notchCoEffs(newConf["freq"], newConf["gain"], newConf["q"]); + } + break; case Type::Bandpass: - if (newConf.find("bandwidth") != newConf.end()) - { - bandPassCoEffsBandwidth(newConf["freq"], newConf["bandwidth"]); - } - else - { - bandPassCoEffs(newConf["freq"], newConf["q"]); - } - break; + if (newConf.find("bandwidth") != newConf.end()) { + bandPassCoEffsBandwidth(newConf["freq"], newConf["bandwidth"]); + } else { + bandPassCoEffs(newConf["freq"], newConf["q"]); + } + break; case Type::Allpass: - if (newConf.find("bandwidth") != newConf.end()) - { - allPassCoEffsBandwidth(newConf["freq"], newConf["bandwidth"]); - } - else - { - allPassCoEffs(newConf["freq"], newConf["q"]); - } - break; + if (newConf.find("bandwidth") != newConf.end()) { + allPassCoEffsBandwidth(newConf["freq"], newConf["bandwidth"]); + } else { + allPassCoEffs(newConf["freq"], newConf["q"]); + } + break; case Type::AllpassFO: - allPassFOCoEffs(newConf["freq"]); - break; - } + allPassFOCoEffs(newConf["freq"]); + break; + } } // coefficients for a high pass biquad filter -void Biquad::highPassCoEffs(float f, float q) -{ - float w0 = 2 * M_PI * f / this->sampleRate; - float c = cosf(w0); - float s = sinf(w0); - float alpha = s / (2 * q); +void Biquad::highPassCoEffs(float f, float q) { + float w0 = 2 * M_PI * f / this->sampleRate; + float c = cosf(w0); + float s = sinf(w0); + float alpha = s / (2 * q); - float b0 = (1 + c) / 2; - float b1 = -(1 + c); - float b2 = b0; - float a0 = 1 + alpha; - float a1 = -2 * c; - float a2 = 1 - alpha; + float b0 = (1 + c) / 2; + float b1 = -(1 + c); + float b2 = b0; + float a0 = 1 + alpha; + float a1 = -2 * c; + float a2 = 1 - alpha; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } // coefficients for a high pass first order biquad filter -void Biquad::highPassFOCoEffs(float f) -{ - float w0 = 2 * M_PI * f / this->sampleRate; - float k = tanf(w0 / 2.0); +void Biquad::highPassFOCoEffs(float f) { + float w0 = 2 * M_PI * f / this->sampleRate; + float k = tanf(w0 / 2.0); - float alpha = 1.0 + k; + float alpha = 1.0 + k; - float b0 = 1.0 / alpha; - float b1 = -1.0 / alpha; - float b2 = 0.0; - float a0 = 1.0; - float a1 = -(1.0 - k) / alpha; - float a2 = 0.0; + float b0 = 1.0 / alpha; + float b1 = -1.0 / alpha; + float b2 = 0.0; + float a0 = 1.0; + float a1 = -(1.0 - k) / alpha; + float a2 = 0.0; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } // coefficients for a low pass biquad filter -void Biquad::lowPassCoEffs(float f, float q) -{ - float w0 = 2 * M_PI * f / this->sampleRate; - float c = cosf(w0); - float s = sinf(w0); - float alpha = s / (2 * q); +void Biquad::lowPassCoEffs(float f, float q) { + float w0 = 2 * M_PI * f / this->sampleRate; + float c = cosf(w0); + float s = sinf(w0); + float alpha = s / (2 * q); - float b0 = (1 - c) / 2; - float b1 = 1 - c; - float b2 = b0; - float a0 = 1 + alpha; - float a1 = -2 * c; - float a2 = 1 - alpha; + float b0 = (1 - c) / 2; + float b1 = 1 - c; + float b2 = b0; + float a0 = 1 + alpha; + float a1 = -2 * c; + float a2 = 1 - alpha; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } // coefficients for a low pass first order biquad filter -void Biquad::lowPassFOCoEffs(float f) -{ - float w0 = 2 * M_PI * f / this->sampleRate; - float k = tanf(w0 / 2.0); +void Biquad::lowPassFOCoEffs(float f) { + float w0 = 2 * M_PI * f / this->sampleRate; + float k = tanf(w0 / 2.0); - float alpha = 1.0 + k; + float alpha = 1.0 + k; - float b0 = k / alpha; - float b1 = k / alpha; - float b2 = 0.0; - float a0 = 1.0; - float a1 = -(1.0 - k) / alpha; - float a2 = 0.0; + float b0 = k / alpha; + float b1 = k / alpha; + float b2 = 0.0; + float a0 = 1.0; + float a1 = -(1.0 - k) / alpha; + float a2 = 0.0; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } // coefficients for a peak biquad filter -void Biquad::peakCoEffs(float f, float gain, float q) -{ - float w0 = 2 * M_PI * f / this->sampleRate; - float c = cosf(w0); - float s = sinf(w0); - float alpha = s / (2 * q); +void Biquad::peakCoEffs(float f, float gain, float q) { + float w0 = 2 * M_PI * f / this->sampleRate; + float c = cosf(w0); + float s = sinf(w0); + float alpha = s / (2 * q); - float ampl = std::pow(10.0f, gain / 40.0f); - float b0 = 1.0 + (alpha * ampl); - float b1 = -2.0 * c; - float b2 = 1.0 - (alpha * ampl); - float a0 = 1 + (alpha / ampl); - float a1 = -2 * c; - float a2 = 1 - (alpha / ampl); + float ampl = std::pow(10.0f, gain / 40.0f); + float b0 = 1.0 + (alpha * ampl); + float b1 = -2.0 * c; + float b2 = 1.0 - (alpha * ampl); + float a0 = 1 + (alpha / ampl); + float a1 = -2 * c; + float a2 = 1 - (alpha / ampl); - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } -void Biquad::peakCoEffsBandwidth(float f, float gain, float bandwidth) -{ - float w0 = 2 * M_PI * f / this->sampleRate; - float c = cosf(w0); - float s = sinf(w0); - float alpha = s * sinh(logf(2.0) / 2.0 * bandwidth * w0 / s); +void Biquad::peakCoEffsBandwidth(float f, float gain, float bandwidth) { + float w0 = 2 * M_PI * f / this->sampleRate; + float c = cosf(w0); + float s = sinf(w0); + float alpha = s * sinh(logf(2.0) / 2.0 * bandwidth * w0 / s); - float ampl = std::pow(10.0f, gain / 40.0f); - float b0 = 1.0 + (alpha * ampl); - float b1 = -2.0 * c; - float b2 = 1.0 - (alpha * ampl); - float a0 = 1 + (alpha / ampl); - float a1 = -2 * c; - float a2 = 1 - (alpha / ampl); + float ampl = std::pow(10.0f, gain / 40.0f); + float b0 = 1.0 + (alpha * ampl); + float b1 = -2.0 * c; + float b2 = 1.0 - (alpha * ampl); + float a0 = 1 + (alpha / ampl); + float a1 = -2 * c; + float a2 = 1 - (alpha / ampl); - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } -void Biquad::highShelfCoEffs(float f, float gain, float q) -{ - float A = std::pow(10.0f, gain / 40.0f); - float w0 = 2 * M_PI * f / this->sampleRate; - float c = cosf(w0); - float s = sinf(w0); - float alpha = s / (2 * q); - float beta = s * sqrtf(A) / q; - float b0 = A * ((A + 1.0) + (A - 1.0) * c + beta); - float b1 = -2.0 * A * ((A - 1.0) + (A + 1.0) * c); - float b2 = A * ((A + 1.0) + (A - 1.0) * c - beta); - float a0 = (A + 1.0) - (A - 1.0) * c + beta; - float a1 = 2.0 * ((A - 1.0) - (A + 1.0) * c); - float a2 = (A + 1.0) - (A - 1.0) * c - beta; +void Biquad::highShelfCoEffs(float f, float gain, float q) { + float A = std::pow(10.0f, gain / 40.0f); + float w0 = 2 * M_PI * f / this->sampleRate; + float c = cosf(w0); + float s = sinf(w0); + float alpha = s / (2 * q); + float beta = s * sqrtf(A) / q; + float b0 = A * ((A + 1.0) + (A - 1.0) * c + beta); + float b1 = -2.0 * A * ((A - 1.0) + (A + 1.0) * c); + float b2 = A * ((A + 1.0) + (A - 1.0) * c - beta); + float a0 = (A + 1.0) - (A - 1.0) * c + beta; + float a1 = 2.0 * ((A - 1.0) - (A + 1.0) * c); + float a2 = (A + 1.0) - (A - 1.0) * c - beta; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } -void Biquad::highShelfCoEffsSlope(float f, float gain, float slope) -{ - float A = std::pow(10.0f, gain / 40.0f); - float w0 = 2 * M_PI * f / this->sampleRate; - float c = cosf(w0); - float s = sinf(w0); - float alpha = - s / 2.0 * sqrtf((A + 1.0 / A) * (1.0 / (slope / 12.0) - 1.0) + 2.0); - float beta = 2.0 * sqrtf(A) * alpha; - float b0 = A * ((A + 1.0) + (A - 1.0) * c + beta); - float b1 = -2.0 * A * ((A - 1.0) + (A + 1.0) * c); - float b2 = A * ((A + 1.0) + (A - 1.0) * c - beta); - float a0 = (A + 1.0) - (A - 1.0) * c + beta; - float a1 = 2.0 * ((A - 1.0) - (A + 1.0) * c); - float a2 = (A + 1.0) - (A - 1.0) * c - beta; +void Biquad::highShelfCoEffsSlope(float f, float gain, float slope) { + float A = std::pow(10.0f, gain / 40.0f); + float w0 = 2 * M_PI * f / this->sampleRate; + float c = cosf(w0); + float s = sinf(w0); + float alpha = + s / 2.0 * sqrtf((A + 1.0 / A) * (1.0 / (slope / 12.0) - 1.0) + 2.0); + float beta = 2.0 * sqrtf(A) * alpha; + float b0 = A * ((A + 1.0) + (A - 1.0) * c + beta); + float b1 = -2.0 * A * ((A - 1.0) + (A + 1.0) * c); + float b2 = A * ((A + 1.0) + (A - 1.0) * c - beta); + float a0 = (A + 1.0) - (A - 1.0) * c + beta; + float a1 = 2.0 * ((A - 1.0) - (A + 1.0) * c); + float a2 = (A + 1.0) - (A - 1.0) * c - beta; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } -void Biquad::highShelfFOCoEffs(float f, float gain) -{ - float A = std::pow(10.0f, gain / 40.0f); - float w0 = 2 * M_PI * f / this->sampleRate; - float tn = tanf(w0 / 2.0); +void Biquad::highShelfFOCoEffs(float f, float gain) { + float A = std::pow(10.0f, gain / 40.0f); + float w0 = 2 * M_PI * f / this->sampleRate; + float tn = tanf(w0 / 2.0); - float b0 = A * tn + std::pow(A, 2); - float b1 = A * tn - std::pow(A, 2); - float b2 = 0.0; - float a0 = A * tn + 1.0; - float a1 = A * tn - 1.0; - float a2 = 0.0; + float b0 = A * tn + std::pow(A, 2); + float b1 = A * tn - std::pow(A, 2); + float b2 = 0.0; + float a0 = A * tn + 1.0; + float a1 = A * tn - 1.0; + float a2 = 0.0; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } void Biquad::lowShelfCoEffs(float f, float gain, float q) { - float A = std::pow(10.0f, gain / 40.0f); - float w0 = 2 * M_PI * f / this->sampleRate; - float c = cosf(w0); - float s = sinf(w0); - float beta = s * sqrtf(A) / q; + float A = std::pow(10.0f, gain / 40.0f); + float w0 = 2 * M_PI * f / this->sampleRate; + float c = cosf(w0); + float s = sinf(w0); + float beta = s * sqrtf(A) / q; - float b0 = A * ((A + 1.0) - (A - 1.0) * c + beta); - float b1 = 2.0 * A * ((A - 1.0) - (A + 1.0) * c); - float b2 = A * ((A + 1.0) - (A - 1.0) * c - beta); - float a0 = (A + 1.0) + (A - 1.0) * c + beta; - float a1 = -2.0 * ((A - 1.0) + (A + 1.0) * c); - float a2 = (A + 1.0) + (A - 1.0) * c - beta; + float b0 = A * ((A + 1.0) - (A - 1.0) * c + beta); + float b1 = 2.0 * A * ((A - 1.0) - (A + 1.0) * c); + float b2 = A * ((A + 1.0) - (A - 1.0) * c - beta); + float a0 = (A + 1.0) + (A - 1.0) * c + beta; + float a1 = -2.0 * ((A - 1.0) + (A + 1.0) * c); + float a2 = (A + 1.0) + (A - 1.0) * c - beta; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } void Biquad::lowShelfCoEffsSlope(float f, float gain, float slope) { - float A = std::pow(10.0f, gain / 40.0f); - float w0 = 2 * M_PI * f / this->sampleRate; - float c = cosf(w0); - float s = sinf(w0); - float alpha = - s / 2.0 * sqrtf((A + 1.0 / A) * (1.0 / (slope / 12.0) - 1.0) + 2.0); - float beta = 2.0 * sqrtf(A) * alpha; + float A = std::pow(10.0f, gain / 40.0f); + float w0 = 2 * M_PI * f / this->sampleRate; + float c = cosf(w0); + float s = sinf(w0); + float alpha = + s / 2.0 * sqrtf((A + 1.0 / A) * (1.0 / (slope / 12.0) - 1.0) + 2.0); + float beta = 2.0 * sqrtf(A) * alpha; - float b0 = A * ((A + 1.0) - (A - 1.0) * c + beta); - float b1 = 2.0 * A * ((A - 1.0) - (A + 1.0) * c); - float b2 = A * ((A + 1.0) - (A - 1.0) * c - beta); - float a0 = (A + 1.0) + (A - 1.0) * c + beta; - float a1 = -2.0 * ((A - 1.0) + (A + 1.0) * c); - float a2 = (A + 1.0) + (A - 1.0) * c - beta; + float b0 = A * ((A + 1.0) - (A - 1.0) * c + beta); + float b1 = 2.0 * A * ((A - 1.0) - (A + 1.0) * c); + float b2 = A * ((A + 1.0) - (A - 1.0) * c - beta); + float a0 = (A + 1.0) + (A - 1.0) * c + beta; + float a1 = -2.0 * ((A - 1.0) + (A + 1.0) * c); + float a2 = (A + 1.0) + (A - 1.0) * c - beta; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } void Biquad::lowShelfFOCoEffs(float f, float gain) { - float A = std::pow(10.0f, gain / 40.0f); - float w0 = 2 * M_PI * f / this->sampleRate; - float tn = tanf(w0 / 2.0); + float A = std::pow(10.0f, gain / 40.0f); + float w0 = 2 * M_PI * f / this->sampleRate; + float tn = tanf(w0 / 2.0); - float b0 = std::pow(A, 2) * tn + A; - float b1 = std::pow(A, 2) * tn - A; - float b2 = 0.0; - float a0 = tn + A; - float a1 = tn - A; - float a2 = 0.0; + float b0 = std::pow(A, 2) * tn + A; + float b1 = std::pow(A, 2) * tn - A; + float b2 = 0.0; + float a0 = tn + A; + float a1 = tn - A; + float a2 = 0.0; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } void Biquad::notchCoEffs(float f, float gain, float q) { - float A = std::pow(10.0f, gain / 40.0f); - float w0 = 2 * M_PI * f / this->sampleRate; - float c = cosf(w0); - float s = sinf(w0); - float alpha = s / (2.0 * q); + float A = std::pow(10.0f, gain / 40.0f); + float w0 = 2 * M_PI * f / this->sampleRate; + float c = cosf(w0); + float s = sinf(w0); + float alpha = s / (2.0 * q); - float b0 = 1.0; - float b1 = -2.0 * c; - float b2 = 1.0; - float a0 = 1.0 + alpha; - float a1 = -2.0 * c; - float a2 = 1.0 - alpha; + float b0 = 1.0; + float b1 = -2.0 * c; + float b2 = 1.0; + float a0 = 1.0 + alpha; + float a1 = -2.0 * c; + float a2 = 1.0 - alpha; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } void Biquad::notchCoEffsBandwidth(float f, float gain, float bandwidth) { - float A = std::pow(10.0f, gain / 40.0f); - float w0 = 2 * M_PI * f / this->sampleRate; - float c = cosf(w0); - float s = sinf(w0); - float alpha = s * sinh(logf(2.0) / 2.0 * bandwidth * w0 / s); + float A = std::pow(10.0f, gain / 40.0f); + float w0 = 2 * M_PI * f / this->sampleRate; + float c = cosf(w0); + float s = sinf(w0); + float alpha = s * sinh(logf(2.0) / 2.0 * bandwidth * w0 / s); - float b0 = 1.0; - float b1 = -2.0 * c; - float b2 = 1.0; - float a0 = 1.0 + alpha; - float a1 = -2.0 * c; - float a2 = 1.0 - alpha; + float b0 = 1.0; + float b1 = -2.0 * c; + float b2 = 1.0; + float a0 = 1.0 + alpha; + float a1 = -2.0 * c; + float a2 = 1.0 - alpha; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } void Biquad::bandPassCoEffs(float f, float q) { - float w0 = 2 * M_PI * f / this->sampleRate; - float c = cosf(w0); - float s = sinf(w0); - float alpha = s / (2.0 * q); + float w0 = 2 * M_PI * f / this->sampleRate; + float c = cosf(w0); + float s = sinf(w0); + float alpha = s / (2.0 * q); - float b0 = alpha; - float b1 = 0.0; - float b2 = -alpha; - float a0 = 1.0 + alpha; - float a1 = -2.0 * c; - float a2 = 1.0 - alpha; + float b0 = alpha; + float b1 = 0.0; + float b2 = -alpha; + float a0 = 1.0 + alpha; + float a1 = -2.0 * c; + float a2 = 1.0 - alpha; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } void Biquad::bandPassCoEffsBandwidth(float f, float bandwidth) { - float w0 = 2 * M_PI * f / this->sampleRate; - float c = cosf(w0); - float s = sinf(w0); - float alpha = s * sinh(logf(2.0) / 2.0 * bandwidth * w0 / s); + float w0 = 2 * M_PI * f / this->sampleRate; + float c = cosf(w0); + float s = sinf(w0); + float alpha = s * sinh(logf(2.0) / 2.0 * bandwidth * w0 / s); - float b0 = alpha; - float b1 = 0.0; - float b2 = -alpha; - float a0 = 1.0 + alpha; - float a1 = -2.0 * c; - float a2 = 1.0 - alpha; + float b0 = alpha; + float b1 = 0.0; + float b2 = -alpha; + float a0 = 1.0 + alpha; + float a1 = -2.0 * c; + float a2 = 1.0 - alpha; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } void Biquad::allPassCoEffs(float f, float q) { - float w0 = 2 * M_PI * f / this->sampleRate; - float c = cosf(w0); - float s = sinf(w0); - float alpha = s / (2.0 * q); + float w0 = 2 * M_PI * f / this->sampleRate; + float c = cosf(w0); + float s = sinf(w0); + float alpha = s / (2.0 * q); - float b0 = 1.0 - alpha; - float b1 = -2.0 * c; - float b2 = 1.0 + alpha; - float a0 = 1.0 + alpha; - float a1 = -2.0 * c; - float a2 = 1.0 - alpha; + float b0 = 1.0 - alpha; + float b1 = -2.0 * c; + float b2 = 1.0 + alpha; + float a0 = 1.0 + alpha; + float a1 = -2.0 * c; + float a2 = 1.0 - alpha; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } void Biquad::allPassCoEffsBandwidth(float f, float bandwidth) { - float w0 = 2 * M_PI * f / this->sampleRate; - float c = cosf(w0); - float s = sinf(w0); - float alpha = s * sinh(logf(2.0) / 2.0 * bandwidth * w0 / s); + float w0 = 2 * M_PI * f / this->sampleRate; + float c = cosf(w0); + float s = sinf(w0); + float alpha = s * sinh(logf(2.0) / 2.0 * bandwidth * w0 / s); - float b0 = 1.0 - alpha; - float b1 = -2.0 * c; - float b2 = 1.0 + alpha; - float a0 = 1.0 + alpha; - float a1 = -2.0 * c; - float a2 = 1.0 - alpha; + float b0 = 1.0 - alpha; + float b1 = -2.0 * c; + float b2 = 1.0 + alpha; + float a0 = 1.0 + alpha; + float a1 = -2.0 * c; + float a2 = 1.0 - alpha; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } void Biquad::allPassFOCoEffs(float f) { - float w0 = 2 * M_PI * f / this->sampleRate; - float tn = tanf(w0 / 2.0); + float w0 = 2 * M_PI * f / this->sampleRate; + float tn = tanf(w0 / 2.0); - float alpha = (tn + 1.0) / (tn - 1.0); + float alpha = (tn + 1.0) / (tn - 1.0); - float b0 = 1.0; - float b1 = alpha; - float b2 = 0.0; - float a0 = alpha; - float a1 = 1.0; - float a2 = 0.0; + float b0 = 1.0; + float b1 = alpha; + float b2 = 0.0; + float a0 = alpha; + float a1 = 1.0; + float a2 = 0.0; - this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); + this->normalizeCoEffs(a0, a1, a2, b0, b1, b2); } -void Biquad::normalizeCoEffs(float a0, float a1, float a2, float b0, float b1, float b2) -{ - coeffs[0] = b0 / a0; - coeffs[1] = b1 / a0; - coeffs[2] = b2 / a0; - coeffs[3] = a1 / a0; - coeffs[4] = a2 / a0; +void Biquad::normalizeCoEffs(float a0, float a1, float a2, float b0, float b1, + float b2) { + coeffs[0] = b0 / a0; + coeffs[1] = b1 / a0; + coeffs[2] = b2 / a0; + coeffs[3] = a1 / a0; + coeffs[4] = a2 / a0; } -std::unique_ptr Biquad::process(std::unique_ptr stream) -{ - std::scoped_lock lock(accessMutex); +std::unique_ptr Biquad::process( + std::unique_ptr stream) { + std::scoped_lock lock(accessMutex); - auto input = stream->data[this->channel]; - auto numSamples = stream->numSamples; + auto input = stream->data[this->channel]; + auto numSamples = stream->numSamples; #ifdef ESP_PLATFORM - dsps_biquad_f32_ae32(input, input, numSamples, coeffs, w); + dsps_biquad_f32_ae32(input, input, numSamples, coeffs, w); #else - // Apply the set coefficients - for (int i = 0; i < numSamples; i++) - { - float d0 = input[i] - coeffs[3] * w[0] - coeffs[4] * w[1]; - input[i] = coeffs[0] * d0 + coeffs[1] * w[0] + coeffs[2] * w[1]; - w[1] = w[0]; - w[0] = d0; - } + // Apply the set coefficients + for (int i = 0; i < numSamples; i++) { + float d0 = input[i] - coeffs[3] * w[0] - coeffs[4] * w[1]; + input[i] = coeffs[0] * d0 + coeffs[1] * w[0] + coeffs[2] * w[1]; + w[1] = w[0]; + w[0] = d0; + } #endif - return stream; + return stream; }; diff --git a/components/spotify/cspot/bell/main/audio-dsp/BiquadCombo.cpp b/components/spotify/cspot/bell/main/audio-dsp/BiquadCombo.cpp index 8c962700..90ed50fb 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/BiquadCombo.cpp +++ b/components/spotify/cspot/bell/main/audio-dsp/BiquadCombo.cpp @@ -1,109 +1,89 @@ #include "BiquadCombo.h" +#include // for printf +#include // for sinf, M_PI +#include // for move + using namespace bell; -BiquadCombo::BiquadCombo() -{ +BiquadCombo::BiquadCombo() {} + +void BiquadCombo::sampleRateChanged(uint32_t sampleRate) { + for (auto& biquad : biquads) { + biquad->sampleRateChanged(sampleRate); + } } -void BiquadCombo::sampleRateChanged(uint32_t sampleRate) -{ - for (auto &biquad : biquads) - { - biquad->sampleRateChanged(sampleRate); - } +std::vector BiquadCombo::calculateBWQ(int order) { + + std::vector qValues; + for (int n = 0; n < order / 2; n++) { + float q = 1.0f / (2.0f * sinf(M_PI / order * (((float)n) + 0.5))); + qValues.push_back(q); + } + + if (order % 2 > 0) { + qValues.push_back(-1.0); + } + + printf("%d\n", qValues.size()); + + return qValues; } -std::vector BiquadCombo::calculateBWQ(int order) -{ +std::vector BiquadCombo::calculateLRQ(int order) { + auto qValues = calculateBWQ(order / 2); - std::vector qValues; - for (int n = 0; n < order / 2; n++) - { - float q = 1.0f / (2.0f * sinf(M_PI / order * (((float)n) + 0.5))); - qValues.push_back(q); - } + if (order % 4 > 0) { + qValues.pop_back(); + qValues.insert(qValues.end(), qValues.begin(), qValues.end()); + qValues.push_back(0.5); + } else { + qValues.insert(qValues.end(), qValues.begin(), qValues.end()); + } - if (order % 2 > 0) - { - qValues.push_back(-1.0); - } - - printf("%d\n", qValues.size()); - - return qValues; + return qValues; } -std::vector BiquadCombo::calculateLRQ(int order) -{ - auto qValues = calculateBWQ(order / 2); - - if (order % 4 > 0) - { - qValues.pop_back(); - qValues.insert(qValues.end(), qValues.begin(), qValues.end()); - qValues.push_back(0.5); - } - else - { - qValues.insert(qValues.end(), qValues.begin(), qValues.end()); - } - - return qValues; +void BiquadCombo::butterworth(float freq, int order, FilterType type) { + std::vector qValues = calculateBWQ(order); + for (auto& q : qValues) {} } -void BiquadCombo::butterworth(float freq, int order, FilterType type) -{ - std::vector qValues = calculateBWQ(order); - for (auto &q : qValues) - { +void BiquadCombo::linkwitzRiley(float freq, int order, FilterType type) { + std::vector qValues = calculateLRQ(order); + for (auto& q : qValues) { + auto filter = std::make_unique(); + filter->channel = channel; + + auto config = std::map(); + config["freq"] = freq; + config["q"] = q; + + if (q >= 0.0) { + if (type == FilterType::Highpass) { + filter->configure(Biquad::Type::Highpass, config); + } else { + filter->configure(Biquad::Type::Lowpass, config); + } + } else { + if (type == FilterType::Highpass) { + filter->configure(Biquad::Type::HighpassFO, config); + } else { + filter->configure(Biquad::Type::LowpassFO, config); + } } + + this->biquads.push_back(std::move(filter)); + } } -void BiquadCombo::linkwitzRiley(float freq, int order, FilterType type) -{ - std::vector qValues = calculateLRQ(order); - for (auto &q : qValues) - { - auto filter = std::make_unique(); - filter->channel = channel; +std::unique_ptr BiquadCombo::process( + std::unique_ptr data) { + std::scoped_lock lock(this->accessMutex); + for (auto& transform : this->biquads) { + data = transform->process(std::move(data)); + } - auto config = std::map(); - config["freq"] = freq; - config["q"] = q; - - if (q >= 0.0) - { - if (type == FilterType::Highpass) - { - filter->configure(Biquad::Type::Highpass, config); - } - else - { - filter->configure(Biquad::Type::Lowpass, config); - } - } - else - { - if (type == FilterType::Highpass) - { - filter->configure(Biquad::Type::HighpassFO, config); - } - else - { - filter->configure(Biquad::Type::LowpassFO, config); - } - } - - this->biquads.push_back(std::move(filter)); - } -} - -std::unique_ptr BiquadCombo::process(std::unique_ptr data) { - std::scoped_lock lock(this->accessMutex); - for (auto &transform : this->biquads) { - data = transform->process(std::move(data)); - } - - return data; + return data; } \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-dsp/Compressor.cpp b/components/spotify/cspot/bell/main/audio-dsp/Compressor.cpp index 95194fbf..aa8bb48b 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/Compressor.cpp +++ b/components/spotify/cspot/bell/main/audio-dsp/Compressor.cpp @@ -1,5 +1,7 @@ #include "Compressor.h" +#include // for abs + using namespace bell; float log2f_approx(float X) { @@ -19,11 +21,11 @@ float log2f_approx(float X) { Compressor::Compressor() {} -void Compressor::sumChannels(std::unique_ptr &data) { +void Compressor::sumChannels(std::unique_ptr& data) { tmp.resize(data->numSamples); for (int i = 0; i < data->numSamples; i++) { float sum = 0.0f; - for (auto &channel : channels) { + for (auto& channel : channels) { sum += data->data[channel][i]; } tmp[i] = sum; @@ -31,7 +33,7 @@ void Compressor::sumChannels(std::unique_ptr &data) { } void Compressor::calLoudness() { - for (auto &value : tmp) { + for (auto& value : tmp) { value = 20 * log10f_fast(std::abs(value) + 1.0e-9f); if (value >= lastLoudness) { value = attack * lastLoudness + (1.0 - attack) * value; @@ -44,7 +46,7 @@ void Compressor::calLoudness() { } void Compressor::calGain() { - for (auto &value : tmp) { + for (auto& value : tmp) { if (value > threshold) { value = -(value - threshold) * (factor - 1.0) / factor; } else { @@ -58,9 +60,9 @@ void Compressor::calGain() { } } -void Compressor::applyGain(std::unique_ptr &data) { +void Compressor::applyGain(std::unique_ptr& data) { for (int i = 0; i < data->numSamples; i++) { - for (auto &channel : channels) { + for (auto& channel : channels) { data->data[channel][i] *= tmp[i]; } } diff --git a/components/spotify/cspot/bell/main/audio-dsp/Gain.cpp b/components/spotify/cspot/bell/main/audio-dsp/Gain.cpp index 9775758a..a2b8fe23 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/Gain.cpp +++ b/components/spotify/cspot/bell/main/audio-dsp/Gain.cpp @@ -1,31 +1,29 @@ #include "Gain.h" +#include // for pow +#include // for string + using namespace bell; -Gain::Gain() : AudioTransform() -{ - this->gainFactor = 1.0f; - this->filterType = "gain"; +Gain::Gain() : AudioTransform() { + this->gainFactor = 1.0f; + this->filterType = "gain"; } -void Gain::configure(std::vector channels, float gainDB) -{ - this->channels = channels; - this->gainDb = gainDB; - this->gainFactor = std::pow(10.0f, gainDB / 20.0f); +void Gain::configure(std::vector channels, float gainDB) { + this->channels = channels; + this->gainDb = gainDB; + this->gainFactor = std::pow(10.0f, gainDB / 20.0f); } -std::unique_ptr Gain::process(std::unique_ptr data) -{ - std::scoped_lock lock(this->accessMutex); - for (int i = 0; i < data->numSamples; i++) - { - // Apply gain to all channels - for (auto &channel : channels) - { - data->data[channel][i] *= gainFactor; - } +std::unique_ptr Gain::process(std::unique_ptr data) { + std::scoped_lock lock(this->accessMutex); + for (int i = 0; i < data->numSamples; i++) { + // Apply gain to all channels + for (auto& channel : channels) { + data->data[channel][i] *= gainFactor; } + } - return data; + return data; } \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-dsp/include/AudioMixer.h b/components/spotify/cspot/bell/main/audio-dsp/include/AudioMixer.h index 979b878c..04b86b09 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/include/AudioMixer.h +++ b/components/spotify/cspot/bell/main/audio-dsp/include/AudioMixer.h @@ -1,89 +1,80 @@ #pragma once -#include -#include -#include +#include // for cJSON_GetObjectItem, cJSON, cJSON_IsArray +#include // for NULL +#include // for find +#include // for uint8_t +#include // for unique_ptr +#include // for invalid_argument +#include // for vector -#include "AudioTransform.h" +#include "AudioTransform.h" // for AudioTransform +#include "StreamInfo.h" // for StreamInfo -namespace bell -{ - class AudioMixer : public bell::AudioTransform - { - public: - enum DownmixMode - { - DEFAULT - }; +namespace bell { +class AudioMixer : public bell::AudioTransform { + public: + enum DownmixMode { DEFAULT }; - struct MixerConfig - { - std::vector source; - int destination; - }; + struct MixerConfig { + std::vector source; + int destination; + }; - AudioMixer(); - ~AudioMixer(){}; - // Amount of channels in the input - int from; + AudioMixer(); + ~AudioMixer(){}; + // Amount of channels in the input + int from; - // Amount of channels in the output - int to; + // Amount of channels in the output + int to; - // Configuration of each channels in the mixer - std::vector mixerConfig; + // Configuration of each channels in the mixer + std::vector mixerConfig; - std::unique_ptr process(std::unique_ptr data) override; + std::unique_ptr process( + std::unique_ptr data) override; - void reconfigure() override - { + void reconfigure() override {} + + void fromJSON(cJSON* json) { + cJSON* mappedChannels = cJSON_GetObjectItem(json, "mapped_channels"); + + if (mappedChannels == NULL || !cJSON_IsArray(mappedChannels)) { + throw std::invalid_argument("Mixer configuration invalid"); + } + + this->mixerConfig = std::vector(); + + cJSON* iterator = NULL; + cJSON_ArrayForEach(iterator, mappedChannels) { + std::vector sources(0); + cJSON* iteratorNested = NULL; + cJSON_ArrayForEach(iteratorNested, + cJSON_GetObjectItem(iterator, "source")) { + sources.push_back(iteratorNested->valueint); + } + + int destination = cJSON_GetObjectItem(iterator, "destination")->valueint; + + this->mixerConfig.push_back( + MixerConfig{.source = sources, .destination = destination}); + } + + std::vector sources(0); + + for (auto& config : mixerConfig) { + + for (auto& source : config.source) { + if (std::find(sources.begin(), sources.end(), source) == + sources.end()) { + sources.push_back(source); } + } + } - void fromJSON(cJSON *json) - { - cJSON *mappedChannels = cJSON_GetObjectItem(json, "mapped_channels"); - - if (mappedChannels == NULL || !cJSON_IsArray(mappedChannels)) - { - throw std::invalid_argument("Mixer configuration invalid"); - } - - this->mixerConfig = std::vector(); - - cJSON *iterator = NULL; - cJSON_ArrayForEach(iterator, mappedChannels) - { - std::vector sources(0); - cJSON *iteratorNested = NULL; - cJSON_ArrayForEach(iteratorNested, cJSON_GetObjectItem(iterator, "source")) - { - sources.push_back(iteratorNested->valueint); - } - - int destination = cJSON_GetObjectItem(iterator, "destination")->valueint; - - this->mixerConfig.push_back(MixerConfig{ - .source = sources, - .destination = destination - }); - } - - std::vector sources(0); - - for (auto &config : mixerConfig) - { - - for (auto &source : config.source) - { - if (std::find(sources.begin(), sources.end(), source) == sources.end()) - { - sources.push_back(source); - } - } - } - - this->from = sources.size(); - this->to = mixerConfig.size(); - } - }; -} \ No newline at end of file + this->from = sources.size(); + this->to = mixerConfig.size(); + } +}; +} // namespace bell \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-dsp/include/AudioPipeline.h b/components/spotify/cspot/bell/main/audio-dsp/include/AudioPipeline.h index 5f1b0559..211d5b57 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/include/AudioPipeline.h +++ b/components/spotify/cspot/bell/main/audio-dsp/include/AudioPipeline.h @@ -1,28 +1,29 @@ #pragma once -#include "AudioTransform.h" -#include "StreamInfo.h" -#include -#include "Gain.h" -#include +#include // for shared_ptr, unique_ptr +#include // for mutex +#include // for vector -namespace bell -{ - class AudioPipeline - { - private: - std::shared_ptr headroomGainTransform; +#include "StreamInfo.h" // for StreamInfo - public: - AudioPipeline(); - ~AudioPipeline(){}; +namespace bell { +class AudioTransform; +class Gain; - std::mutex accessMutex; - std::vector> transforms; +class AudioPipeline { + private: + std::shared_ptr headroomGainTransform; - void recalculateHeadroom(); - void addTransform(std::shared_ptr transform); - void volumeUpdated(int volume); - std::unique_ptr process(std::unique_ptr data); - }; -}; // namespace bell \ No newline at end of file + public: + AudioPipeline(); + ~AudioPipeline(){}; + + std::mutex accessMutex; + std::vector> transforms; + + void recalculateHeadroom(); + void addTransform(std::shared_ptr transform); + void volumeUpdated(int volume); + std::unique_ptr process(std::unique_ptr data); +}; +}; // namespace bell \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-dsp/include/AudioTransform.h b/components/spotify/cspot/bell/main/audio-dsp/include/AudioTransform.h index 1845aca4..697a0e10 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/include/AudioTransform.h +++ b/components/spotify/cspot/bell/main/audio-dsp/include/AudioTransform.h @@ -1,29 +1,28 @@ #pragma once #include -#include #include +#include #include "StreamInfo.h" #include "TransformConfig.h" -namespace bell -{ - class AudioTransform - { - protected: - std::mutex accessMutex; +namespace bell { +class AudioTransform { + protected: + std::mutex accessMutex; - public: - virtual std::unique_ptr process(std::unique_ptr data) = 0; - virtual void sampleRateChanged(uint32_t sampleRate){}; - virtual float calculateHeadroom() { return 0; }; + public: + virtual std::unique_ptr process( + std::unique_ptr data) = 0; + virtual void sampleRateChanged(uint32_t sampleRate){}; + virtual float calculateHeadroom() { return 0; }; - virtual void reconfigure() {}; + virtual void reconfigure(){}; - std::string filterType; - std::unique_ptr config; + std::string filterType; + std::unique_ptr config; - AudioTransform() = default; - virtual ~AudioTransform() = default; - }; -}; \ No newline at end of file + AudioTransform() = default; + virtual ~AudioTransform() = default; +}; +}; // namespace bell \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-dsp/include/BellDSP.h b/components/spotify/cspot/bell/main/audio-dsp/include/BellDSP.h index 5589731e..8e618710 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/include/BellDSP.h +++ b/components/spotify/cspot/bell/main/audio-dsp/include/BellDSP.h @@ -1,34 +1,43 @@ #pragma once -#include -#include -#include -#include "AudioPipeline.h" -#include "CentralAudioBuffer.h" +#include // for size_t +#include // for uint32_t, uint8_t +#include // for function +#include // for shared_ptr, unique_ptr +#include // for mutex +#include // for vector + +#include "StreamInfo.h" // for BitWidth namespace bell { +class AudioPipeline; +class CentralAudioBuffer; + #define MAX_INT16 32767 class BellDSP { public: BellDSP(std::shared_ptr centralAudioBuffer); - ~BellDSP() {}; + ~BellDSP(){}; class AudioEffect { public: AudioEffect() = default; ~AudioEffect() = default; size_t duration; - virtual void apply(float* sampleData, size_t samples, size_t relativePosition) = 0; + virtual void apply(float* sampleData, size_t samples, + size_t relativePosition) = 0; }; - class FadeEffect: public AudioEffect { - private: + class FadeEffect : public AudioEffect { + private: std::function onFinish; bool isFadeIn; - public: - FadeEffect(size_t duration, bool isFadeIn, std::function onFinish = nullptr); - ~FadeEffect() {}; + + public: + FadeEffect(size_t duration, bool isFadeIn, + std::function onFinish = nullptr); + ~FadeEffect(){}; void apply(float* sampleData, size_t samples, size_t relativePosition); }; @@ -38,8 +47,8 @@ class BellDSP { std::shared_ptr getActivePipeline(); - size_t process(uint8_t* data, size_t bytes, int channels, - uint32_t sampleRate, BitWidth bitWidth); + size_t process(uint8_t* data, size_t bytes, int channels, uint32_t sampleRate, + BitWidth bitWidth); private: std::shared_ptr activePipeline; @@ -48,7 +57,6 @@ class BellDSP { std::vector dataLeft = std::vector(1024); std::vector dataRight = std::vector(1024); - std::unique_ptr underflowEffect = nullptr; std::unique_ptr startEffect = nullptr; std::unique_ptr instantEffect = nullptr; diff --git a/components/spotify/cspot/bell/main/audio-dsp/include/Biquad.h b/components/spotify/cspot/bell/main/audio-dsp/include/Biquad.h index 2c2ce662..01195a3c 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/include/Biquad.h +++ b/components/spotify/cspot/bell/main/audio-dsp/include/Biquad.h @@ -1,158 +1,158 @@ #pragma once -#include -#include -#include -#include +#include // for uint32_t +#include // for map +#include // for unique_ptr, allocator +#include // for scoped_lock +#include // for invalid_argument +#include // for string, operator<, hash, operator== +#include // for operator!=, unordered_map, __hash_map_c... +#include // for pair +#include // for vector -#include "AudioTransform.h" -extern "C" int dsps_biquad_f32_ae32(const float *input, float *output, int len, float *coef, float *w); +#include "AudioTransform.h" // for AudioTransform +#include "StreamInfo.h" // for StreamInfo +#include "TransformConfig.h" // for TransformConfig -namespace bell -{ - class Biquad : public bell::AudioTransform - { - public: - Biquad(); - ~Biquad(){}; +extern "C" int dsps_biquad_f32_ae32(const float* input, float* output, int len, + float* coef, float* w); - enum class Type - { - Free, - Highpass, - Lowpass, - HighpassFO, - LowpassFO, +namespace bell { +class Biquad : public bell::AudioTransform { + public: + Biquad(); + ~Biquad(){}; - Peaking, - Highshelf, - HighshelfFO, - Lowshelf, - LowshelfFO, - Notch, - Bandpass, - Allpass, - AllpassFO - }; + enum class Type { + Free, + Highpass, + Lowpass, + HighpassFO, + LowpassFO, - std::map currentConfig; + Peaking, + Highshelf, + HighshelfFO, + Lowshelf, + LowshelfFO, + Notch, + Bandpass, + Allpass, + AllpassFO + }; - std::unordered_map const strMapType = { - {"free", Type::Free}, - {"highpass", Type::Highpass}, - {"lowpass", Type::Lowpass}, - {"highpass_fo", Type::HighpassFO}, - {"lowpass_fo", Type::LowpassFO}, - {"peaking", Type::Peaking}, - {"highshelf", Type::Highshelf}, - {"highshelf_fo", Type::HighpassFO}, - {"lowshelf", Type::Lowshelf}, - {"lowshelf_fo", Type::LowpassFO}, - {"notch", Type::Notch}, - {"bandpass", Type::Bandpass}, - {"allpass", Type::Allpass}, - {"allpass_fo", Type::AllpassFO}, - }; + std::map currentConfig; - float freq, q, gain; - int channel; - Biquad::Type type; + std::unordered_map const strMapType = { + {"free", Type::Free}, + {"highpass", Type::Highpass}, + {"lowpass", Type::Lowpass}, + {"highpass_fo", Type::HighpassFO}, + {"lowpass_fo", Type::LowpassFO}, + {"peaking", Type::Peaking}, + {"highshelf", Type::Highshelf}, + {"highshelf_fo", Type::HighpassFO}, + {"lowshelf", Type::Lowshelf}, + {"lowshelf_fo", Type::LowpassFO}, + {"notch", Type::Notch}, + {"bandpass", Type::Bandpass}, + {"allpass", Type::Allpass}, + {"allpass_fo", Type::AllpassFO}, + }; - std::unique_ptr process(std::unique_ptr data) override; + float freq, q, gain; + int channel; + Biquad::Type type; - void configure(Type type, std::map &config); + std::unique_ptr process( + std::unique_ptr data) override; - void sampleRateChanged(uint32_t sampleRate) override; + void configure(Type type, std::map& config); - void reconfigure() override - { - std::scoped_lock lock(this->accessMutex); - std::map biquadConfig; - this->channel = config->getChannels()[0]; + void sampleRateChanged(uint32_t sampleRate) override; - float invalid = -0x7C; + void reconfigure() override { + std::scoped_lock lock(this->accessMutex); + std::map biquadConfig; + this->channel = config->getChannels()[0]; - auto type = config->getString("biquad_type"); - float bandwidth = config->getFloat("bandwidth", false, invalid); - float slope = config->getFloat("slope", false, invalid); - float gain = config->getFloat("gain", false, invalid); - float frequency = config->getFloat("frequency", false, invalid); - float q = config->getFloat("q", false, invalid); + float invalid = -0x7C; - if (currentConfig["bandwidth"] == bandwidth && - currentConfig["slope"] == slope && - currentConfig["gain"] == gain && - currentConfig["frequency"] == frequency && - currentConfig["q"] == q) - { - return; - } + auto type = config->getString("biquad_type"); + float bandwidth = config->getFloat("bandwidth", false, invalid); + float slope = config->getFloat("slope", false, invalid); + float gain = config->getFloat("gain", false, invalid); + float frequency = config->getFloat("frequency", false, invalid); + float q = config->getFloat("q", false, invalid); - if (bandwidth != invalid) - biquadConfig["bandwidth"] = bandwidth; - if (slope != invalid) - biquadConfig["slope"] = slope; - if (gain != invalid) - biquadConfig["gain"] = gain; - if (frequency != invalid) - biquadConfig["freq"] = frequency; - if (q != invalid) - biquadConfig["q"] = q; + if (currentConfig["bandwidth"] == bandwidth && + currentConfig["slope"] == slope && currentConfig["gain"] == gain && + currentConfig["frequency"] == frequency && currentConfig["q"] == q) { + return; + } - if (type == "free") - { - biquadConfig["a1"] = config->getFloat("a1"); - biquadConfig["a2"] = config->getFloat("a2"); - biquadConfig["b0"] = config->getFloat("b0"); - biquadConfig["b1"] = config->getFloat("b1"); - biquadConfig["b2"] = config->getFloat("b2"); - } + if (bandwidth != invalid) + biquadConfig["bandwidth"] = bandwidth; + if (slope != invalid) + biquadConfig["slope"] = slope; + if (gain != invalid) + biquadConfig["gain"] = gain; + if (frequency != invalid) + biquadConfig["freq"] = frequency; + if (q != invalid) + biquadConfig["q"] = q; - auto typeElement = strMapType.find(type); - if (typeElement != strMapType.end()) - { - this->configure(typeElement->second, biquadConfig); - } - else - { - throw std::invalid_argument("No biquad of type " + type); - } - } + if (type == "free") { + biquadConfig["a1"] = config->getFloat("a1"); + biquadConfig["a2"] = config->getFloat("a2"); + biquadConfig["b0"] = config->getFloat("b0"); + biquadConfig["b1"] = config->getFloat("b1"); + biquadConfig["b2"] = config->getFloat("b2"); + } - private: - float coeffs[5]; - float w[2] = {1.0, 1.0}; + auto typeElement = strMapType.find(type); + if (typeElement != strMapType.end()) { + this->configure(typeElement->second, biquadConfig); + } else { + throw std::invalid_argument("No biquad of type " + type); + } + } - float sampleRate = 44100; + private: + float coeffs[5]; + float w[2] = {1.0, 1.0}; - // Generator methods for different filter types - void highPassCoEffs(float f, float q); - void highPassFOCoEffs(float f); - void lowPassCoEffs(float f, float q); - void lowPassFOCoEffs(float f); + float sampleRate = 44100; - void peakCoEffs(float f, float gain, float q); - void peakCoEffsBandwidth(float f, float gain, float bandwidth); + // Generator methods for different filter types + void highPassCoEffs(float f, float q); + void highPassFOCoEffs(float f); + void lowPassCoEffs(float f, float q); + void lowPassFOCoEffs(float f); - void highShelfCoEffs(float f, float gain, float q); - void highShelfCoEffsSlope(float f, float gain, float slope); - void highShelfFOCoEffs(float f, float gain); + void peakCoEffs(float f, float gain, float q); + void peakCoEffsBandwidth(float f, float gain, float bandwidth); - void lowShelfCoEffs(float f, float gain, float q); - void lowShelfCoEffsSlope(float f, float gain, float slope); - void lowShelfFOCoEffs(float f, float gain); + void highShelfCoEffs(float f, float gain, float q); + void highShelfCoEffsSlope(float f, float gain, float slope); + void highShelfFOCoEffs(float f, float gain); - void notchCoEffs(float f, float gain, float q); - void notchCoEffsBandwidth(float f, float gain, float bandwidth); + void lowShelfCoEffs(float f, float gain, float q); + void lowShelfCoEffsSlope(float f, float gain, float slope); + void lowShelfFOCoEffs(float f, float gain); - void bandPassCoEffs(float f, float q); - void bandPassCoEffsBandwidth(float f, float bandwidth); + void notchCoEffs(float f, float gain, float q); + void notchCoEffsBandwidth(float f, float gain, float bandwidth); - void allPassCoEffs(float f, float q); - void allPassCoEffsBandwidth(float f, float bandwidth); - void allPassFOCoEffs(float f); + void bandPassCoEffs(float f, float q); + void bandPassCoEffsBandwidth(float f, float bandwidth); - void normalizeCoEffs(float a0, float a1, float a2, float b0, float b1, float b2); - }; + void allPassCoEffs(float f, float q); + void allPassCoEffsBandwidth(float f, float bandwidth); + void allPassFOCoEffs(float f); -} \ No newline at end of file + void normalizeCoEffs(float a0, float a1, float a2, float b0, float b1, + float b2); +}; + +} // namespace bell \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-dsp/include/BiquadCombo.h b/components/spotify/cspot/bell/main/audio-dsp/include/BiquadCombo.h index 66a75f38..bc7234dd 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/include/BiquadCombo.h +++ b/components/spotify/cspot/bell/main/audio-dsp/include/BiquadCombo.h @@ -1,85 +1,71 @@ #pragma once -#include -#include -#include -#include -#include +#include // for uint32_t +#include // for map +#include // for unique_ptr, allocator +#include // for scoped_lock +#include // for invalid_argument +#include // for string, operator==, char_traits, basic_... +#include // for vector -#include "Biquad.h" -#include "AudioTransform.h" +#include "AudioTransform.h" // for AudioTransform +#include "Biquad.h" // for Biquad +#include "StreamInfo.h" // for StreamInfo +#include "TransformConfig.h" // for TransformConfig -namespace bell -{ - class BiquadCombo : public bell::AudioTransform - { - private: - std::vector> biquads; +namespace bell { +class BiquadCombo : public bell::AudioTransform { + private: + std::vector> biquads; - // Calculates Q values for Nth order Butterworth / Linkwitz-Riley filters - std::vector calculateBWQ(int order); - std::vector calculateLRQ(int order); + // Calculates Q values for Nth order Butterworth / Linkwitz-Riley filters + std::vector calculateBWQ(int order); + std::vector calculateLRQ(int order); - public: - BiquadCombo(); - ~BiquadCombo(){}; - int channel; + public: + BiquadCombo(); + ~BiquadCombo(){}; + int channel; - std::map paramCache = { - {"order", 0.0f}, - {"frequency", 0.0f} - }; + std::map paramCache = {{"order", 0.0f}, + {"frequency", 0.0f}}; - enum class FilterType - { - Highpass, - Lowpass - }; + enum class FilterType { Highpass, Lowpass }; - void linkwitzRiley(float freq, int order, FilterType type); - void butterworth(float freq, int order, FilterType type); + void linkwitzRiley(float freq, int order, FilterType type); + void butterworth(float freq, int order, FilterType type); - std::unique_ptr process(std::unique_ptr data) override; - void sampleRateChanged(uint32_t sampleRate) override; + std::unique_ptr process( + std::unique_ptr data) override; + void sampleRateChanged(uint32_t sampleRate) override; - void reconfigure() override - { - std::scoped_lock lock(this->accessMutex); + void reconfigure() override { + std::scoped_lock lock(this->accessMutex); - float freq = config->getFloat("frequency"); - int order = config->getInt("order"); + float freq = config->getFloat("frequency"); + int order = config->getInt("order"); - if (paramCache["frequency"] == freq && paramCache["order"] == order) - { - return; - } else { - paramCache["frequency"] = freq; - paramCache["order"] = order; - } + if (paramCache["frequency"] == freq && paramCache["order"] == order) { + return; + } else { + paramCache["frequency"] = freq; + paramCache["order"] = order; + } - this->channel = config->getChannels()[0]; - this->biquads = std::vector>(); - auto type = config->getString("combo_type"); - if (type == "lr_lowpass") - { - this->linkwitzRiley(freq, order, FilterType::Lowpass); - } - else if (type == "lr_highpass") - { - this->linkwitzRiley(freq, order, FilterType::Highpass); - } - else if (type == "bw_highpass") - { - this->butterworth(freq, order, FilterType::Highpass); - } - else if (type == "bw_lowpass") - { - this->butterworth(freq, order, FilterType::Highpass); - } - else - { - throw std::invalid_argument("Invalid combo filter type"); - } - } - }; -}; \ No newline at end of file + this->channel = config->getChannels()[0]; + this->biquads = std::vector>(); + auto type = config->getString("combo_type"); + if (type == "lr_lowpass") { + this->linkwitzRiley(freq, order, FilterType::Lowpass); + } else if (type == "lr_highpass") { + this->linkwitzRiley(freq, order, FilterType::Highpass); + } else if (type == "bw_highpass") { + this->butterworth(freq, order, FilterType::Highpass); + } else if (type == "bw_lowpass") { + this->butterworth(freq, order, FilterType::Highpass); + } else { + throw std::invalid_argument("Invalid combo filter type"); + } + } +}; +}; // namespace bell \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-dsp/include/CentralAudioBuffer.h b/components/spotify/cspot/bell/main/audio-dsp/include/CentralAudioBuffer.h index c2723da5..d55f3ed7 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/include/CentralAudioBuffer.h +++ b/components/spotify/cspot/bell/main/audio-dsp/include/CentralAudioBuffer.h @@ -2,10 +2,10 @@ #include #include +#include #include #include #include -#include #include "BellUtils.h" #include "CircularBuffer.h" @@ -70,9 +70,9 @@ class CentralAudioBuffer { */ void clearBuffer() { std::scoped_lock lock(this->dataAccessMutex); - //size_t exceptSize = currentSampleRate + (sizeof(AudioChunk) - (currentSampleRate % sizeof(AudioChunk))); - hasChunk = false; + audioBuffer->emptyBuffer(); + hasChunk = false; } void emptyCompletely() { @@ -106,10 +106,10 @@ class CentralAudioBuffer { } } - AudioChunk currentChunk = { }; + AudioChunk currentChunk = {}; bool hasChunk = false; - AudioChunk lastReadChunk = { }; + AudioChunk lastReadChunk = {}; AudioChunk* readChunk() { std::scoped_lock lock(this->dataAccessMutex); diff --git a/components/spotify/cspot/bell/main/audio-dsp/include/Compressor.h b/components/spotify/cspot/bell/main/audio-dsp/include/Compressor.h index 4308160d..9d1995c3 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/include/Compressor.h +++ b/components/spotify/cspot/bell/main/audio-dsp/include/Compressor.h @@ -1,103 +1,102 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include +#include // for expf +#include // for uint32_t +#include // for map +#include // for unique_ptr +#include // for scoped_lock +#include // for string, operator< +#include // for vector -#include "Biquad.h" -#include "AudioTransform.h" +#include "AudioTransform.h" // for AudioTransform +#include "StreamInfo.h" // for StreamInfo +#include "TransformConfig.h" // for TransformConfig -#define pow10f(x) expf(2.302585092994046f*x) +#define pow10f(x) expf(2.302585092994046f * x) // This is a fast approximation to log2() // Y = C[0]*F*F*F + C[1]*F*F + C[2]*F + C[3] + E; -float log2f_approx(float X); +float log2f_approx(float X); -#define log10f_fast(x) (log2f_approx(x)*0.3010299956639812f) +#define log10f_fast(x) (log2f_approx(x) * 0.3010299956639812f) -namespace bell -{ - class Compressor : public bell::AudioTransform - { - private: - std::vector channels; - std::vector tmp; +namespace bell { +class Compressor : public bell::AudioTransform { + private: + std::vector channels; + std::vector tmp; - std::map paramCache; + std::map paramCache; - float attack; - float release; - float threshold; - float factor; - float clipLimit; - float makeupGain; + float attack; + float release; + float threshold; + float factor; + float clipLimit; + float makeupGain; - float lastLoudness = -100.0f; + float lastLoudness = -100.0f; - float sampleRate = 44100; + float sampleRate = 44100; - public: - Compressor(); - ~Compressor(){}; + public: + Compressor(); + ~Compressor(){}; - void configure(std::vector channels, float attack, float release, float threshold, float factor, float makeupGain); + void configure(std::vector channels, float attack, float release, + float threshold, float factor, float makeupGain); - void sumChannels(std::unique_ptr &data); - void calLoudness(); - void calGain(); + void sumChannels(std::unique_ptr& data); + void calLoudness(); + void calGain(); - void applyGain(std::unique_ptr &data); + void applyGain(std::unique_ptr& data); - void reconfigure() override - { - std::scoped_lock lock(this->accessMutex); - auto newChannels = config->getChannels(); - - float newAttack = config->getFloat("attack"); - float newRelease = config->getFloat("release"); - float newThreshold = config->getFloat("threshold"); - float newFactor = config->getFloat("factor"); - float newMakeupGain = config->getFloat("makeup_gain"); + void reconfigure() override { + std::scoped_lock lock(this->accessMutex); + auto newChannels = config->getChannels(); - if (paramCache["attack"] == newAttack && - paramCache["release"] == newRelease && - paramCache["threshold"] == newThreshold && - paramCache["factor"] == newFactor && - paramCache["makeup_gain"] == newMakeupGain) - { - return; - } - else - { + float newAttack = config->getFloat("attack"); + float newRelease = config->getFloat("release"); + float newThreshold = config->getFloat("threshold"); + float newFactor = config->getFloat("factor"); + float newMakeupGain = config->getFloat("makeup_gain"); - paramCache["attack"] = newAttack; - paramCache["release"] = newRelease; - paramCache["threshold"] = newThreshold; - paramCache["factor"] = newFactor; - paramCache["makeup_gain"] = newMakeupGain; - } + if (paramCache["attack"] == newAttack && + paramCache["release"] == newRelease && + paramCache["threshold"] == newThreshold && + paramCache["factor"] == newFactor && + paramCache["makeup_gain"] == newMakeupGain) { + return; + } else { - this->configure(newChannels, newAttack, newRelease, newThreshold, newFactor, newMakeupGain); - } + paramCache["attack"] = newAttack; + paramCache["release"] = newRelease; + paramCache["threshold"] = newThreshold; + paramCache["factor"] = newFactor; + paramCache["makeup_gain"] = newMakeupGain; + } - // void fromJSON(cJSON* json) override { - // // get field channels - // channels = jsonGetChannels(json); - // float attack = jsonGetNumber(json, "attack", false, 0); - // float release = jsonGetNumber(json, "release", false, 0); - // float factor = jsonGetNumber(json, "factor", false, 4); - // float makeupGain = jsonGetNumber(json, "makeup_gain", false, 0); - // float threshold = jsonGetNumber(json, "threshold", false, 0); + this->configure(newChannels, newAttack, newRelease, newThreshold, newFactor, + newMakeupGain); + } - // this->configure(attack, release, clipLimit, threshold, factor, makeupGain); - // } + // void fromJSON(cJSON* json) override { + // // get field channels + // channels = jsonGetChannels(json); + // float attack = jsonGetNumber(json, "attack", false, 0); + // float release = jsonGetNumber(json, "release", false, 0); + // float factor = jsonGetNumber(json, "factor", false, 4); + // float makeupGain = jsonGetNumber(json, "makeup_gain", false, 0); + // float threshold = jsonGetNumber(json, "threshold", false, 0); - std::unique_ptr process(std::unique_ptr data) override; - void sampleRateChanged(uint32_t sampleRate) override { this->sampleRate = sampleRate; }; - }; + // this->configure(attack, release, clipLimit, threshold, factor, makeupGain); + // } + + std::unique_ptr process( + std::unique_ptr data) override; + void sampleRateChanged(uint32_t sampleRate) override { + this->sampleRate = sampleRate; + }; }; +}; // namespace bell diff --git a/components/spotify/cspot/bell/main/audio-dsp/include/Gain.h b/components/spotify/cspot/bell/main/audio-dsp/include/Gain.h index 8948b223..f8151a9e 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/include/Gain.h +++ b/components/spotify/cspot/bell/main/audio-dsp/include/Gain.h @@ -1,40 +1,41 @@ #pragma once -#include -#include -#include +#include // for unique_ptr +#include // for scoped_lock +#include // for vector -#include "AudioTransform.h" +#include "AudioTransform.h" // for AudioTransform +#include "StreamInfo.h" // for StreamInfo +#include "TransformConfig.h" // for TransformConfig -namespace bell -{ - class Gain : public bell::AudioTransform - { - private: - float gainFactor = 1.0f; +namespace bell { +class Gain : public bell::AudioTransform { + private: + float gainFactor = 1.0f; - std::vector channels; + std::vector channels; - public: - Gain(); - ~Gain() {}; - - float gainDb = 0.0; - - void configure(std::vector channels, float gainDB); + public: + Gain(); + ~Gain(){}; - std::unique_ptr process(std::unique_ptr data) override; + float gainDb = 0.0; - void reconfigure() override { - std::scoped_lock lock(this->accessMutex); - float gain = config->getFloat("gain"); - this->channels = config->getChannels(); + void configure(std::vector channels, float gainDB); - if (gainDb == gain) { - return; - } + std::unique_ptr process( + std::unique_ptr data) override; - this->configure(channels, gain); - } - }; -} \ No newline at end of file + void reconfigure() override { + std::scoped_lock lock(this->accessMutex); + float gain = config->getFloat("gain"); + this->channels = config->getChannels(); + + if (gainDb == gain) { + return; + } + + this->configure(channels, gain); + } +}; +} // namespace bell \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-dsp/include/JSONTransformConfig.h b/components/spotify/cspot/bell/main/audio-dsp/include/JSONTransformConfig.h index 9ee15229..3b63680e 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/include/JSONTransformConfig.h +++ b/components/spotify/cspot/bell/main/audio-dsp/include/JSONTransformConfig.h @@ -3,108 +3,87 @@ #include "TransformConfig.h" #include "cJSON.h" -namespace bell -{ - class JSONTransformConfig : public bell::TransformConfig - { - private: - cJSON *json; +namespace bell { +class JSONTransformConfig : public bell::TransformConfig { + private: + cJSON* json; - public: - JSONTransformConfig(cJSON *body) - { - this->json = body; - }; - ~JSONTransformConfig(){}; + public: + JSONTransformConfig(cJSON* body) { this->json = body; }; + ~JSONTransformConfig(){}; - std::string rawGetString(const std::string &field) override - { - cJSON *value = cJSON_GetObjectItem(json, field.c_str()); + std::string rawGetString(const std::string& field) override { + cJSON* value = cJSON_GetObjectItem(json, field.c_str()); - if (value != NULL && cJSON_IsString(value)) - { - return std::string(value->valuestring); - } + if (value != NULL && cJSON_IsString(value)) { + return std::string(value->valuestring); + } - return "invalid"; + return "invalid"; + } + + std::vector rawGetIntArray(const std::string& field) override { + std::vector result; + + cJSON* value = cJSON_GetObjectItem(json, field.c_str()); + + if (value != NULL && cJSON_IsArray(value)) { + for (int i = 0; i < cJSON_GetArraySize(value); i++) { + cJSON* item = cJSON_GetArrayItem(value, i); + if (item != NULL && cJSON_IsNumber(item)) { + result.push_back(item->valueint); } + } + } - std::vector rawGetIntArray(const std::string &field) override - { - std::vector result; + return result; + } - cJSON *value = cJSON_GetObjectItem(json, field.c_str()); + std::vector rawGetFloatArray(const std::string& field) override { + std::vector result; - if (value != NULL && cJSON_IsArray(value)) - { - for (int i = 0; i < cJSON_GetArraySize(value); i++) - { - cJSON *item = cJSON_GetArrayItem(value, i); - if (item != NULL && cJSON_IsNumber(item)) - { - result.push_back(item->valueint); - } - } - } + cJSON* value = cJSON_GetObjectItem(json, field.c_str()); - return result; + if (value != NULL && cJSON_IsArray(value)) { + for (int i = 0; i < cJSON_GetArraySize(value); i++) { + cJSON* item = cJSON_GetArrayItem(value, i); + if (item != NULL && cJSON_IsNumber(item)) { + result.push_back(item->valuedouble); } + } + } - std::vector rawGetFloatArray(const std::string &field) override - { - std::vector result; + return result; + } - cJSON *value = cJSON_GetObjectItem(json, field.c_str()); + int rawGetInt(const std::string& field) override { + cJSON* value = cJSON_GetObjectItem(json, field.c_str()); - if (value != NULL && cJSON_IsArray(value)) - { - for (int i = 0; i < cJSON_GetArraySize(value); i++) - { - cJSON *item = cJSON_GetArrayItem(value, i); - if (item != NULL && cJSON_IsNumber(item)) - { - result.push_back(item->valuedouble); - } - } - } + if (value != NULL && cJSON_IsNumber(value)) { + return (int)value->valueint; + } - return result; - } + return invalidInt; + } - int rawGetInt(const std::string &field) override - { - cJSON *value = cJSON_GetObjectItem(json, field.c_str()); + bool isArray(const std::string& field) override { + cJSON* value = cJSON_GetObjectItem(json, field.c_str()); - if (value != NULL && cJSON_IsNumber(value)) - { - return (int)value->valueint; - } + if (value != NULL && cJSON_IsArray(value)) { + return true; + } - return invalidInt; - } + return false; + } - bool isArray(const std::string &field) override - { - cJSON *value = cJSON_GetObjectItem(json, field.c_str()); + float rawGetFloat(const std::string& field) override { + cJSON* value = cJSON_GetObjectItem(json, field.c_str()); - if (value != NULL && cJSON_IsArray(value)) - { - return true; - } + if (value != NULL && cJSON_IsNumber(value)) { + return (float)value->valuedouble; + } - return false; - } - - float rawGetFloat(const std::string &field) override - { - cJSON *value = cJSON_GetObjectItem(json, field.c_str()); - - if (value != NULL && cJSON_IsNumber(value)) - { - return (float)value->valuedouble; - } - - return invalidInt; - } - }; -} \ No newline at end of file + return invalidInt; + } +}; +} // namespace bell \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-dsp/include/StreamInfo.h b/components/spotify/cspot/bell/main/audio-dsp/include/StreamInfo.h index 313a3824..bac4661b 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/include/StreamInfo.h +++ b/components/spotify/cspot/bell/main/audio-dsp/include/StreamInfo.h @@ -1,36 +1,28 @@ #pragma once #include -#include #include +#include -namespace bell -{ - enum class Channels { - LEFT, - RIGHT, - LEFT_RIGHT - }; - - enum class SampleRate : uint32_t - { - SR_44100 = 44100, - SR_48000 = 48000, - }; +namespace bell { +enum class Channels { LEFT, RIGHT, LEFT_RIGHT }; - enum class BitWidth : uint32_t - { - BW_16 = 16, - BW_24 = 24, - BW_32 = 32, - }; +enum class SampleRate : uint32_t { + SR_44100 = 44100, + SR_48000 = 48000, +}; - typedef struct - { - float** data; - BitWidth bitwidth; - int numChannels; - SampleRate sampleRate; - size_t numSamples; - } StreamInfo; -}; \ No newline at end of file +enum class BitWidth : uint32_t { + BW_16 = 16, + BW_24 = 24, + BW_32 = 32, +}; + +typedef struct { + float** data; + BitWidth bitwidth; + int numChannels; + SampleRate sampleRate; + size_t numSamples; +} StreamInfo; +}; // namespace bell \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-dsp/include/TransformConfig.h b/components/spotify/cspot/bell/main/audio-dsp/include/TransformConfig.h index c0809fd3..942db428 100644 --- a/components/spotify/cspot/bell/main/audio-dsp/include/TransformConfig.h +++ b/components/spotify/cspot/bell/main/audio-dsp/include/TransformConfig.h @@ -1,134 +1,112 @@ #pragma once -#include -#include -#include -#include #include #include +#include +#include +#include +#include -namespace bell -{ - class TransformConfig - { - protected: - int invalidInt = -0x7C; - std::string invalidString = "_invalid"; +namespace bell { +class TransformConfig { + protected: + int invalidInt = -0x7C; + std::string invalidString = "_invalid"; - public: - TransformConfig() = default; - virtual ~TransformConfig() = default; + public: + TransformConfig() = default; + virtual ~TransformConfig() = default; - int currentVolume = 60; + int currentVolume = 60; - virtual std::string rawGetString(const std::string &field) = 0; + virtual std::string rawGetString(const std::string& field) = 0; - virtual int rawGetInt(const std::string &field) = 0; - virtual bool isArray(const std::string &field) = 0; + virtual int rawGetInt(const std::string& field) = 0; + virtual bool isArray(const std::string& field) = 0; - virtual float rawGetFloat(const std::string &field) = 0; - virtual std::vector rawGetFloatArray(const std::string &field) = 0; - virtual std::vector rawGetIntArray(const std::string &field) = 0; + virtual float rawGetFloat(const std::string& field) = 0; + virtual std::vector rawGetFloatArray(const std::string& field) = 0; + virtual std::vector rawGetIntArray(const std::string& field) = 0; - typedef std::variant Value; - std::map> rawValues; + typedef std::variant Value; + std::map> rawValues; - Value getRawValue(const std::string &field) - { - int index = this->currentVolume * (rawValues[field].size()) / 100; - if (index >= rawValues[field].size()) - index = rawValues[field].size() - 1; - return rawValues[field][index]; + Value getRawValue(const std::string& field) { + int index = this->currentVolume * (rawValues[field].size()) / 100; + if (index >= rawValues[field].size()) + index = rawValues[field].size() - 1; + return rawValues[field][index]; + } + + std::string getString(const std::string& field, bool isRequired = false, + std::string defaultValue = "") { + if (rawValues.count(field) == 0) { + rawValues[field] = std::vector({Value(rawGetString(field))}); + } + auto val = std::get(getRawValue(field)); + if (val == invalidString) { + if (isRequired) + throw std::invalid_argument("Field " + field + " is required"); + else + return defaultValue; + } else + return val; + } + + int getInt(const std::string& field, bool isRequired = false, + int defaultValue = 0) { + if (rawValues.count(field) == 0) { + if (isArray(field)) { + rawValues[field] = std::vector(); + for (auto f : rawGetIntArray(field)) { + rawValues[field].push_back(f); } + } else { + rawValues[field] = std::vector({Value(rawGetInt(field))}); + } + } - std::string getString(const std::string &field, bool isRequired = false, std::string defaultValue = "") - { - if (rawValues.count(field) == 0) - { - rawValues[field] = std::vector({Value(rawGetString(field))}); - } - auto val = std::get(getRawValue(field)); - if (val == invalidString) - { - if (isRequired) - throw std::invalid_argument("Field " + field + " is required"); - else - return defaultValue; - } - else - return val; + auto val = std::get(getRawValue(field)); + if (val == invalidInt) { + if (isRequired) + throw std::invalid_argument("Field " + field + " is required"); + else + return defaultValue; + } else + return val; + } + + float getFloat(const std::string& field, bool isRequired = false, + float defaultValue = 0) { + if (rawValues.count(field) == 0) { + if (isArray(field)) { + + rawValues[field] = std::vector(); + for (auto f : rawGetFloatArray(field)) { + rawValues[field].push_back(f); } + } else { + rawValues[field] = std::vector({Value(rawGetFloat(field))}); + } + } + auto val = std::get(getRawValue(field)); + if (val == invalidInt) { + if (isRequired) + throw std::invalid_argument("Field " + field + " is required"); + else + return defaultValue; + } else + return val; + } - int getInt(const std::string &field, bool isRequired = false, int defaultValue = 0) - { - if (rawValues.count(field) == 0) - { - if (isArray(field)) - { - rawValues[field] = std::vector(); - for (auto f : rawGetIntArray(field)) - { - rawValues[field].push_back(f); - } - } - else - { - rawValues[field] = std::vector({Value(rawGetInt(field))}); - } - } + std::vector getChannels() { + auto channel = getInt("channel", false, invalidInt); - auto val = std::get(getRawValue(field)); - if (val == invalidInt) - { - if (isRequired) - throw std::invalid_argument("Field " + field + " is required"); - else - return defaultValue; - } - else - return val; - } + if (channel != invalidInt) { + return std::vector({channel}); + } - float getFloat(const std::string &field, bool isRequired = false, float defaultValue = 0) - { - if (rawValues.count(field) == 0) - { - if (isArray(field)) - { - - rawValues[field] = std::vector(); - for (auto f : rawGetFloatArray(field)) - { - rawValues[field].push_back(f); - } - } - else - { - rawValues[field] = std::vector({ Value(rawGetFloat(field)) }); - } - } - auto val = std::get(getRawValue(field)); - if (val == invalidInt) - { - if (isRequired) - throw std::invalid_argument("Field " + field + " is required"); - else - return defaultValue; - } - else - return val; - } - - std::vector getChannels() - { - auto channel = getInt("channel", false, invalidInt); - - if (channel != invalidInt) - { - return std::vector({channel}); - } - - return rawGetIntArray("channels"); - } - }; -} \ No newline at end of file + return rawGetIntArray("channels"); + } +}; +} // namespace bell \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/esp/AC101AudioSink.cpp b/components/spotify/cspot/bell/main/audio-sinks/esp/AC101AudioSink.cpp index 5e83f10d..e07f65a2 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/esp/AC101AudioSink.cpp +++ b/components/spotify/cspot/bell/main/audio-sinks/esp/AC101AudioSink.cpp @@ -2,45 +2,42 @@ #include "driver/i2s.h" -AC101AudioSink::AC101AudioSink() -{ - // Disable software volume control, all handled by ::volumeChanged - softwareVolumeControl = false; +AC101AudioSink::AC101AudioSink() { + // Disable software volume control, all handled by ::volumeChanged + softwareVolumeControl = false; - i2s_config_t i2s_config = { + i2s_config_t i2s_config = { - .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX - .sample_rate = 44100, - .bits_per_sample = (i2s_bits_per_sample_t)16, - .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels - .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_I2S, - .intr_alloc_flags = 0, //Default interrupt priority - .dma_buf_count = 8, - .dma_buf_len = 512, - .use_apll = true, - .tx_desc_auto_clear = true //Auto clear tx descriptor on underflow - }; + .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX + .sample_rate = 44100, + .bits_per_sample = (i2s_bits_per_sample_t)16, + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels + .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_I2S, + .intr_alloc_flags = 0, //Default interrupt priority + .dma_buf_count = 8, + .dma_buf_len = 512, + .use_apll = true, + .tx_desc_auto_clear = true //Auto clear tx descriptor on underflow + }; - i2s_pin_config_t pin_config = { - .bck_io_num = 27, - .ws_io_num = 26, - .data_out_num = 25, - .data_in_num = -1 //Not used - }; + i2s_pin_config_t pin_config = { + .bck_io_num = 27, + .ws_io_num = 26, + .data_out_num = 25, + .data_in_num = -1 //Not used + }; - dac = &dac_a1s; + dac = &dac_a1s; - dac->init(0, 0, &i2s_config); - dac->speaker(false); - dac->power(ADAC_ON); + dac->init(0, 0, &i2s_config); + dac->speaker(false); + dac->power(ADAC_ON); - startI2sFeed(); + startI2sFeed(); } -AC101AudioSink::~AC101AudioSink() -{ -} +AC101AudioSink::~AC101AudioSink() {} void AC101AudioSink::volumeChanged(uint16_t volume) { - dac->volume(volume, volume); + dac->volume(volume, volume); } diff --git a/components/spotify/cspot/bell/main/audio-sinks/esp/BufferedAudioSink.cpp b/components/spotify/cspot/bell/main/audio-sinks/esp/BufferedAudioSink.cpp index 4d849fc3..e32a9c7a 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/esp/BufferedAudioSink.cpp +++ b/components/spotify/cspot/bell/main/audio-sinks/esp/BufferedAudioSink.cpp @@ -1,47 +1,45 @@ #include "BufferedAudioSink.h" #include "driver/i2s.h" -#include "freertos/task.h" #include "freertos/ringbuf.h" +#include "freertos/task.h" RingbufHandle_t dataBuffer; -static void i2sFeed(void *pvParameters) -{ - while (true) - { - size_t itemSize; - char *item = (char *)xRingbufferReceiveUpTo(dataBuffer, &itemSize, portMAX_DELAY, 512); - if (item != NULL) - { - size_t written = 0; - while (written < itemSize) - { - i2s_write((i2s_port_t)0, item, itemSize, &written, portMAX_DELAY); - } - vRingbufferReturnItem(dataBuffer, (void *)item); - } +static void i2sFeed(void* pvParameters) { + while (true) { + size_t itemSize; + char* item = (char*)xRingbufferReceiveUpTo(dataBuffer, &itemSize, + portMAX_DELAY, 512); + if (item != NULL) { + size_t written = 0; + while (written < itemSize) { + i2s_write((i2s_port_t)0, item, itemSize, &written, portMAX_DELAY); + } + vRingbufferReturnItem(dataBuffer, (void*)item); } + } } -void BufferedAudioSink::startI2sFeed(size_t buf_size) -{ - dataBuffer = xRingbufferCreate(buf_size, RINGBUF_TYPE_BYTEBUF); - xTaskCreatePinnedToCore(&i2sFeed, "i2sFeed", 4096, NULL, 10, NULL, tskNO_AFFINITY); +void BufferedAudioSink::startI2sFeed(size_t buf_size) { + dataBuffer = xRingbufferCreate(buf_size, RINGBUF_TYPE_BYTEBUF); + xTaskCreatePinnedToCore(&i2sFeed, "i2sFeed", 4096, NULL, 10, NULL, + tskNO_AFFINITY); } -void BufferedAudioSink::feedPCMFrames(const uint8_t *buffer, size_t bytes) -{ - feedPCMFramesInternal(buffer, bytes); +void BufferedAudioSink::feedPCMFrames(const uint8_t* buffer, size_t bytes) { + feedPCMFramesInternal(buffer, bytes); } -void BufferedAudioSink::feedPCMFramesInternal(const void *pvItem, size_t xItemSize) -{ - xRingbufferSend(dataBuffer, pvItem, xItemSize, portMAX_DELAY); +void BufferedAudioSink::feedPCMFramesInternal(const void* pvItem, + size_t xItemSize) { + xRingbufferSend(dataBuffer, pvItem, xItemSize, portMAX_DELAY); } -bool BufferedAudioSink::setParams(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) { - // TODO override this for sinks with custom mclk - i2s_set_clk((i2s_port_t)0, sampleRate, (i2s_bits_per_sample_t)bitDepth, (i2s_channel_t)channelCount); - return true; +bool BufferedAudioSink::setParams(uint32_t sampleRate, uint8_t channelCount, + uint8_t bitDepth) { + // TODO override this for sinks with custom mclk + i2s_set_clk((i2s_port_t)0, sampleRate, (i2s_bits_per_sample_t)bitDepth, + (i2s_channel_t)channelCount); + return true; } \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/esp/ES8311AudioSink.cpp b/components/spotify/cspot/bell/main/audio-sinks/esp/ES8311AudioSink.cpp index 62041593..aa9ff9fd 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/esp/ES8311AudioSink.cpp +++ b/components/spotify/cspot/bell/main/audio-sinks/esp/ES8311AudioSink.cpp @@ -1,106 +1,101 @@ #include "ES8311AudioSink.h" extern "C" { - #include "es8311.h" +#include "es8311.h" } -ES8311AudioSink::ES8311AudioSink() -{ - this->softwareVolumeControl = false; - esp_err_t ret_val = ESP_OK; - Es8311Config cfg = { - .esMode = ES_MODE_SLAVE, - .i2c_port_num = I2C_NUM_0, - .i2c_cfg = { - .mode = I2C_MODE_MASTER, - .sda_io_num = 1, - .scl_io_num = 2, - .sda_pullup_en = GPIO_PULLUP_ENABLE, - .scl_pullup_en = GPIO_PULLUP_ENABLE, - }, - .dacOutput = (DacOutput) (DAC_OUTPUT_LOUT1 | DAC_OUTPUT_LOUT2 | DAC_OUTPUT_ROUT1 | DAC_OUTPUT_ROUT2), - .adcInput = ADC_INPUT_LINPUT1_RINPUT1, - }; - cfg.i2c_cfg.master.clk_speed = 100000; - Es8311Init(&cfg); - Es8311SetBitsPerSample(ES_MODULE_DAC, BIT_LENGTH_16BITS); - Es8311ConfigFmt(ES_MODULE_DAC, ES_I2S_NORMAL); - Es8311SetVoiceVolume(60); - Es8311Start(ES_MODULE_DAC); - ES8311WriteReg(ES8311_CLK_MANAGER_REG01, 0xbf); - ES8311WriteReg(ES8311_CLK_MANAGER_REG02, 0x18); +ES8311AudioSink::ES8311AudioSink() { + this->softwareVolumeControl = false; + esp_err_t ret_val = ESP_OK; + Es8311Config cfg = { + .esMode = ES_MODE_SLAVE, + .i2c_port_num = I2C_NUM_0, + .i2c_cfg = + { + .mode = I2C_MODE_MASTER, + .sda_io_num = 1, + .scl_io_num = 2, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + }, + .dacOutput = (DacOutput)(DAC_OUTPUT_LOUT1 | DAC_OUTPUT_LOUT2 | + DAC_OUTPUT_ROUT1 | DAC_OUTPUT_ROUT2), + .adcInput = ADC_INPUT_LINPUT1_RINPUT1, + }; + cfg.i2c_cfg.master.clk_speed = 100000; + Es8311Init(&cfg); + Es8311SetBitsPerSample(ES_MODULE_DAC, BIT_LENGTH_16BITS); + Es8311ConfigFmt(ES_MODULE_DAC, ES_I2S_NORMAL); + Es8311SetVoiceVolume(60); + Es8311Start(ES_MODULE_DAC); + ES8311WriteReg(ES8311_CLK_MANAGER_REG01, 0xbf); + ES8311WriteReg(ES8311_CLK_MANAGER_REG02, 0x18); - // .codec_mode = AUDIO_HAL_CODEC_MODE_DECODE, - // .i2s_iface = { - // .mode = AUDIO_HAL_MODE_SLAVE, - // .fmt = AUDIO_HAL_I2S_NORMAL, - // .samples = AUDIO_HAL_44K_SAMPLES, - // .bits = AUDIO_HAL_BIT_LENGTH_16BITS, - // }, - // }; + // .codec_mode = AUDIO_HAL_CODEC_MODE_DECODE, + // .i2s_iface = { + // .mode = AUDIO_HAL_MODE_SLAVE, + // .fmt = AUDIO_HAL_I2S_NORMAL, + // .samples = AUDIO_HAL_44K_SAMPLES, + // .bits = AUDIO_HAL_BIT_LENGTH_16BITS, + // }, + // }; - // ret_val |= es8311_codec_init(&cfg); - // ret_val |= es8311_set_bits_per_sample(cfg.i2s_iface.bits); - // ret_val |= es8311_config_fmt((es_i2s_fmt_t) cfg.i2s_iface.fmt); - // ret_val |= es8311_codec_set_voice_volume(60); - // ret_val |= es8311_codec_ctrl_state(cfg.codec_mode, AUDIO_HAL_CTRL_START); - // ret_val |= es8311_codec_set_clk(); + // ret_val |= es8311_codec_init(&cfg); + // ret_val |= es8311_set_bits_per_sample(cfg.i2s_iface.bits); + // ret_val |= es8311_config_fmt((es_i2s_fmt_t) cfg.i2s_iface.fmt); + // ret_val |= es8311_codec_set_voice_volume(60); + // ret_val |= es8311_codec_ctrl_state(cfg.codec_mode, AUDIO_HAL_CTRL_START); + // ret_val |= es8311_codec_set_clk(); - i2s_config_t i2s_config = { - .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX - .sample_rate = 44100, - .bits_per_sample = (i2s_bits_per_sample_t)16, - .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // 2-channels - .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_I2S, - .intr_alloc_flags = 0, // Default interrupt priority - .dma_buf_count = 8, - .dma_buf_len = 512, - .use_apll = false, - .tx_desc_auto_clear = true, // Auto clear tx descriptor on underflow - }; + i2s_config_t i2s_config = { + .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX + .sample_rate = 44100, + .bits_per_sample = (i2s_bits_per_sample_t)16, + .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // 2-channels + .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_I2S, + .intr_alloc_flags = 0, // Default interrupt priority + .dma_buf_count = 8, + .dma_buf_len = 512, + .use_apll = false, + .tx_desc_auto_clear = true, // Auto clear tx descriptor on underflow + }; - i2s_pin_config_t pin_config = { - .mck_io_num = 42, - .bck_io_num = 40, - .ws_io_num = 41, - .data_out_num = 39, - .data_in_num = -1, - }; + i2s_pin_config_t pin_config = { + .mck_io_num = 42, + .bck_io_num = 40, + .ws_io_num = 41, + .data_out_num = 39, + .data_in_num = -1, + }; - int err; + int err; - err = i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); - if (err != ESP_OK) - { - ESP_LOGE("OI", "i2s driver installation error: %d", err); - } + err = i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); + if (err != ESP_OK) { + ESP_LOGE("OI", "i2s driver installation error: %d", err); + } - err = i2s_set_pin((i2s_port_t)0, &pin_config); - if (err != ESP_OK) - { - ESP_LOGE("OI", "i2s set pin error: %d", err); - } + err = i2s_set_pin((i2s_port_t)0, &pin_config); + if (err != ESP_OK) { + ESP_LOGE("OI", "i2s set pin error: %d", err); + } - // PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1); - // REG_SET_FIELD(PIN_CTRL, CLK_OUT1, 0); - // ESP_LOGI("OI", "MCLK output on CLK_OUT1"); + // PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1); + // REG_SET_FIELD(PIN_CTRL, CLK_OUT1, 0); + // ESP_LOGI("OI", "MCLK output on CLK_OUT1"); - startI2sFeed(); + startI2sFeed(); } -void ES8311AudioSink::volumeChanged(uint16_t volume) -{ - Es8311SetVoiceVolume(volume); +void ES8311AudioSink::volumeChanged(uint16_t volume) { + Es8311SetVoiceVolume(volume); } -void ES8311AudioSink::writeReg(uint8_t reg_add, uint8_t data) -{ -} +void ES8311AudioSink::writeReg(uint8_t reg_add, uint8_t data) {} void ES8311AudioSink::setSampleRate(uint32_t sampleRate) { - std::cout << "ES8311AudioSink::setSampleRate(" << sampleRate << ")" << std::endl; - // i2s set sample rate - es8311_Codec_Startup(0, sampleRate); + std::cout << "ES8311AudioSink::setSampleRate(" << sampleRate << ")" + << std::endl; + // i2s set sample rate + es8311_Codec_Startup(0, sampleRate); } -ES8311AudioSink::~ES8311AudioSink() -{ -} +ES8311AudioSink::~ES8311AudioSink() {} diff --git a/components/spotify/cspot/bell/main/audio-sinks/esp/ES8388AudioSink.cpp b/components/spotify/cspot/bell/main/audio-sinks/esp/ES8388AudioSink.cpp index b5b5f312..bd450e26 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/esp/ES8388AudioSink.cpp +++ b/components/spotify/cspot/bell/main/audio-sinks/esp/ES8388AudioSink.cpp @@ -1,150 +1,145 @@ #include "ES8388AudioSink.h" struct es8388_cmd_s { - uint8_t reg; - uint8_t value; + uint8_t reg; + uint8_t value; }; -ES8388AudioSink::ES8388AudioSink() -{ - // configure i2c - i2c_config = { - .mode = I2C_MODE_MASTER, - .sda_io_num = 33, - .scl_io_num = 32, - .sda_pullup_en = GPIO_PULLUP_ENABLE, - .scl_pullup_en = GPIO_PULLUP_ENABLE, - }; +ES8388AudioSink::ES8388AudioSink() { + // configure i2c + i2c_config = { + .mode = I2C_MODE_MASTER, + .sda_io_num = 33, + .scl_io_num = 32, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + }; - i2c_config.master.clk_speed = 100000; + i2c_config.master.clk_speed = 100000; - i2s_config_t i2s_config = { - .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX - .sample_rate = 44100, - .bits_per_sample = (i2s_bits_per_sample_t)16, - .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels - .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_MSB, - .intr_alloc_flags = 0, //Default interrupt priority - .dma_buf_count = 8, - .dma_buf_len = 512, - .use_apll = true, - .tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow - .fixed_mclk = 256 * 44100 - }; + i2s_config_t i2s_config = { + .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX + .sample_rate = 44100, + .bits_per_sample = (i2s_bits_per_sample_t)16, + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels + .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_MSB, + .intr_alloc_flags = 0, //Default interrupt priority + .dma_buf_count = 8, + .dma_buf_len = 512, + .use_apll = true, + .tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow + .fixed_mclk = 256 * 44100}; - i2s_pin_config_t pin_config = { - .bck_io_num = 27, - .ws_io_num = 25, - .data_out_num = 26, - .data_in_num = -1 //Not used - }; + i2s_pin_config_t pin_config = { + .bck_io_num = 27, + .ws_io_num = 25, + .data_out_num = 26, + .data_in_num = -1 //Not used + }; - int err; + int err; - err = i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); - if (err != ESP_OK) { - ESP_LOGE("OI", "i2s driver installation error: %d", err); - } + err = i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); + if (err != ESP_OK) { + ESP_LOGE("OI", "i2s driver installation error: %d", err); + } - err = i2s_set_pin((i2s_port_t)0, &pin_config); - if (err != ESP_OK) { - ESP_LOGE("OI", "i2s set pin error: %d", err); - } + err = i2s_set_pin((i2s_port_t)0, &pin_config); + if (err != ESP_OK) { + ESP_LOGE("OI", "i2s set pin error: %d", err); + } - err = i2c_param_config(0, &i2c_config); - if (err != ESP_OK) { - ESP_LOGE("OI", "i2c param config error: %d", err); - } - - err = i2c_driver_install(0, I2C_MODE_MASTER, 0, 0, 0); - if (err != ESP_OK) { - ESP_LOGE("OI", "i2c driver installation error: %d", err); - } + err = i2c_param_config(0, &i2c_config); + if (err != ESP_OK) { + ESP_LOGE("OI", "i2c param config error: %d", err); + } - i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create(); + err = i2c_driver_install(0, I2C_MODE_MASTER, 0, 0, 0); + if (err != ESP_OK) { + ESP_LOGE("OI", "i2c driver installation error: %d", err); + } - err = i2c_master_start(i2c_cmd); - if (err != ESP_OK) { - ESP_LOGE("OI", "i2c master start error: %d", err); - } + i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create(); - /* mute DAC during setup, power up all systems, slave mode */ - writeReg(ES8388_DACCONTROL3, 0x04); - writeReg(ES8388_CONTROL2, 0x50); - writeReg(ES8388_CHIPPOWER, 0x00); - writeReg(ES8388_MASTERMODE, 0x00); + err = i2c_master_start(i2c_cmd); + if (err != ESP_OK) { + ESP_LOGE("OI", "i2c master start error: %d", err); + } - /* power up DAC and enable LOUT1+2 / ROUT1+2, ADC sample rate = DAC sample rate */ - writeReg(ES8388_DACPOWER, 0x3e); - writeReg(ES8388_CONTROL1, 0x12); + /* mute DAC during setup, power up all systems, slave mode */ + writeReg(ES8388_DACCONTROL3, 0x04); + writeReg(ES8388_CONTROL2, 0x50); + writeReg(ES8388_CHIPPOWER, 0x00); + writeReg(ES8388_MASTERMODE, 0x00); - /* DAC I2S setup: 16 bit word length, I2S format; MCLK / Fs = 256*/ - writeReg(ES8388_DACCONTROL1, 0x18); - writeReg(ES8388_DACCONTROL2, 0x02); + /* power up DAC and enable LOUT1+2 / ROUT1+2, ADC sample rate = DAC sample rate */ + writeReg(ES8388_DACPOWER, 0x3e); + writeReg(ES8388_CONTROL1, 0x12); - /* DAC to output route mixer configuration: ADC MIX TO OUTPUT */ - writeReg(ES8388_DACCONTROL16, 0x1B); - writeReg(ES8388_DACCONTROL17, 0x90); - writeReg(ES8388_DACCONTROL20, 0x90); + /* DAC I2S setup: 16 bit word length, I2S format; MCLK / Fs = 256*/ + writeReg(ES8388_DACCONTROL1, 0x18); + writeReg(ES8388_DACCONTROL2, 0x02); - /* DAC and ADC use same LRCK, enable MCLK input; output resistance setup */ - writeReg(ES8388_DACCONTROL21, 0x80); - writeReg(ES8388_DACCONTROL23, 0x00); + /* DAC to output route mixer configuration: ADC MIX TO OUTPUT */ + writeReg(ES8388_DACCONTROL16, 0x1B); + writeReg(ES8388_DACCONTROL17, 0x90); + writeReg(ES8388_DACCONTROL20, 0x90); - /* DAC volume control: 0dB (maximum, unattented) */ - writeReg(ES8388_DACCONTROL5, 0x00); - writeReg(ES8388_DACCONTROL4, 0x00); + /* DAC and ADC use same LRCK, enable MCLK input; output resistance setup */ + writeReg(ES8388_DACCONTROL21, 0x80); + writeReg(ES8388_DACCONTROL23, 0x00); - /* power down ADC while configuring; volume: +9dB for both channels */ - writeReg(ES8388_ADCPOWER, 0xff); - writeReg(ES8388_ADCCONTROL1, 0x88); // +24db + /* DAC volume control: 0dB (maximum, unattented) */ + writeReg(ES8388_DACCONTROL5, 0x00); + writeReg(ES8388_DACCONTROL4, 0x00); - /* select LINPUT2 / RINPUT2 as ADC input; stereo; 16 bit word length, format right-justified, MCLK / Fs = 256 */ - writeReg(ES8388_ADCCONTROL2, 0xf0); // 50 - writeReg(ES8388_ADCCONTROL3, 0x80); // 00 - writeReg(ES8388_ADCCONTROL4, 0x0e); - writeReg(ES8388_ADCCONTROL5, 0x02); + /* power down ADC while configuring; volume: +9dB for both channels */ + writeReg(ES8388_ADCPOWER, 0xff); + writeReg(ES8388_ADCCONTROL1, 0x88); // +24db - /* set ADC volume */ - writeReg(ES8388_ADCCONTROL8, 0x20); - writeReg(ES8388_ADCCONTROL9, 0x20); + /* select LINPUT2 / RINPUT2 as ADC input; stereo; 16 bit word length, format right-justified, MCLK / Fs = 256 */ + writeReg(ES8388_ADCCONTROL2, 0xf0); // 50 + writeReg(ES8388_ADCCONTROL3, 0x80); // 00 + writeReg(ES8388_ADCCONTROL4, 0x0e); + writeReg(ES8388_ADCCONTROL5, 0x02); - /* set LOUT1 / ROUT1 volume: 0dB (unattenuated) */ - writeReg(ES8388_DACCONTROL24, 0x1e); - writeReg(ES8388_DACCONTROL25, 0x1e); + /* set ADC volume */ + writeReg(ES8388_ADCCONTROL8, 0x20); + writeReg(ES8388_ADCCONTROL9, 0x20); - /* set LOUT2 / ROUT2 volume: 0dB (unattenuated) */ - writeReg(ES8388_DACCONTROL26, 0x1e); - writeReg(ES8388_DACCONTROL27, 0x1e); + /* set LOUT1 / ROUT1 volume: 0dB (unattenuated) */ + writeReg(ES8388_DACCONTROL24, 0x1e); + writeReg(ES8388_DACCONTROL25, 0x1e); - /* power up and enable DAC; power up ADC (no MIC bias) */ - writeReg(ES8388_DACPOWER, 0x3c); - writeReg(ES8388_DACCONTROL3, 0x00); - writeReg(ES8388_ADCPOWER, 0x00); + /* set LOUT2 / ROUT2 volume: 0dB (unattenuated) */ + writeReg(ES8388_DACCONTROL26, 0x1e); + writeReg(ES8388_DACCONTROL27, 0x1e); - startI2sFeed(); + /* power up and enable DAC; power up ADC (no MIC bias) */ + writeReg(ES8388_DACPOWER, 0x3c); + writeReg(ES8388_DACCONTROL3, 0x00); + writeReg(ES8388_ADCPOWER, 0x00); + + startI2sFeed(); } -void ES8388AudioSink::writeReg(uint8_t reg_add, uint8_t data) -{ +void ES8388AudioSink::writeReg(uint8_t reg_add, uint8_t data) { - int res = 0; - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - res |= i2c_master_start(cmd); - res |= i2c_master_write_byte(cmd, ES8388_ADDR, ACK_CHECK_EN); - res |= i2c_master_write_byte(cmd, reg_add, ACK_CHECK_EN); - res |= i2c_master_write_byte(cmd, data, ACK_CHECK_EN); - res |= i2c_master_stop(cmd); - res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS); - i2c_cmd_link_delete(cmd); + int res = 0; + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + res |= i2c_master_start(cmd); + res |= i2c_master_write_byte(cmd, ES8388_ADDR, ACK_CHECK_EN); + res |= i2c_master_write_byte(cmd, reg_add, ACK_CHECK_EN); + res |= i2c_master_write_byte(cmd, data, ACK_CHECK_EN); + res |= i2c_master_stop(cmd); + res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); - if (res != ESP_OK) { - ESP_LOGE("RR", "Unable to write to ES8388: %d", res); - }else{ - ESP_LOGE("RR", "register successfull written."); - } + if (res != ESP_OK) { + ESP_LOGE("RR", "Unable to write to ES8388: %d", res); + } else { + ESP_LOGE("RR", "register successfull written."); + } } -ES8388AudioSink::~ES8388AudioSink() -{ -} +ES8388AudioSink::~ES8388AudioSink() {} diff --git a/components/spotify/cspot/bell/main/audio-sinks/esp/ES9018AudioSink.cpp b/components/spotify/cspot/bell/main/audio-sinks/esp/ES9018AudioSink.cpp index 94dc5817..24143fbc 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/esp/ES9018AudioSink.cpp +++ b/components/spotify/cspot/bell/main/audio-sinks/esp/ES9018AudioSink.cpp @@ -2,35 +2,31 @@ #include "driver/i2s.h" -ES9018AudioSink::ES9018AudioSink() -{ - i2s_config_t i2s_config = { +ES9018AudioSink::ES9018AudioSink() { + i2s_config_t i2s_config = { - .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX - .sample_rate = 44100, - .bits_per_sample = (i2s_bits_per_sample_t)16, - .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels - .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_MSB, - .intr_alloc_flags = 0, //Default interrupt priority - .dma_buf_count = 8, - .dma_buf_len = 512, - .use_apll = true, - .tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow - .fixed_mclk = 384 * 44100 - }; + .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX + .sample_rate = 44100, + .bits_per_sample = (i2s_bits_per_sample_t)16, + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels + .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_MSB, + .intr_alloc_flags = 0, //Default interrupt priority + .dma_buf_count = 8, + .dma_buf_len = 512, + .use_apll = true, + .tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow + .fixed_mclk = 384 * 44100}; - i2s_pin_config_t pin_config = { - .bck_io_num = 27, - .ws_io_num = 32, - .data_out_num = 25, - .data_in_num = -1 //Not used - }; - i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); - i2s_set_pin((i2s_port_t)0, &pin_config); - - startI2sFeed(); + i2s_pin_config_t pin_config = { + .bck_io_num = 27, + .ws_io_num = 32, + .data_out_num = 25, + .data_in_num = -1 //Not used + }; + i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); + i2s_set_pin((i2s_port_t)0, &pin_config); + + startI2sFeed(); } -ES9018AudioSink::~ES9018AudioSink() -{ -} +ES9018AudioSink::~ES9018AudioSink() {} diff --git a/components/spotify/cspot/bell/main/audio-sinks/esp/InternalAudioSink.cpp b/components/spotify/cspot/bell/main/audio-sinks/esp/InternalAudioSink.cpp index 26872a2e..a918f037 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/esp/InternalAudioSink.cpp +++ b/components/spotify/cspot/bell/main/audio-sinks/esp/InternalAudioSink.cpp @@ -1,35 +1,32 @@ #include "InternalAudioSink.h" #include "driver/i2s.h" -InternalAudioSink::InternalAudioSink() -{ - softwareVolumeControl = true; - usign = true; - #ifdef I2S_MODE_DAC_BUILT_IN +InternalAudioSink::InternalAudioSink() { + softwareVolumeControl = true; + usign = true; +#ifdef I2S_MODE_DAC_BUILT_IN - i2s_config_t i2s_config = { - .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN), // Only TX - .sample_rate = (i2s_bits_per_sample_t)44100, - .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, - .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, - .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_I2S, - .intr_alloc_flags = 0,//ESP_INTR_FLAG_LEVEL1 - .dma_buf_count = 6, - .dma_buf_len = 512, - .use_apll = true, - .tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow - .fixed_mclk=-1 - }; + i2s_config_t i2s_config = { + .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | + I2S_MODE_DAC_BUILT_IN), // Only TX + .sample_rate = (i2s_bits_per_sample_t)44100, + .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, + .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_I2S, + .intr_alloc_flags = 0, //ESP_INTR_FLAG_LEVEL1 + .dma_buf_count = 6, + .dma_buf_len = 512, + .use_apll = true, + .tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow + .fixed_mclk = -1}; - //install and start i2s driver - i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); - //init DAC - i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN); - #endif + //install and start i2s driver + i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); + //init DAC + i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN); +#endif - startI2sFeed(); + startI2sFeed(); } -InternalAudioSink::~InternalAudioSink() -{ -} +InternalAudioSink::~InternalAudioSink() {} diff --git a/components/spotify/cspot/bell/main/audio-sinks/esp/PCM5102AudioSink.cpp b/components/spotify/cspot/bell/main/audio-sinks/esp/PCM5102AudioSink.cpp index 5cd6ecb6..81f0867a 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/esp/PCM5102AudioSink.cpp +++ b/components/spotify/cspot/bell/main/audio-sinks/esp/PCM5102AudioSink.cpp @@ -2,35 +2,31 @@ #include "driver/i2s.h" -PCM5102AudioSink::PCM5102AudioSink() -{ - i2s_config_t i2s_config = { +PCM5102AudioSink::PCM5102AudioSink() { + i2s_config_t i2s_config = { - .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX - .sample_rate = 44100, - .bits_per_sample = (i2s_bits_per_sample_t)16, - .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels - .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_I2S, - .intr_alloc_flags = 0, //Default interrupt priority - .dma_buf_count = 8, - .dma_buf_len = 512, - .use_apll = true, - .tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow - .fixed_mclk = 384 * 44100 - }; + .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX + .sample_rate = 44100, + .bits_per_sample = (i2s_bits_per_sample_t)16, + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels + .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_I2S, + .intr_alloc_flags = 0, //Default interrupt priority + .dma_buf_count = 8, + .dma_buf_len = 512, + .use_apll = true, + .tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow + .fixed_mclk = 384 * 44100}; - i2s_pin_config_t pin_config = { - .bck_io_num = 27, - .ws_io_num = 32, - .data_out_num = 25, - .data_in_num = -1 //Not used - }; - i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); - i2s_set_pin((i2s_port_t)0, &pin_config); + i2s_pin_config_t pin_config = { + .bck_io_num = 27, + .ws_io_num = 32, + .data_out_num = 25, + .data_in_num = -1 //Not used + }; + i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); + i2s_set_pin((i2s_port_t)0, &pin_config); - startI2sFeed(); + startI2sFeed(); } -PCM5102AudioSink::~PCM5102AudioSink() -{ -} +PCM5102AudioSink::~PCM5102AudioSink() {} diff --git a/components/spotify/cspot/bell/main/audio-sinks/esp/SPDIFAudioSink.cpp b/components/spotify/cspot/bell/main/audio-sinks/esp/SPDIFAudioSink.cpp index d6ce712b..1b4644f0 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/esp/SPDIFAudioSink.cpp +++ b/components/spotify/cspot/bell/main/audio-sinks/esp/SPDIFAudioSink.cpp @@ -5,124 +5,118 @@ // See http://www.hardwarebook.info/S/PDIF for more info on this protocol // Conversion table to biphase code mark (LSB first, ending in 1) static const uint16_t bmc_convert[256] = { - 0x3333, 0xb333, 0xd333, 0x5333, 0xcb33, 0x4b33, 0x2b33, 0xab33, - 0xcd33, 0x4d33, 0x2d33, 0xad33, 0x3533, 0xb533, 0xd533, 0x5533, - 0xccb3, 0x4cb3, 0x2cb3, 0xacb3, 0x34b3, 0xb4b3, 0xd4b3, 0x54b3, - 0x32b3, 0xb2b3, 0xd2b3, 0x52b3, 0xcab3, 0x4ab3, 0x2ab3, 0xaab3, - 0xccd3, 0x4cd3, 0x2cd3, 0xacd3, 0x34d3, 0xb4d3, 0xd4d3, 0x54d3, - 0x32d3, 0xb2d3, 0xd2d3, 0x52d3, 0xcad3, 0x4ad3, 0x2ad3, 0xaad3, - 0x3353, 0xb353, 0xd353, 0x5353, 0xcb53, 0x4b53, 0x2b53, 0xab53, - 0xcd53, 0x4d53, 0x2d53, 0xad53, 0x3553, 0xb553, 0xd553, 0x5553, - 0xcccb, 0x4ccb, 0x2ccb, 0xaccb, 0x34cb, 0xb4cb, 0xd4cb, 0x54cb, - 0x32cb, 0xb2cb, 0xd2cb, 0x52cb, 0xcacb, 0x4acb, 0x2acb, 0xaacb, - 0x334b, 0xb34b, 0xd34b, 0x534b, 0xcb4b, 0x4b4b, 0x2b4b, 0xab4b, - 0xcd4b, 0x4d4b, 0x2d4b, 0xad4b, 0x354b, 0xb54b, 0xd54b, 0x554b, - 0x332b, 0xb32b, 0xd32b, 0x532b, 0xcb2b, 0x4b2b, 0x2b2b, 0xab2b, - 0xcd2b, 0x4d2b, 0x2d2b, 0xad2b, 0x352b, 0xb52b, 0xd52b, 0x552b, - 0xccab, 0x4cab, 0x2cab, 0xacab, 0x34ab, 0xb4ab, 0xd4ab, 0x54ab, - 0x32ab, 0xb2ab, 0xd2ab, 0x52ab, 0xcaab, 0x4aab, 0x2aab, 0xaaab, - 0xcccd, 0x4ccd, 0x2ccd, 0xaccd, 0x34cd, 0xb4cd, 0xd4cd, 0x54cd, - 0x32cd, 0xb2cd, 0xd2cd, 0x52cd, 0xcacd, 0x4acd, 0x2acd, 0xaacd, - 0x334d, 0xb34d, 0xd34d, 0x534d, 0xcb4d, 0x4b4d, 0x2b4d, 0xab4d, - 0xcd4d, 0x4d4d, 0x2d4d, 0xad4d, 0x354d, 0xb54d, 0xd54d, 0x554d, - 0x332d, 0xb32d, 0xd32d, 0x532d, 0xcb2d, 0x4b2d, 0x2b2d, 0xab2d, - 0xcd2d, 0x4d2d, 0x2d2d, 0xad2d, 0x352d, 0xb52d, 0xd52d, 0x552d, - 0xccad, 0x4cad, 0x2cad, 0xacad, 0x34ad, 0xb4ad, 0xd4ad, 0x54ad, - 0x32ad, 0xb2ad, 0xd2ad, 0x52ad, 0xcaad, 0x4aad, 0x2aad, 0xaaad, - 0x3335, 0xb335, 0xd335, 0x5335, 0xcb35, 0x4b35, 0x2b35, 0xab35, - 0xcd35, 0x4d35, 0x2d35, 0xad35, 0x3535, 0xb535, 0xd535, 0x5535, - 0xccb5, 0x4cb5, 0x2cb5, 0xacb5, 0x34b5, 0xb4b5, 0xd4b5, 0x54b5, - 0x32b5, 0xb2b5, 0xd2b5, 0x52b5, 0xcab5, 0x4ab5, 0x2ab5, 0xaab5, - 0xccd5, 0x4cd5, 0x2cd5, 0xacd5, 0x34d5, 0xb4d5, 0xd4d5, 0x54d5, - 0x32d5, 0xb2d5, 0xd2d5, 0x52d5, 0xcad5, 0x4ad5, 0x2ad5, 0xaad5, - 0x3355, 0xb355, 0xd355, 0x5355, 0xcb55, 0x4b55, 0x2b55, 0xab55, - 0xcd55, 0x4d55, 0x2d55, 0xad55, 0x3555, 0xb555, 0xd555, 0x5555, + 0x3333, 0xb333, 0xd333, 0x5333, 0xcb33, 0x4b33, 0x2b33, 0xab33, 0xcd33, + 0x4d33, 0x2d33, 0xad33, 0x3533, 0xb533, 0xd533, 0x5533, 0xccb3, 0x4cb3, + 0x2cb3, 0xacb3, 0x34b3, 0xb4b3, 0xd4b3, 0x54b3, 0x32b3, 0xb2b3, 0xd2b3, + 0x52b3, 0xcab3, 0x4ab3, 0x2ab3, 0xaab3, 0xccd3, 0x4cd3, 0x2cd3, 0xacd3, + 0x34d3, 0xb4d3, 0xd4d3, 0x54d3, 0x32d3, 0xb2d3, 0xd2d3, 0x52d3, 0xcad3, + 0x4ad3, 0x2ad3, 0xaad3, 0x3353, 0xb353, 0xd353, 0x5353, 0xcb53, 0x4b53, + 0x2b53, 0xab53, 0xcd53, 0x4d53, 0x2d53, 0xad53, 0x3553, 0xb553, 0xd553, + 0x5553, 0xcccb, 0x4ccb, 0x2ccb, 0xaccb, 0x34cb, 0xb4cb, 0xd4cb, 0x54cb, + 0x32cb, 0xb2cb, 0xd2cb, 0x52cb, 0xcacb, 0x4acb, 0x2acb, 0xaacb, 0x334b, + 0xb34b, 0xd34b, 0x534b, 0xcb4b, 0x4b4b, 0x2b4b, 0xab4b, 0xcd4b, 0x4d4b, + 0x2d4b, 0xad4b, 0x354b, 0xb54b, 0xd54b, 0x554b, 0x332b, 0xb32b, 0xd32b, + 0x532b, 0xcb2b, 0x4b2b, 0x2b2b, 0xab2b, 0xcd2b, 0x4d2b, 0x2d2b, 0xad2b, + 0x352b, 0xb52b, 0xd52b, 0x552b, 0xccab, 0x4cab, 0x2cab, 0xacab, 0x34ab, + 0xb4ab, 0xd4ab, 0x54ab, 0x32ab, 0xb2ab, 0xd2ab, 0x52ab, 0xcaab, 0x4aab, + 0x2aab, 0xaaab, 0xcccd, 0x4ccd, 0x2ccd, 0xaccd, 0x34cd, 0xb4cd, 0xd4cd, + 0x54cd, 0x32cd, 0xb2cd, 0xd2cd, 0x52cd, 0xcacd, 0x4acd, 0x2acd, 0xaacd, + 0x334d, 0xb34d, 0xd34d, 0x534d, 0xcb4d, 0x4b4d, 0x2b4d, 0xab4d, 0xcd4d, + 0x4d4d, 0x2d4d, 0xad4d, 0x354d, 0xb54d, 0xd54d, 0x554d, 0x332d, 0xb32d, + 0xd32d, 0x532d, 0xcb2d, 0x4b2d, 0x2b2d, 0xab2d, 0xcd2d, 0x4d2d, 0x2d2d, + 0xad2d, 0x352d, 0xb52d, 0xd52d, 0x552d, 0xccad, 0x4cad, 0x2cad, 0xacad, + 0x34ad, 0xb4ad, 0xd4ad, 0x54ad, 0x32ad, 0xb2ad, 0xd2ad, 0x52ad, 0xcaad, + 0x4aad, 0x2aad, 0xaaad, 0x3335, 0xb335, 0xd335, 0x5335, 0xcb35, 0x4b35, + 0x2b35, 0xab35, 0xcd35, 0x4d35, 0x2d35, 0xad35, 0x3535, 0xb535, 0xd535, + 0x5535, 0xccb5, 0x4cb5, 0x2cb5, 0xacb5, 0x34b5, 0xb4b5, 0xd4b5, 0x54b5, + 0x32b5, 0xb2b5, 0xd2b5, 0x52b5, 0xcab5, 0x4ab5, 0x2ab5, 0xaab5, 0xccd5, + 0x4cd5, 0x2cd5, 0xacd5, 0x34d5, 0xb4d5, 0xd4d5, 0x54d5, 0x32d5, 0xb2d5, + 0xd2d5, 0x52d5, 0xcad5, 0x4ad5, 0x2ad5, 0xaad5, 0x3355, 0xb355, 0xd355, + 0x5355, 0xcb55, 0x4b55, 0x2b55, 0xab55, 0xcd55, 0x4d55, 0x2d55, 0xad55, + 0x3555, 0xb555, 0xd555, 0x5555, }; -#define I2S_BUG_MAGIC (26 * 1000 * 1000) // magic number for avoiding I2S bug -#define BITS_PER_SUBFRAME 64 -#define FRAMES_PER_BLOCK 192 -#define SPDIF_BUF_SIZE (BITS_PER_SUBFRAME/8 * 2 * FRAMES_PER_BLOCK) -#define SPDIF_BUF_ARRAY_SIZE (SPDIF_BUF_SIZE / sizeof(uint32_t)) +#define I2S_BUG_MAGIC (26 * 1000 * 1000) // magic number for avoiding I2S bug +#define BITS_PER_SUBFRAME 64 +#define FRAMES_PER_BLOCK 192 +#define SPDIF_BUF_SIZE (BITS_PER_SUBFRAME / 8 * 2 * FRAMES_PER_BLOCK) +#define SPDIF_BUF_ARRAY_SIZE (SPDIF_BUF_SIZE / sizeof(uint32_t)) -#define BMC_B 0x33173333 // block start -#define BMC_M 0x331d3333 // left ch -#define BMC_W 0x331b3333 // right ch -#define BMC_MW_DIF (BMC_M ^ BMC_W) +#define BMC_B 0x33173333 // block start +#define BMC_M 0x331d3333 // left ch +#define BMC_W 0x331b3333 // right ch +#define BMC_MW_DIF (BMC_M ^ BMC_W) static uint32_t spdif_buf[SPDIF_BUF_ARRAY_SIZE]; -static uint32_t *spdif_ptr; +static uint32_t* spdif_ptr; -static void spdif_buf_init(void) -{ - // first bllock has W preamble - spdif_buf[0] = BMC_B; +static void spdif_buf_init(void) { + // first bllock has W preamble + spdif_buf[0] = BMC_B; - // all other blocks are alternating M, then W preamble - uint32_t bmc_mw = BMC_M; - for (int i = 2; i < SPDIF_BUF_ARRAY_SIZE; i += 2) - { - spdif_buf[i] = bmc_mw ^= BMC_MW_DIF; - } + // all other blocks are alternating M, then W preamble + uint32_t bmc_mw = BMC_M; + for (int i = 2; i < SPDIF_BUF_ARRAY_SIZE; i += 2) { + spdif_buf[i] = bmc_mw ^= BMC_MW_DIF; + } } -SPDIFAudioSink::SPDIFAudioSink(uint8_t spdifPin) -{ - // initialize S/PDIF buffer - spdif_buf_init(); - spdif_ptr = spdif_buf; - this->spdifPin = spdifPin; - this->setParams(44100, 16, 2); - startI2sFeed(SPDIF_BUF_SIZE * 16); +SPDIFAudioSink::SPDIFAudioSink(uint8_t spdifPin) { + // initialize S/PDIF buffer + spdif_buf_init(); + spdif_ptr = spdif_buf; + this->spdifPin = spdifPin; + this->setParams(44100, 16, 2); + startI2sFeed(SPDIF_BUF_SIZE * 16); } -bool SPDIFAudioSink::setParams(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) { - if (bitDepth != 16 || channelCount != 2) // TODO support mono playback and different bit widths - return false; - int sample_rate = (int)sampleRate * 2; - int bclk = sample_rate * 64 * 2; - int mclk = (I2S_BUG_MAGIC / bclk) * bclk; +bool SPDIFAudioSink::setParams(uint32_t sampleRate, uint8_t channelCount, + uint8_t bitDepth) { + if (bitDepth != 16 || + channelCount != 2) // TODO support mono playback and different bit widths + return false; + int sample_rate = (int)sampleRate * 2; + int bclk = sample_rate * 64 * 2; + int mclk = (I2S_BUG_MAGIC / bclk) * bclk; - i2s_config_t i2s_config = { - .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), + i2s_config_t i2s_config = { + .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0) - .sample_rate = (uint32_t) sample_rate, + .sample_rate = (uint32_t)sample_rate, #else - .sample_rate = (int) sample_rate, + .sample_rate = (int)sample_rate, #endif - .bits_per_sample = (i2s_bits_per_sample_t)(bitDepth * 2), - .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, - .communication_format = I2S_COMM_FORMAT_STAND_I2S, - .intr_alloc_flags = 0, - .dma_buf_count = 8, - .dma_buf_len = 512, - .use_apll = true, - .tx_desc_auto_clear = true, - .fixed_mclk = mclk, // avoiding I2S bug - }; - i2s_pin_config_t pin_config = { - .bck_io_num = -1, - .ws_io_num = -1, - .data_out_num = spdifPin, - .data_in_num = -1, - }; - i2s_driver_uninstall((i2s_port_t)0); - int err = i2s_driver_install((i2s_port_t)0, &i2s_config, 0, nullptr); - i2s_set_pin((i2s_port_t)0, &pin_config); - return !err; + .bits_per_sample = (i2s_bits_per_sample_t)(bitDepth * 2), + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, + .communication_format = I2S_COMM_FORMAT_STAND_I2S, + .intr_alloc_flags = 0, + .dma_buf_count = 8, + .dma_buf_len = 512, + .use_apll = true, + .tx_desc_auto_clear = true, + .fixed_mclk = mclk, // avoiding I2S bug + }; + i2s_pin_config_t pin_config = { + .bck_io_num = -1, + .ws_io_num = -1, + .data_out_num = spdifPin, + .data_in_num = -1, + }; + i2s_driver_uninstall((i2s_port_t)0); + int err = i2s_driver_install((i2s_port_t)0, &i2s_config, 0, nullptr); + i2s_set_pin((i2s_port_t)0, &pin_config); + return !err; } SPDIFAudioSink::~SPDIFAudioSink() { - i2s_driver_uninstall((i2s_port_t)0); + i2s_driver_uninstall((i2s_port_t)0); } int num_frames = 0; -void SPDIFAudioSink::feedPCMFrames(const uint8_t *buffer, size_t bytes) -{ - for (int i = 0; i < bytes; i += 2) - { - /** +void SPDIFAudioSink::feedPCMFrames(const uint8_t* buffer, size_t bytes) { + for (int i = 0; i < bytes; i += 2) { + /** * What is this, and why does it work? * * Rather than assemble all S/PDIF frames from scratch we want to do the @@ -171,16 +165,16 @@ void SPDIFAudioSink::feedPCMFrames(const uint8_t *buffer, size_t bytes) * I did not come up with this, all credit goes to * github.com/amedes/esp_a2dp_sink_spdif */ - uint32_t lo = ((uint32_t)(bmc_convert[buffer[i]]) << 16); - uint32_t hi = (uint32_t)((int16_t)bmc_convert[buffer[i+1]]); + uint32_t lo = ((uint32_t)(bmc_convert[buffer[i]]) << 16); + uint32_t hi = (uint32_t)((int16_t)bmc_convert[buffer[i + 1]]); - *(spdif_ptr + 1) = ((lo ^ hi) << 1) >> 1; + *(spdif_ptr + 1) = ((lo ^ hi) << 1) >> 1; - spdif_ptr += 2; // advance to next audio data - - if (spdif_ptr >= &spdif_buf[SPDIF_BUF_ARRAY_SIZE]) { - feedPCMFramesInternal(spdif_buf, sizeof(spdif_buf)); - spdif_ptr = spdif_buf; - } + spdif_ptr += 2; // advance to next audio data + + if (spdif_ptr >= &spdif_buf[SPDIF_BUF_ARRAY_SIZE]) { + feedPCMFramesInternal(spdif_buf, sizeof(spdif_buf)); + spdif_ptr = spdif_buf; } + } } \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/esp/TAS5711AudioSink.cpp b/components/spotify/cspot/bell/main/audio-sinks/esp/TAS5711AudioSink.cpp index ecee28da..935c5fba 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/esp/TAS5711AudioSink.cpp +++ b/components/spotify/cspot/bell/main/audio-sinks/esp/TAS5711AudioSink.cpp @@ -1,117 +1,107 @@ #include "TAS5711AudioSink.h" - struct tas5711_cmd_s { - uint8_t reg; - uint8_t value; + uint8_t reg; + uint8_t value; }; static const struct tas5711_cmd_s tas5711_init_sequence[] = { - { 0x00, 0x6c }, // 0x6c - 256 x mclk - { 0x04, 0x03 }, // 0x03 - 16 bit i2s - { 0x05, 0x00 }, // system control 0x00 is audio playback - { 0x06, 0x00 }, // disable mute - { 0x07, 0x50 }, // volume register - { 0xff, 0xff } + {0x00, 0x6c}, // 0x6c - 256 x mclk + {0x04, 0x03}, // 0x03 - 16 bit i2s + {0x05, 0x00}, // system control 0x00 is audio playback + {0x06, 0x00}, // disable mute + {0x07, 0x50}, // volume register + {0xff, 0xff} }; i2c_ack_type_t ACK_CHECK_EN = (i2c_ack_type_t)0x1; -TAS5711AudioSink::TAS5711AudioSink() -{ - i2s_config_t i2s_config = { +TAS5711AudioSink::TAS5711AudioSink() { + i2s_config_t i2s_config = { - .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX - .sample_rate = 44100, - .bits_per_sample = (i2s_bits_per_sample_t)16, - .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels - .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_MSB, - .intr_alloc_flags = 0, //Default interrupt priority - .dma_buf_count = 8, - .dma_buf_len = 512, - .use_apll = true, - .tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow - .fixed_mclk = 256 * 44100 - }; + .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), // Only TX + .sample_rate = 44100, + .bits_per_sample = (i2s_bits_per_sample_t)16, + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels + .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_MSB, + .intr_alloc_flags = 0, //Default interrupt priority + .dma_buf_count = 8, + .dma_buf_len = 512, + .use_apll = true, + .tx_desc_auto_clear = true, //Auto clear tx descriptor on underflow + .fixed_mclk = 256 * 44100}; + i2s_pin_config_t pin_config = { + .bck_io_num = 5, + .ws_io_num = 25, + .data_out_num = 26, + .data_in_num = -1 //Not used + }; + i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); + i2s_set_pin((i2s_port_t)0, &pin_config); - i2s_pin_config_t pin_config = { - .bck_io_num = 5, - .ws_io_num = 25, - .data_out_num = 26, - .data_in_num = -1 //Not used - }; - i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL); - i2s_set_pin((i2s_port_t)0, &pin_config); + // configure i2c + i2c_config = { + .mode = I2C_MODE_MASTER, + .sda_io_num = 21, + .scl_io_num = 23, + .sda_pullup_en = GPIO_PULLUP_DISABLE, + .scl_pullup_en = GPIO_PULLUP_DISABLE, + }; - // configure i2c - i2c_config = { - .mode = I2C_MODE_MASTER, - .sda_io_num = 21, - .scl_io_num = 23, - .sda_pullup_en = GPIO_PULLUP_DISABLE, - .scl_pullup_en = GPIO_PULLUP_DISABLE, - }; + i2c_config.master.clk_speed = 250000; - i2c_config.master.clk_speed = 250000; + i2c_param_config(i2c_port, &i2c_config); + i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false); + i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create(); - i2c_param_config(i2c_port, &i2c_config); - i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false); - i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create(); + uint8_t data, addr = (0x1b); - uint8_t data, addr = (0x1b); + i2c_master_start(i2c_cmd); + i2c_master_write_byte(i2c_cmd, (addr << 1) | I2C_MASTER_WRITE, ACK_CHECK_EN); + i2c_master_write_byte(i2c_cmd, 00, ACK_CHECK_EN); - i2c_master_start(i2c_cmd); - i2c_master_write_byte(i2c_cmd, (addr << 1) | I2C_MASTER_WRITE, ACK_CHECK_EN); - i2c_master_write_byte(i2c_cmd, 00, ACK_CHECK_EN); + i2c_master_start(i2c_cmd); + i2c_master_write_byte(i2c_cmd, (addr << 1) | I2C_MASTER_READ, ACK_CHECK_EN); + i2c_master_read_byte(i2c_cmd, &data, ACK_CHECK_EN); - i2c_master_start(i2c_cmd); - i2c_master_write_byte(i2c_cmd, (addr << 1) | I2C_MASTER_READ, ACK_CHECK_EN); - i2c_master_read_byte(i2c_cmd, &data, ACK_CHECK_EN); + i2c_master_stop(i2c_cmd); + int ret = i2c_master_cmd_begin(i2c_port, i2c_cmd, 50 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(i2c_cmd); - i2c_master_stop(i2c_cmd); - int ret = i2c_master_cmd_begin(i2c_port, i2c_cmd, 50 / portTICK_PERIOD_MS); - i2c_cmd_link_delete(i2c_cmd); + if (ret == ESP_OK) { + ESP_LOGI("RR", "Detected TAS"); + } else { + ESP_LOGI("RR", "Unable to detect dac"); + } - if (ret == ESP_OK) { - ESP_LOGI("RR", "Detected TAS"); - } - else { - ESP_LOGI("RR", "Unable to detect dac"); - } + writeReg(0x1b, 0x00); + vTaskDelay(100 / portTICK_PERIOD_MS); - writeReg(0x1b, 0x00); - vTaskDelay(100 / portTICK_PERIOD_MS); + for (int i = 0; tas5711_init_sequence[i].reg != 0xff; i++) { + writeReg(tas5711_init_sequence[i].reg, tas5711_init_sequence[i].value); + vTaskDelay(1 / portTICK_PERIOD_MS); + } - - for (int i = 0; tas5711_init_sequence[i].reg != 0xff; i++) { - writeReg(tas5711_init_sequence[i].reg, tas5711_init_sequence[i].value); - vTaskDelay(1 / portTICK_PERIOD_MS); - } - - startI2sFeed(); + startI2sFeed(); } -void TAS5711AudioSink::writeReg(uint8_t reg, uint8_t value) -{ - i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create(); +void TAS5711AudioSink::writeReg(uint8_t reg, uint8_t value) { + i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create(); - i2c_master_start(i2c_cmd); - i2c_master_write_byte(i2c_cmd, (0x1b << 1) | I2C_MASTER_WRITE, ACK_CHECK_EN); - i2c_master_write_byte(i2c_cmd, reg, ACK_CHECK_EN); - i2c_master_write_byte(i2c_cmd, value, ACK_CHECK_EN); + i2c_master_start(i2c_cmd); + i2c_master_write_byte(i2c_cmd, (0x1b << 1) | I2C_MASTER_WRITE, ACK_CHECK_EN); + i2c_master_write_byte(i2c_cmd, reg, ACK_CHECK_EN); + i2c_master_write_byte(i2c_cmd, value, ACK_CHECK_EN); + i2c_master_stop(i2c_cmd); + esp_err_t res = + i2c_master_cmd_begin(i2c_port, i2c_cmd, 500 / portTICK_PERIOD_MS); - i2c_master_stop(i2c_cmd); - esp_err_t res = i2c_master_cmd_begin(i2c_port, i2c_cmd, 500 / portTICK_PERIOD_MS); - - if (res != ESP_OK) { - ESP_LOGE("RR", "Unable to write to TAS5711"); - } - i2c_cmd_link_delete(i2c_cmd); - + if (res != ESP_OK) { + ESP_LOGE("RR", "Unable to write to TAS5711"); + } + i2c_cmd_link_delete(i2c_cmd); } -TAS5711AudioSink::~TAS5711AudioSink() -{ -} +TAS5711AudioSink::~TAS5711AudioSink() {} diff --git a/components/spotify/cspot/bell/main/audio-sinks/esp/ac101.c b/components/spotify/cspot/bell/main/audio-sinks/esp/ac101.c index c05abd86..d0fdbd0e 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/esp/ac101.c +++ b/components/spotify/cspot/bell/main/audio-sinks/esp/ac101.c @@ -22,16 +22,16 @@ * */ -#include -#include -#include -#include -#include -#include +#include "ac101.h" #include #include +#include +#include +#include +#include +#include +#include #include "adac.h" -#include "ac101.h" const static char TAG[] = "AC101"; @@ -42,14 +42,13 @@ const static char TAG[] = "AC101"; #define min(a, b) (((a) < (b)) ? (a) : (b)) #define max(a, b) (((a) > (b)) ? (a) : (b)) -#define AC_ASSERT(a, format, b, ...) \ - if ((a) != 0) \ - { \ - ESP_LOGE(TAG, format, ##__VA_ARGS__); \ - return b; \ - } +#define AC_ASSERT(a, format, b, ...) \ + if ((a) != 0) { \ + ESP_LOGE(TAG, format, ##__VA_ARGS__); \ + return b; \ + } -static bool init(int i2c_port_num, int i2s_num, i2s_config_t *config); +static bool init(int i2c_port_num, int i2s_num, i2s_config_t* config); static void deinit(void); static void speaker(bool active); static void headset(bool active); @@ -71,356 +70,357 @@ static int i2c_port; /**************************************************************************************** * init */ -static bool init(int i2c_port_num, int i2s_num, i2s_config_t *i2s_config) -{ - esp_err_t res = ESP_OK; +static bool init(int i2c_port_num, int i2s_num, i2s_config_t* i2s_config) { + esp_err_t res = ESP_OK; - i2c_port = i2c_port_num; + i2c_port = i2c_port_num; - // configure i2c - i2c_config_t i2c_config = { - .mode = I2C_MODE_MASTER, - .sda_io_num = 33, - .sda_pullup_en = GPIO_PULLUP_ENABLE, - .scl_io_num = 32, - .scl_pullup_en = GPIO_PULLUP_ENABLE, - .master.clk_speed = 250000, - }; + // configure i2c + i2c_config_t i2c_config = { + .mode = I2C_MODE_MASTER, + .sda_io_num = 33, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_io_num = 32, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .master.clk_speed = 250000, + }; - i2c_param_config(i2c_port, &i2c_config); - i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false); + i2c_param_config(i2c_port, &i2c_config); + i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false); - res = i2c_read_reg(CHIP_AUDIO_RS); + res = i2c_read_reg(CHIP_AUDIO_RS); - if (!res) - { - ESP_LOGW(TAG, "No AC101 detected"); - i2c_driver_delete(i2c_port); - return 0; - } + if (!res) { + ESP_LOGW(TAG, "No AC101 detected"); + i2c_driver_delete(i2c_port); + return 0; + } - ESP_LOGI(TAG, "AC101 DAC using I2C sda:%u, scl:%u", i2c_config.sda_io_num, i2c_config.scl_io_num); + ESP_LOGI(TAG, "AC101 DAC using I2C sda:%u, scl:%u", i2c_config.sda_io_num, + i2c_config.scl_io_num); - res = i2c_write_reg(CHIP_AUDIO_RS, 0x123); - // huh? - vTaskDelay(100 / portTICK_PERIOD_MS); + res = i2c_write_reg(CHIP_AUDIO_RS, 0x123); + // huh? + vTaskDelay(100 / portTICK_PERIOD_MS); - // enable the PLL from BCLK source - i2c_write_reg(PLL_CTRL1, BIN(0000, 0001, 0100, 1111)); // F=1,M=1,PLL,INT=31 (medium) - i2c_write_reg(PLL_CTRL2, BIN(1000, 0110, 0000, 0000)); // PLL, F=96,N_i=1024-96,F=0,N_f=0*0.2; - // i2c_write_reg(PLL_CTRL2, BIN(1000,0011,1100,0000)); + // enable the PLL from BCLK source + i2c_write_reg(PLL_CTRL1, + BIN(0000, 0001, 0100, 1111)); // F=1,M=1,PLL,INT=31 (medium) + i2c_write_reg(PLL_CTRL2, BIN(1000, 0110, 0000, + 0000)); // PLL, F=96,N_i=1024-96,F=0,N_f=0*0.2; + // i2c_write_reg(PLL_CTRL2, BIN(1000,0011,1100,0000)); - // clocking system - i2c_write_reg(SYSCLK_CTRL, BIN(1010, 1010, 0000, 1000)); // PLLCLK, BCLK1, IS1CLK, PLL, SYSCLK - i2c_write_reg(MOD_CLK_ENA, BIN(1000, 0000, 0000, 1100)); // IS21, ADC, DAC - i2c_write_reg(MOD_RST_CTRL, BIN(1000, 0000, 0000, 1100)); // IS21, ADC, DAC - i2c_write_reg(I2S_SR_CTRL, BIN(0111, 0000, 0000, 0000)); // 44.1kHz + // clocking system + i2c_write_reg(SYSCLK_CTRL, BIN(1010, 1010, 0000, + 1000)); // PLLCLK, BCLK1, IS1CLK, PLL, SYSCLK + i2c_write_reg(MOD_CLK_ENA, BIN(1000, 0000, 0000, 1100)); // IS21, ADC, DAC + i2c_write_reg(MOD_RST_CTRL, BIN(1000, 0000, 0000, 1100)); // IS21, ADC, DAC + i2c_write_reg(I2S_SR_CTRL, BIN(0111, 0000, 0000, 0000)); // 44.1kHz - // analogue config - i2c_write_reg(I2S1LCK_CTRL, BIN(1000, 1000, 0101, 0000)); // Slave, BCLK=I2S/8,LRCK=32,16bits,I2Smode, Stereo - i2c_write_reg(I2S1_SDOUT_CTRL, BIN(1100, 0000, 0000, 0000)); // I2S1ADC (R&L) - i2c_write_reg(I2S1_SDIN_CTRL, BIN(1100, 0000, 0000, 0000)); // IS21DAC (R&L) - i2c_write_reg(I2S1_MXR_SRC, BIN(0010, 0010, 0000, 0000)); // ADCL, ADCR - i2c_write_reg(ADC_SRCBST_CTRL, BIN(0100, 0100, 0100, 0000)); // disable all boost (default) + // analogue config + i2c_write_reg(I2S1LCK_CTRL, + BIN(1000, 1000, 0101, + 0000)); // Slave, BCLK=I2S/8,LRCK=32,16bits,I2Smode, Stereo + i2c_write_reg(I2S1_SDOUT_CTRL, BIN(1100, 0000, 0000, 0000)); // I2S1ADC (R&L) + i2c_write_reg(I2S1_SDIN_CTRL, BIN(1100, 0000, 0000, 0000)); // IS21DAC (R&L) + i2c_write_reg(I2S1_MXR_SRC, BIN(0010, 0010, 0000, 0000)); // ADCL, ADCR + i2c_write_reg(ADC_SRCBST_CTRL, + BIN(0100, 0100, 0100, 0000)); // disable all boost (default) #if ENABLE_ADC - i2c_write_reg(ADC_SRC, BIN(0000, 0100, 0000, 1000)); // source=linein(R/L) - i2c_write_reg(ADC_DIG_CTRL, BIN(1000, 0000, 0000, 0000)); // enable digital ADC - i2c_write_reg(ADC_ANA_CTRL, BIN(1011, 1011, 0000, 0000)); // enable analogue R/L, 0dB + i2c_write_reg(ADC_SRC, BIN(0000, 0100, 0000, 1000)); // source=linein(R/L) + i2c_write_reg(ADC_DIG_CTRL, + BIN(1000, 0000, 0000, 0000)); // enable digital ADC + i2c_write_reg(ADC_ANA_CTRL, + BIN(1011, 1011, 0000, 0000)); // enable analogue R/L, 0dB #else - i2c_write_reg(ADC_SRC, BIN(0000, 0000, 0000, 0000)); // source=none - i2c_write_reg(ADC_DIG_CTRL, BIN(0000, 0000, 0000, 0000)); // disable digital ADC - i2c_write_reg(ADC_ANA_CTRL, BIN(0011, 0011, 0000, 0000)); // disable analogue R/L, 0dB + i2c_write_reg(ADC_SRC, BIN(0000, 0000, 0000, 0000)); // source=none + i2c_write_reg(ADC_DIG_CTRL, + BIN(0000, 0000, 0000, 0000)); // disable digital ADC + i2c_write_reg(ADC_ANA_CTRL, + BIN(0011, 0011, 0000, 0000)); // disable analogue R/L, 0dB #endif - //Path Configuration - i2c_write_reg(DAC_MXR_SRC, BIN(1000, 1000, 0000, 0000)); // DAC from I2S - i2c_write_reg(DAC_DIG_CTRL, BIN(1000, 0000, 0000, 0000)); // enable DAC - i2c_write_reg(OMIXER_DACA_CTRL, BIN(1111, 0000, 0000, 0000)); // enable DAC/Analogue (see note on offset removal and PA) - i2c_write_reg(OMIXER_DACA_CTRL, BIN(1111, 1111, 0000, 0000)); // this toggle is needed for headphone PA offset + //Path Configuration + i2c_write_reg(DAC_MXR_SRC, BIN(1000, 1000, 0000, 0000)); // DAC from I2S + i2c_write_reg(DAC_DIG_CTRL, BIN(1000, 0000, 0000, 0000)); // enable DAC + i2c_write_reg( + OMIXER_DACA_CTRL, + BIN(1111, 0000, 0000, + 0000)); // enable DAC/Analogue (see note on offset removal and PA) + i2c_write_reg(OMIXER_DACA_CTRL, + BIN(1111, 1111, 0000, + 0000)); // this toggle is needed for headphone PA offset #if ENABLE_ADC - i2c_write_reg(OMIXER_SR, BIN(0000, 0001, 0000, 0010)); // source=DAC(R/L) (are DACR and DACL really inverted in bitmap?) + i2c_write_reg( + OMIXER_SR, + BIN(0000, 0001, 0000, + 0010)); // source=DAC(R/L) (are DACR and DACL really inverted in bitmap?) #else - i2c_write_reg(OMIXER_SR, BIN(0000, 0101, 0000, 1010)); // source=DAC(R/L) and LINEIN(R/L) + i2c_write_reg(OMIXER_SR, BIN(0000, 0101, 0000, + 1010)); // source=DAC(R/L) and LINEIN(R/L) #endif - // configure I2S pins & install driver - i2s_pin_config_t i2s_pin_config = (i2s_pin_config_t){.bck_io_num = 27, .ws_io_num = 26, .data_out_num = 25, .data_in_num = -1}; - res |= i2s_driver_install(i2s_num, i2s_config, 0, NULL); - res |= i2s_set_pin(i2s_num, &i2s_pin_config); + // configure I2S pins & install driver + i2s_pin_config_t i2s_pin_config = (i2s_pin_config_t){ + .bck_io_num = 27, .ws_io_num = 26, .data_out_num = 25, .data_in_num = -1}; + res |= i2s_driver_install(i2s_num, i2s_config, 0, NULL); + res |= i2s_set_pin(i2s_num, &i2s_pin_config); - // enable earphone & speaker - i2c_write_reg(SPKOUT_CTRL, 0x0220); - i2c_write_reg(HPOUT_CTRL, 0xf801); + // enable earphone & speaker + i2c_write_reg(SPKOUT_CTRL, 0x0220); + i2c_write_reg(HPOUT_CTRL, 0xf801); - // set gain for speaker and earphone - ac101_set_spk_volume(70); - ac101_set_earph_volume(70); + // set gain for speaker and earphone + ac101_set_spk_volume(70); + ac101_set_earph_volume(70); - ESP_LOGI(TAG, "DAC using I2S bck:%d, ws:%d, do:%d", i2s_pin_config.bck_io_num, i2s_pin_config.ws_io_num, i2s_pin_config.data_out_num); + ESP_LOGI(TAG, "DAC using I2S bck:%d, ws:%d, do:%d", i2s_pin_config.bck_io_num, + i2s_pin_config.ws_io_num, i2s_pin_config.data_out_num); - return (res == ESP_OK); + return (res == ESP_OK); } /**************************************************************************************** * init */ -static void deinit(void) -{ - i2c_driver_delete(i2c_port); +static void deinit(void) { + i2c_driver_delete(i2c_port); } /**************************************************************************************** * change volume */ -static void volume(unsigned left, unsigned right) -{ - ac101_set_earph_volume(left); - // nothing at that point, volume is handled by backend +static void volume(unsigned left, unsigned right) { + ac101_set_earph_volume(left); + // nothing at that point, volume is handled by backend } /**************************************************************************************** * power */ -static void power(adac_power_e mode) -{ - switch (mode) - { - case ADAC_STANDBY: - case ADAC_OFF: - ac101_stop(); - break; - case ADAC_ON: - ac101_start(AC_MODULE_DAC); - break; - default: - ESP_LOGW(TAG, "unknown power command"); - break; - } +static void power(adac_power_e mode) { + switch (mode) { + case ADAC_STANDBY: + case ADAC_OFF: + ac101_stop(); + break; + case ADAC_ON: + ac101_start(AC_MODULE_DAC); + break; + default: + ESP_LOGW(TAG, "unknown power command"); + break; + } } /**************************************************************************************** * speaker */ -static void speaker(bool active) -{ - uint16_t value = i2c_read_reg(SPKOUT_CTRL); - if (active) - i2c_write_reg(SPKOUT_CTRL, value | SPKOUT_EN); - else - i2c_write_reg(SPKOUT_CTRL, value & ~SPKOUT_EN); +static void speaker(bool active) { + uint16_t value = i2c_read_reg(SPKOUT_CTRL); + if (active) + i2c_write_reg(SPKOUT_CTRL, value | SPKOUT_EN); + else + i2c_write_reg(SPKOUT_CTRL, value & ~SPKOUT_EN); } /**************************************************************************************** * headset */ -static void headset(bool active) -{ - // there might be aneed to toggle OMIXER_DACA_CTRL 11:8, not sure - uint16_t value = i2c_read_reg(HPOUT_CTRL); - if (active) - i2c_write_reg(HPOUT_CTRL, value | EAROUT_EN); - else - i2c_write_reg(HPOUT_CTRL, value & ~EAROUT_EN); +static void headset(bool active) { + // there might be aneed to toggle OMIXER_DACA_CTRL 11:8, not sure + uint16_t value = i2c_read_reg(HPOUT_CTRL); + if (active) + i2c_write_reg(HPOUT_CTRL, value | EAROUT_EN); + else + i2c_write_reg(HPOUT_CTRL, value & ~EAROUT_EN); } /**************************************************************************************** * */ -static esp_err_t i2c_write_reg(uint8_t reg, uint16_t val) -{ - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - esp_err_t ret = 0; - uint8_t send_buff[4]; - send_buff[0] = (AC101_ADDR << 1); - send_buff[1] = reg; - send_buff[2] = (val >> 8) & 0xff; - send_buff[3] = val & 0xff; - ret |= i2c_master_start(cmd); - ret |= i2c_master_write(cmd, send_buff, 4, ACK_CHECK_EN); - ret |= i2c_master_stop(cmd); - ret |= i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_PERIOD_MS); - i2c_cmd_link_delete(cmd); - return ret; +static esp_err_t i2c_write_reg(uint8_t reg, uint16_t val) { + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + esp_err_t ret = 0; + uint8_t send_buff[4]; + send_buff[0] = (AC101_ADDR << 1); + send_buff[1] = reg; + send_buff[2] = (val >> 8) & 0xff; + send_buff[3] = val & 0xff; + ret |= i2c_master_start(cmd); + ret |= i2c_master_write(cmd, send_buff, 4, ACK_CHECK_EN); + ret |= i2c_master_stop(cmd); + ret |= i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); + return ret; } /**************************************************************************************** * */ -static uint16_t i2c_read_reg(uint8_t reg) -{ - uint8_t data[2] = {0}; +static uint16_t i2c_read_reg(uint8_t reg) { + uint8_t data[2] = {0}; - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - i2c_master_start(cmd); - i2c_master_write_byte(cmd, (AC101_ADDR << 1) | WRITE_BIT, ACK_CHECK_EN); - i2c_master_write_byte(cmd, reg, ACK_CHECK_EN); - i2c_master_start(cmd); - i2c_master_write_byte(cmd, (AC101_ADDR << 1) | READ_BIT, ACK_CHECK_EN); //check or not - i2c_master_read(cmd, data, 2, ACK_VAL); - i2c_master_stop(cmd); - i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_PERIOD_MS); - i2c_cmd_link_delete(cmd); + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (AC101_ADDR << 1) | WRITE_BIT, ACK_CHECK_EN); + i2c_master_write_byte(cmd, reg, ACK_CHECK_EN); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (AC101_ADDR << 1) | READ_BIT, + ACK_CHECK_EN); //check or not + i2c_master_read(cmd, data, 2, ACK_VAL); + i2c_master_stop(cmd); + i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); - return (data[0] << 8) + data[1]; - ; + return (data[0] << 8) + data[1]; + ; } /**************************************************************************************** * */ -void set_sample_rate(int rate) -{ - if (rate == 8000) - rate = SAMPLE_RATE_8000; - else if (rate == 11025) - rate = SAMPLE_RATE_11052; - else if (rate == 12000) - rate = SAMPLE_RATE_12000; - else if (rate == 16000) - rate = SAMPLE_RATE_16000; - else if (rate == 22050) - rate = SAMPLE_RATE_22050; - else if (rate == 24000) - rate = SAMPLE_RATE_24000; - else if (rate == 32000) - rate = SAMPLE_RATE_32000; - else if (rate == 44100) - rate = SAMPLE_RATE_44100; - else if (rate == 48000) - rate = SAMPLE_RATE_48000; - else if (rate == 96000) - rate = SAMPLE_RATE_96000; - else if (rate == 192000) - rate = SAMPLE_RATE_192000; - else - { - ESP_LOGW(TAG, "Unknown sample rate %hu", rate); - rate = SAMPLE_RATE_44100; - } - i2c_write_reg(I2S_SR_CTRL, rate); +void set_sample_rate(int rate) { + if (rate == 8000) + rate = SAMPLE_RATE_8000; + else if (rate == 11025) + rate = SAMPLE_RATE_11052; + else if (rate == 12000) + rate = SAMPLE_RATE_12000; + else if (rate == 16000) + rate = SAMPLE_RATE_16000; + else if (rate == 22050) + rate = SAMPLE_RATE_22050; + else if (rate == 24000) + rate = SAMPLE_RATE_24000; + else if (rate == 32000) + rate = SAMPLE_RATE_32000; + else if (rate == 44100) + rate = SAMPLE_RATE_44100; + else if (rate == 48000) + rate = SAMPLE_RATE_48000; + else if (rate == 96000) + rate = SAMPLE_RATE_96000; + else if (rate == 192000) + rate = SAMPLE_RATE_192000; + else { + ESP_LOGW(TAG, "Unknown sample rate %hu", rate); + rate = SAMPLE_RATE_44100; + } + i2c_write_reg(I2S_SR_CTRL, rate); } /**************************************************************************************** * Get normalized (0..100) speaker volume */ -static int ac101_get_spk_volume(void) -{ - return ((i2c_read_reg(SPKOUT_CTRL) & 0x1f) * 100) / 0x1f; +static int ac101_get_spk_volume(void) { + return ((i2c_read_reg(SPKOUT_CTRL) & 0x1f) * 100) / 0x1f; } /**************************************************************************************** * Set normalized (0..100) volume */ -static void ac101_set_spk_volume(uint8_t volume) -{ - uint16_t value = min(volume, 100); - value = ((int)value * 0x1f) / 100; - value |= i2c_read_reg(SPKOUT_CTRL) & ~0x1f; - i2c_write_reg(SPKOUT_CTRL, value); +static void ac101_set_spk_volume(uint8_t volume) { + uint16_t value = min(volume, 100); + value = ((int)value * 0x1f) / 100; + value |= i2c_read_reg(SPKOUT_CTRL) & ~0x1f; + i2c_write_reg(SPKOUT_CTRL, value); } /**************************************************************************************** * Get normalized (0..100) earphone volume */ -static int ac101_get_earph_volume(void) -{ - return (((i2c_read_reg(HPOUT_CTRL) >> 4) & 0x3f) * 100) / 0x3f; +static int ac101_get_earph_volume(void) { + return (((i2c_read_reg(HPOUT_CTRL) >> 4) & 0x3f) * 100) / 0x3f; } /**************************************************************************************** * Set normalized (0..100) earphone volume */ -static void ac101_set_earph_volume(uint8_t volume) -{ - uint16_t value = min(volume, 255); - value = (((int)value * 0x3f) / 255) << 4; - value |= i2c_read_reg(HPOUT_CTRL) & ~(0x3f << 4); - i2c_write_reg(HPOUT_CTRL, value); +static void ac101_set_earph_volume(uint8_t volume) { + uint16_t value = min(volume, 255); + value = (((int)value * 0x3f) / 255) << 4; + value |= i2c_read_reg(HPOUT_CTRL) & ~(0x3f << 4); + i2c_write_reg(HPOUT_CTRL, value); } /**************************************************************************************** * */ -static void ac101_set_output_mixer_gain(ac_output_mixer_gain_t gain, ac_output_mixer_source_t source) -{ - uint16_t regval, temp, clrbit; - regval = i2c_read_reg(OMIXER_BST1_CTRL); - switch (source) - { - case SRC_MIC1: - temp = (gain & 0x7) << 6; - clrbit = ~(0x7 << 6); - break; - case SRC_MIC2: - temp = (gain & 0x7) << 3; - clrbit = ~(0x7 << 3); - break; - case SRC_LINEIN: - temp = (gain & 0x7); - clrbit = ~0x7; - break; - default: - return; - } - regval &= clrbit; - regval |= temp; - i2c_write_reg(OMIXER_BST1_CTRL, regval); +static void ac101_set_output_mixer_gain(ac_output_mixer_gain_t gain, + ac_output_mixer_source_t source) { + uint16_t regval, temp, clrbit; + regval = i2c_read_reg(OMIXER_BST1_CTRL); + switch (source) { + case SRC_MIC1: + temp = (gain & 0x7) << 6; + clrbit = ~(0x7 << 6); + break; + case SRC_MIC2: + temp = (gain & 0x7) << 3; + clrbit = ~(0x7 << 3); + break; + case SRC_LINEIN: + temp = (gain & 0x7); + clrbit = ~0x7; + break; + default: + return; + } + regval &= clrbit; + regval |= temp; + i2c_write_reg(OMIXER_BST1_CTRL, regval); } /**************************************************************************************** * */ -static void ac101_start(ac_module_t mode) -{ - if (mode == AC_MODULE_LINE) - { - i2c_write_reg(0x51, 0x0408); - i2c_write_reg(0x40, 0x8000); - i2c_write_reg(0x50, 0x3bc0); - } - if (mode == AC_MODULE_ADC || mode == AC_MODULE_ADC_DAC || mode == AC_MODULE_LINE) - { - // I2S1_SDOUT_CTRL - // i2c_write_reg(PLL_CTRL2, 0x8120); - i2c_write_reg(0x04, 0x800c); - i2c_write_reg(0x05, 0x800c); - // res |= i2c_write_reg(0x06, 0x3000); - } - if (mode == AC_MODULE_DAC || mode == AC_MODULE_ADC_DAC || mode == AC_MODULE_LINE) - { - uint16_t value = i2c_read_reg(PLL_CTRL2); - value |= 0x8000; - i2c_write_reg(PLL_CTRL2, value); - } +static void ac101_start(ac_module_t mode) { + if (mode == AC_MODULE_LINE) { + i2c_write_reg(0x51, 0x0408); + i2c_write_reg(0x40, 0x8000); + i2c_write_reg(0x50, 0x3bc0); + } + if (mode == AC_MODULE_ADC || mode == AC_MODULE_ADC_DAC || + mode == AC_MODULE_LINE) { + // I2S1_SDOUT_CTRL + // i2c_write_reg(PLL_CTRL2, 0x8120); + i2c_write_reg(0x04, 0x800c); + i2c_write_reg(0x05, 0x800c); + // res |= i2c_write_reg(0x06, 0x3000); + } + if (mode == AC_MODULE_DAC || mode == AC_MODULE_ADC_DAC || + mode == AC_MODULE_LINE) { + uint16_t value = i2c_read_reg(PLL_CTRL2); + value |= 0x8000; + i2c_write_reg(PLL_CTRL2, value); + } } /**************************************************************************************** * */ -static void ac101_stop(void) -{ - uint16_t value = i2c_read_reg(PLL_CTRL2); - value &= ~0x8000; - i2c_write_reg(PLL_CTRL2, value); +static void ac101_stop(void) { + uint16_t value = i2c_read_reg(PLL_CTRL2); + value &= ~0x8000; + i2c_write_reg(PLL_CTRL2, value); } /**************************************************************************************** * */ -static void ac101_deinit(void) -{ - i2c_write_reg(CHIP_AUDIO_RS, 0x123); //soft reset +static void ac101_deinit(void) { + i2c_write_reg(CHIP_AUDIO_RS, 0x123); //soft reset } /**************************************************************************************** * Don't know when this one is supposed to be called */ -static void ac101_i2s_config_clock(ac_i2s_clock_t *cfg) -{ - uint16_t regval = 0; - regval = i2c_read_reg(I2S1LCK_CTRL); - regval &= 0xe03f; - regval |= (cfg->bclk_div << 9); - regval |= (cfg->lclk_div << 6); - i2c_write_reg(I2S1LCK_CTRL, regval); +static void ac101_i2s_config_clock(ac_i2s_clock_t* cfg) { + uint16_t regval = 0; + regval = i2c_read_reg(I2S1LCK_CTRL); + regval &= 0xe03f; + regval |= (cfg->bclk_div << 9); + regval |= (cfg->lclk_div << 6); + i2c_write_reg(I2S1LCK_CTRL, regval); } \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/esp/es8311.c b/components/spotify/cspot/bell/main/audio-sinks/esp/es8311.c index 7bf5622c..50e3415f 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/esp/es8311.c +++ b/components/spotify/cspot/bell/main/audio-sinks/esp/es8311.c @@ -22,374 +22,434 @@ * */ +#include "es8311.h" #include #include "esp_log.h" -#include "es8311.h" // #include "board.h" /* ES8311 address * 0x32:CE=1;0x30:CE=0 */ -#define ES8311_ADDR 0x32 +#define ES8311_ADDR 0x32 -#define ES7243_ADDR 0x26 +#define ES7243_ADDR 0x26 /* * to define the clock soure of MCLK */ -#define FROM_MCLK_PIN 0 -#define FROM_SCLK_PIN 1 +#define FROM_MCLK_PIN 0 +#define FROM_SCLK_PIN 1 /* * to define work mode(master or slave) */ -#define MASTER_MODE 0 -#define SLAVE_MODE 1 +#define MASTER_MODE 0 +#define SLAVE_MODE 1 /* * to define serial digital audio format */ -#define I2S_FMT 0 -#define LEFT_JUSTIFIED_FMT 1 -#define DPS_PCM_A_FMT 2 -#define DPS_PCM_B_FMT 3 +#define I2S_FMT 0 +#define LEFT_JUSTIFIED_FMT 1 +#define DPS_PCM_A_FMT 2 +#define DPS_PCM_B_FMT 3 /* * to define resolution of PCM interface */ -#define LENGTH_16BIT 0 -#define LENGTH_24BIT 1 -#define LENGTH_32BIT 2 +#define LENGTH_16BIT 0 +#define LENGTH_24BIT 1 +#define LENGTH_32BIT 2 /* * codec private data */ -struct es8311_private { - bool dmic_enable; - bool mclkinv; - bool sclkinv; - uint8_t master_slave_mode; - uint8_t pcm_format; - uint8_t pcm_resolution; - uint8_t mclk_src; +struct es8311_private { + bool dmic_enable; + bool mclkinv; + bool sclkinv; + uint8_t master_slave_mode; + uint8_t pcm_format; + uint8_t pcm_resolution; + uint8_t mclk_src; }; -static struct es8311_private *es8311_priv; +static struct es8311_private* es8311_priv; /* * Clock coefficient structer */ struct _coeff_div { - uint32_t mclk; /* mclk frequency */ - uint32_t rate; /* sample rate */ - uint8_t prediv; /* the pre divider with range from 1 to 8 */ - uint8_t premulti; /* the pre multiplier with x1, x2, x4 and x8 selection */ - uint8_t adcdiv; /* adcclk divider */ - uint8_t dacdiv; /* dacclk divider */ - uint8_t fsmode; /* double speed or single speed, =0, ss, =1, ds */ - uint8_t lrck_h; /* adclrck divider and daclrck divider */ - uint8_t lrck_l; - uint8_t bclkdiv; /* sclk divider */ - uint8_t adcosr; /* adc osr */ - uint8_t dacosr; /* dac osr */ + uint32_t mclk; /* mclk frequency */ + uint32_t rate; /* sample rate */ + uint8_t prediv; /* the pre divider with range from 1 to 8 */ + uint8_t premulti; /* the pre multiplier with x1, x2, x4 and x8 selection */ + uint8_t adcdiv; /* adcclk divider */ + uint8_t dacdiv; /* dacclk divider */ + uint8_t fsmode; /* double speed or single speed, =0, ss, =1, ds */ + uint8_t lrck_h; /* adclrck divider and daclrck divider */ + uint8_t lrck_l; + uint8_t bclkdiv; /* sclk divider */ + uint8_t adcosr; /* adc osr */ + uint8_t dacosr; /* dac osr */ }; /* codec hifi mclk clock divider coefficients */ static const struct _coeff_div coeff_div[] = { //mclk rate prediv mult adcdiv dacdiv fsmode lrch lrcl bckdiv osr /* 8k */ - {12288000, 8000 , 0x06, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {18432000, 8000 , 0x03, 0x02, 0x03, 0x03, 0x00, 0x05, 0xff, 0x18, 0x10, 0x10}, - {16384000, 8000 , 0x08, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {8192000 , 8000 , 0x04, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {6144000 , 8000 , 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {4096000 , 8000 , 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {3072000 , 8000 , 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {2048000 , 8000 , 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {1536000 , 8000 , 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {1024000 , 8000 , 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {12288000, 8000, 0x06, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {18432000, 8000, 0x03, 0x02, 0x03, 0x03, 0x00, 0x05, 0xff, 0x18, 0x10, + 0x10}, + {16384000, 8000, 0x08, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {8192000, 8000, 0x04, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {6144000, 8000, 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {4096000, 8000, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {3072000, 8000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {2048000, 8000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {1536000, 8000, 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {1024000, 8000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, /* 11.025k */ - {11289600, 11025, 0x04, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {5644800 , 11025, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {2822400 , 11025, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {1411200 , 11025, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {11289600, 11025, 0x04, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {5644800, 11025, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {2822400, 11025, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {1411200, 11025, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, /* 12k */ - {12288000, 12000, 0x04, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {6144000 , 12000, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {3072000 , 12000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {1536000 , 12000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {12288000, 12000, 0x04, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {6144000, 12000, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {3072000, 12000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {1536000, 12000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, /* 16k */ - {12288000, 16000, 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {18432000, 16000, 0x03, 0x02, 0x03, 0x03, 0x00, 0x02, 0xff, 0x0c, 0x10, 0x10}, - {16384000, 16000, 0x04, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {8192000 , 16000, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {6144000 , 16000, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {4096000 , 16000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {3072000 , 16000, 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {2048000 , 16000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {1536000 , 16000, 0x03, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {1024000 , 16000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {12288000, 16000, 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {18432000, 16000, 0x03, 0x02, 0x03, 0x03, 0x00, 0x02, 0xff, 0x0c, 0x10, + 0x10}, + {16384000, 16000, 0x04, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {8192000, 16000, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {6144000, 16000, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {4096000, 16000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {3072000, 16000, 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {2048000, 16000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {1536000, 16000, 0x03, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {1024000, 16000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, /* 22.05k */ - {11289600, 22050, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {5644800 , 22050, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {2822400 , 22050, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {1411200 , 22050, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {11289600, 22050, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {5644800, 22050, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {2822400, 22050, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {1411200, 22050, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, /* 24k */ - {12288000, 24000, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {18432000, 24000, 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {6144000 , 24000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {3072000 , 24000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {1536000 , 24000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {12288000, 24000, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {18432000, 24000, 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {6144000, 24000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {3072000, 24000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {1536000, 24000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, /* 32k */ - {12288000, 32000, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {18432000, 32000, 0x03, 0x04, 0x03, 0x03, 0x00, 0x02, 0xff, 0x0c, 0x10, 0x10}, - {16384000, 32000, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {8192000 , 32000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {6144000 , 32000, 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {4096000 , 32000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {3072000 , 32000, 0x03, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {2048000 , 32000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {1536000 , 32000, 0x03, 0x08, 0x01, 0x01, 0x01, 0x00, 0x7f, 0x02, 0x10, 0x10}, - {1024000 , 32000, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {12288000, 32000, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {18432000, 32000, 0x03, 0x04, 0x03, 0x03, 0x00, 0x02, 0xff, 0x0c, 0x10, + 0x10}, + {16384000, 32000, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {8192000, 32000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {6144000, 32000, 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {4096000, 32000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {3072000, 32000, 0x03, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {2048000, 32000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {1536000, 32000, 0x03, 0x08, 0x01, 0x01, 0x01, 0x00, 0x7f, 0x02, 0x10, + 0x10}, + {1024000, 32000, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, /* 44.1k */ - {11289600, 44100, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {5644800 , 44100, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {2822400 , 44100, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {1411200 , 44100, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {11289600, 44100, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {5644800, 44100, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {2822400, 44100, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {1411200, 44100, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, /* 48k */ - {12288000, 48000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {18432000, 48000, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {6144000 , 48000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {3072000 , 48000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {1536000 , 48000, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, + {12288000, 48000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {18432000, 48000, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {6144000, 48000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {3072000, 48000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {1536000, 48000, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, /* 64k */ - {12288000, 64000, 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {18432000, 64000, 0x03, 0x04, 0x03, 0x03, 0x01, 0x01, 0x7f, 0x06, 0x10, 0x10}, - {16384000, 64000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {8192000 , 64000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {6144000 , 64000, 0x01, 0x04, 0x03, 0x03, 0x01, 0x01, 0x7f, 0x06, 0x10, 0x10}, - {4096000 , 64000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {3072000 , 64000, 0x01, 0x08, 0x03, 0x03, 0x01, 0x01, 0x7f, 0x06, 0x10, 0x10}, - {2048000 , 64000, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {1536000 , 64000, 0x01, 0x08, 0x01, 0x01, 0x01, 0x00, 0xbf, 0x03, 0x18, 0x18}, - {1024000 , 64000, 0x01, 0x08, 0x01, 0x01, 0x01, 0x00, 0x7f, 0x02, 0x10, 0x10}, + {12288000, 64000, 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {18432000, 64000, 0x03, 0x04, 0x03, 0x03, 0x01, 0x01, 0x7f, 0x06, 0x10, + 0x10}, + {16384000, 64000, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {8192000, 64000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {6144000, 64000, 0x01, 0x04, 0x03, 0x03, 0x01, 0x01, 0x7f, 0x06, 0x10, + 0x10}, + {4096000, 64000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {3072000, 64000, 0x01, 0x08, 0x03, 0x03, 0x01, 0x01, 0x7f, 0x06, 0x10, + 0x10}, + {2048000, 64000, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {1536000, 64000, 0x01, 0x08, 0x01, 0x01, 0x01, 0x00, 0xbf, 0x03, 0x18, + 0x18}, + {1024000, 64000, 0x01, 0x08, 0x01, 0x01, 0x01, 0x00, 0x7f, 0x02, 0x10, + 0x10}, /* 88.2k */ - {11289600, 88200, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {5644800 , 88200, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {2822400 , 88200, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {1411200 , 88200, 0x01, 0x08, 0x01, 0x01, 0x01, 0x00, 0x7f, 0x02, 0x10, 0x10}, + {11289600, 88200, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {5644800, 88200, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {2822400, 88200, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {1411200, 88200, 0x01, 0x08, 0x01, 0x01, 0x01, 0x00, 0x7f, 0x02, 0x10, + 0x10}, /* 96k */ - {12288000, 96000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {18432000, 96000, 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {6144000 , 96000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {3072000 , 96000, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, 0x10}, - {1536000 , 96000, 0x01, 0x08, 0x01, 0x01, 0x01, 0x00, 0x7f, 0x02, 0x10, 0x10}, + {12288000, 96000, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {18432000, 96000, 0x03, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {6144000, 96000, 0x01, 0x04, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {3072000, 96000, 0x01, 0x08, 0x01, 0x01, 0x00, 0x00, 0xff, 0x04, 0x10, + 0x10}, + {1536000, 96000, 0x01, 0x08, 0x01, 0x01, 0x01, 0x00, 0x7f, 0x02, 0x10, + 0x10}, }; -static char *TAG = "DRV8311"; +static char* TAG = "DRV8311"; -#define ES_ASSERT(a, format, b, ...) \ - if ((a) != 0) { \ - ESP_LOGE(TAG, format, ##__VA_ARGS__); \ - return b;\ - } +#define ES_ASSERT(a, format, b, ...) \ + if ((a) != 0) { \ + ESP_LOGE(TAG, format, ##__VA_ARGS__); \ + return b; \ + } -static int Es8311WriteReg(uint8_t regAdd, uint8_t data) -{ - int res = 0; - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - res |= i2c_master_start(cmd); - res |= i2c_master_write_byte(cmd, ES8311_ADDR, 1 /*ACK_CHECK_EN*/); - res |= i2c_master_write_byte(cmd, regAdd, 1 /*ACK_CHECK_EN*/); - res |= i2c_master_write_byte(cmd, data, 1 /*ACK_CHECK_EN*/); - res |= i2c_master_stop(cmd); - res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS); - i2c_cmd_link_delete(cmd); - ES_ASSERT(res, "Es8311 Write Reg error", -1); - return res; +static int Es8311WriteReg(uint8_t regAdd, uint8_t data) { + int res = 0; + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + res |= i2c_master_start(cmd); + res |= i2c_master_write_byte(cmd, ES8311_ADDR, 1 /*ACK_CHECK_EN*/); + res |= i2c_master_write_byte(cmd, regAdd, 1 /*ACK_CHECK_EN*/); + res |= i2c_master_write_byte(cmd, data, 1 /*ACK_CHECK_EN*/); + res |= i2c_master_stop(cmd); + res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); + ES_ASSERT(res, "Es8311 Write Reg error", -1); + return res; } -int Es8311ReadReg(uint8_t regAdd) -{ - uint8_t data; - int res = 0; - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); +int Es8311ReadReg(uint8_t regAdd) { + uint8_t data; + int res = 0; + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - res |= i2c_master_start(cmd); - res |= i2c_master_write_byte(cmd, ES8311_ADDR, 1 /*ACK_CHECK_EN*/); - res |= i2c_master_write_byte(cmd, regAdd, 1 /*ACK_CHECK_EN*/); - res |= i2c_master_stop(cmd); - res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS); - i2c_cmd_link_delete(cmd); + res |= i2c_master_start(cmd); + res |= i2c_master_write_byte(cmd, ES8311_ADDR, 1 /*ACK_CHECK_EN*/); + res |= i2c_master_write_byte(cmd, regAdd, 1 /*ACK_CHECK_EN*/); + res |= i2c_master_stop(cmd); + res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); - cmd = i2c_cmd_link_create(); - res |= i2c_master_start(cmd); - res |= i2c_master_write_byte(cmd, ES8311_ADDR | 0x01, 1 /*ACK_CHECK_EN*/); - res |= i2c_master_read_byte(cmd, &data, 0x01 /*NACK_VAL*/); - res |= i2c_master_stop(cmd); - res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS); - i2c_cmd_link_delete(cmd); + cmd = i2c_cmd_link_create(); + res |= i2c_master_start(cmd); + res |= i2c_master_write_byte(cmd, ES8311_ADDR | 0x01, 1 /*ACK_CHECK_EN*/); + res |= i2c_master_read_byte(cmd, &data, 0x01 /*NACK_VAL*/); + res |= i2c_master_stop(cmd); + res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); - ES_ASSERT(res, "Es8311 Read Reg error", -1); - return (int)data; + ES_ASSERT(res, "Es8311 Read Reg error", -1); + return (int)data; } -static int Es7243WriteReg(uint8_t regAdd, uint8_t data) -{ - int res = 0; - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - res |= i2c_master_start(cmd); - res |= i2c_master_write_byte(cmd, ES7243_ADDR, 1 /*ACK_CHECK_EN*/); - res |= i2c_master_write_byte(cmd, regAdd, 1 /*ACK_CHECK_EN*/); - res |= i2c_master_write_byte(cmd, data, 1 /*ACK_CHECK_EN*/); - res |= i2c_master_stop(cmd); - res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS); - i2c_cmd_link_delete(cmd); - ES_ASSERT(res, "Es7243 Write Reg error", -1); - return res; +static int Es7243WriteReg(uint8_t regAdd, uint8_t data) { + int res = 0; + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + res |= i2c_master_start(cmd); + res |= i2c_master_write_byte(cmd, ES7243_ADDR, 1 /*ACK_CHECK_EN*/); + res |= i2c_master_write_byte(cmd, regAdd, 1 /*ACK_CHECK_EN*/); + res |= i2c_master_write_byte(cmd, data, 1 /*ACK_CHECK_EN*/); + res |= i2c_master_stop(cmd); + res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); + ES_ASSERT(res, "Es7243 Write Reg error", -1); + return res; } +int Es7243ReadReg(uint8_t regAdd) { + uint8_t data; + int res = 0; + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); -int Es7243ReadReg(uint8_t regAdd) -{ - uint8_t data; - int res = 0; - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + res |= i2c_master_start(cmd); + res |= i2c_master_write_byte(cmd, ES7243_ADDR, 1 /*ACK_CHECK_EN*/); + res |= i2c_master_write_byte(cmd, regAdd, 1 /*ACK_CHECK_EN*/); + res |= i2c_master_stop(cmd); + res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); - res |= i2c_master_start(cmd); - res |= i2c_master_write_byte(cmd, ES7243_ADDR, 1 /*ACK_CHECK_EN*/); - res |= i2c_master_write_byte(cmd, regAdd, 1 /*ACK_CHECK_EN*/); - res |= i2c_master_stop(cmd); - res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS); - i2c_cmd_link_delete(cmd); + cmd = i2c_cmd_link_create(); + res |= i2c_master_start(cmd); + res |= i2c_master_write_byte(cmd, ES7243_ADDR | 0x01, 1 /*ACK_CHECK_EN*/); + res |= i2c_master_read_byte(cmd, &data, 0x01 /*NACK_VAL*/); + res |= i2c_master_stop(cmd); + res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); - cmd = i2c_cmd_link_create(); - res |= i2c_master_start(cmd); - res |= i2c_master_write_byte(cmd, ES7243_ADDR | 0x01, 1 /*ACK_CHECK_EN*/); - res |= i2c_master_read_byte(cmd, &data, 0x01 /*NACK_VAL*/); - res |= i2c_master_stop(cmd); - res |= i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS); - i2c_cmd_link_delete(cmd); - - ES_ASSERT(res, "Es7243 Read Reg error", -1); - return (int)data; + ES_ASSERT(res, "Es7243 Read Reg error", -1); + return (int)data; } -esp_err_t Es7243Init(void) -{ - esp_err_t ret = ESP_OK; - ret |= Es7243WriteReg(0x00, 0x01); - ret |= Es7243WriteReg(0x06, 0x00); - ret |= Es7243WriteReg(0x05, 0x1B); - ret |= Es7243WriteReg(0x01, 0x0C); - ret |= Es7243WriteReg(0x08, 0x43); - ret |= Es7243WriteReg(0x05, 0x13); - if (ret) { - ESP_LOGE(TAG, "Es7243 initialize failed!"); - return ESP_FAIL; - } - return ret; +esp_err_t Es7243Init(void) { + esp_err_t ret = ESP_OK; + ret |= Es7243WriteReg(0x00, 0x01); + ret |= Es7243WriteReg(0x06, 0x00); + ret |= Es7243WriteReg(0x05, 0x1B); + ret |= Es7243WriteReg(0x01, 0x0C); + ret |= Es7243WriteReg(0x08, 0x43); + ret |= Es7243WriteReg(0x05, 0x13); + if (ret) { + ESP_LOGE(TAG, "Es7243 initialize failed!"); + return ESP_FAIL; + } + return ret; } -static int I2cInit(i2c_config_t *conf, int i2cMasterPort) -{ - int res; - res = i2c_param_config(i2cMasterPort, conf); - res |= i2c_driver_install(i2cMasterPort, conf->mode, 0, 0, 0); - ES_ASSERT(res, "I2cInit error", -1); - return res; +static int I2cInit(i2c_config_t* conf, int i2cMasterPort) { + int res; + res = i2c_param_config(i2cMasterPort, conf); + res |= i2c_driver_install(i2cMasterPort, conf->mode, 0, 0, 0); + ES_ASSERT(res, "I2cInit error", -1); + return res; } /* * look for the coefficient in coeff_div[] table */ -static int get_coeff(uint32_t mclk, uint32_t rate) -{ - for (int i = 0; i < (sizeof(coeff_div) / sizeof(coeff_div[0])); i++) { - if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) - return i; - } - return -1; +static int get_coeff(uint32_t mclk, uint32_t rate) { + for (int i = 0; i < (sizeof(coeff_div) / sizeof(coeff_div[0])); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + return -1; } /* * set es8311 clock parameter and PCM/I2S interface */ -static void es8311_pcm_hw_params(uint32_t mclk, uint32_t lrck) -{ - int coeff; - uint8_t regv, datmp; - ESP_LOGI(TAG, "Enter into es8311_pcm_hw_params()\n"); - coeff = get_coeff(mclk, lrck); - if (coeff < 0) { - ESP_LOGE(TAG, "Unable to configure sample rate %dHz with %dHz MCLK\n", lrck, mclk); - return; - } +static void es8311_pcm_hw_params(uint32_t mclk, uint32_t lrck) { + int coeff; + uint8_t regv, datmp; + ESP_LOGI(TAG, "Enter into es8311_pcm_hw_params()\n"); + coeff = get_coeff(mclk, lrck); + if (coeff < 0) { + ESP_LOGE(TAG, "Unable to configure sample rate %dHz with %dHz MCLK\n", lrck, + mclk); + return; + } - /* + /* * set clock parammeters */ - if (coeff >= 0) { - regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG02) & 0x07; - regv |= (coeff_div[coeff].prediv - 1) << 5; + if (coeff >= 0) { + regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG02) & 0x07; + regv |= (coeff_div[coeff].prediv - 1) << 5; + datmp = 0; + switch (coeff_div[coeff].premulti) { + case 1: datmp = 0; - switch (coeff_div[coeff].premulti) { - case 1: - datmp = 0; - break; - case 2: - datmp = 1; - break; - case 4: - datmp = 2; - break; - case 8: - datmp = 3; - break; - default: - break; - } -#if CONFIG_ESP32_KORVO_V1_1_BOARD + break; + case 2: + datmp = 1; + break; + case 4: + datmp = 2; + break; + case 8: datmp = 3; -#endif - regv |= (datmp) << 3; - Es8311WriteReg(ES8311_CLK_MANAGER_REG02, regv); - - regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG05) & 0x00; - regv |= (coeff_div[coeff].adcdiv - 1) << 4; - regv |= (coeff_div[coeff].dacdiv - 1) << 0; - Es8311WriteReg(ES8311_CLK_MANAGER_REG05, regv); - - regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG03) & 0x80; - regv |= coeff_div[coeff].fsmode << 6; - regv |= coeff_div[coeff].adcosr << 0; - Es8311WriteReg(ES8311_CLK_MANAGER_REG03, regv); - - regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG04) & 0x80; - regv |= coeff_div[coeff].dacosr << 0; - Es8311WriteReg(ES8311_CLK_MANAGER_REG04, regv); - - regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG07) & 0xC0; - regv |= coeff_div[coeff].lrck_h << 0; - Es8311WriteReg(ES8311_CLK_MANAGER_REG07, regv); - - regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG08) & 0x00; - regv |= coeff_div[coeff].lrck_l << 0; - Es8311WriteReg(ES8311_CLK_MANAGER_REG08, regv); - - regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG06) & 0xE0; - if (coeff_div[coeff].bclkdiv < 19) { - regv |= (coeff_div[coeff].bclkdiv - 1) << 0; - } else { - regv |= (coeff_div[coeff].bclkdiv) << 0; - } - Es8311WriteReg(ES8311_CLK_MANAGER_REG06, regv); + break; + default: + break; } +#if CONFIG_ESP32_KORVO_V1_1_BOARD + datmp = 3; +#endif + regv |= (datmp) << 3; + Es8311WriteReg(ES8311_CLK_MANAGER_REG02, regv); + + regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG05) & 0x00; + regv |= (coeff_div[coeff].adcdiv - 1) << 4; + regv |= (coeff_div[coeff].dacdiv - 1) << 0; + Es8311WriteReg(ES8311_CLK_MANAGER_REG05, regv); + + regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG03) & 0x80; + regv |= coeff_div[coeff].fsmode << 6; + regv |= coeff_div[coeff].adcosr << 0; + Es8311WriteReg(ES8311_CLK_MANAGER_REG03, regv); + + regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG04) & 0x80; + regv |= coeff_div[coeff].dacosr << 0; + Es8311WriteReg(ES8311_CLK_MANAGER_REG04, regv); + + regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG07) & 0xC0; + regv |= coeff_div[coeff].lrck_h << 0; + Es8311WriteReg(ES8311_CLK_MANAGER_REG07, regv); + + regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG08) & 0x00; + regv |= coeff_div[coeff].lrck_l << 0; + Es8311WriteReg(ES8311_CLK_MANAGER_REG08, regv); + + regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG06) & 0xE0; + if (coeff_div[coeff].bclkdiv < 19) { + regv |= (coeff_div[coeff].bclkdiv - 1) << 0; + } else { + regv |= (coeff_div[coeff].bclkdiv) << 0; + } + Es8311WriteReg(ES8311_CLK_MANAGER_REG06, regv); + } } /* * set data and clock in tri-state mode @@ -412,20 +472,19 @@ static void es8311_pcm_hw_params(uint32_t mclk, uint32_t lrck) * if mute = 0, dac un-mute * if mute = 1, dac mute */ -static void es8311_mute(int mute) -{ - uint8_t regv; - ESP_LOGI(TAG, "Enter into es8311_mute(), mute = %d\n", mute); - regv = Es8311ReadReg(ES8311_DAC_REG31) & 0x9f; - if (mute) { - Es8311WriteReg(ES8311_SYSTEM_REG12, 0x02); - Es8311WriteReg(ES8311_DAC_REG31, regv | 0x60); - Es8311WriteReg(ES8311_DAC_REG32, 0x00); - Es8311WriteReg(ES8311_DAC_REG37, 0x08); - } else { - Es8311WriteReg(ES8311_DAC_REG31, regv); - Es8311WriteReg(ES8311_SYSTEM_REG12, 0x00); - } +static void es8311_mute(int mute) { + uint8_t regv; + ESP_LOGI(TAG, "Enter into es8311_mute(), mute = %d\n", mute); + regv = Es8311ReadReg(ES8311_DAC_REG31) & 0x9f; + if (mute) { + Es8311WriteReg(ES8311_SYSTEM_REG12, 0x02); + Es8311WriteReg(ES8311_DAC_REG31, regv | 0x60); + Es8311WriteReg(ES8311_DAC_REG32, 0x00); + Es8311WriteReg(ES8311_DAC_REG37, 0x08); + } else { + Es8311WriteReg(ES8311_DAC_REG31, regv); + Es8311WriteReg(ES8311_SYSTEM_REG12, 0x00); + } } /* * set es8311 into suspend mode @@ -450,135 +509,133 @@ static void es8311_mute(int mute) /* * initialize es8311 codec */ -static void es8311_init(uint32_t mclk_freq, uint32_t lrck_freq) -{ - int regv; - Es8311WriteReg(ES8311_GP_REG45, 0x00); - Es8311WriteReg(ES8311_CLK_MANAGER_REG01, 0x30); - Es8311WriteReg(ES8311_CLK_MANAGER_REG02, 0x00); - Es8311WriteReg(ES8311_CLK_MANAGER_REG03, 0x10); - Es8311WriteReg(ES8311_ADC_REG16, 0x24); - Es8311WriteReg(ES8311_CLK_MANAGER_REG04, 0x10); - Es8311WriteReg(ES8311_CLK_MANAGER_REG05, 0x00); - Es8311WriteReg(ES8311_SYSTEM_REG0B, 0x00); - Es8311WriteReg(ES8311_SYSTEM_REG0C, 0x00); - Es8311WriteReg(ES8311_SYSTEM_REG10, 0x1F); - Es8311WriteReg(ES8311_SYSTEM_REG11, 0x7F); - Es8311WriteReg(ES8311_RESET_REG00, 0x80); - /* +static void es8311_init(uint32_t mclk_freq, uint32_t lrck_freq) { + int regv; + Es8311WriteReg(ES8311_GP_REG45, 0x00); + Es8311WriteReg(ES8311_CLK_MANAGER_REG01, 0x30); + Es8311WriteReg(ES8311_CLK_MANAGER_REG02, 0x00); + Es8311WriteReg(ES8311_CLK_MANAGER_REG03, 0x10); + Es8311WriteReg(ES8311_ADC_REG16, 0x24); + Es8311WriteReg(ES8311_CLK_MANAGER_REG04, 0x10); + Es8311WriteReg(ES8311_CLK_MANAGER_REG05, 0x00); + Es8311WriteReg(ES8311_SYSTEM_REG0B, 0x00); + Es8311WriteReg(ES8311_SYSTEM_REG0C, 0x00); + Es8311WriteReg(ES8311_SYSTEM_REG10, 0x1F); + Es8311WriteReg(ES8311_SYSTEM_REG11, 0x7F); + Es8311WriteReg(ES8311_RESET_REG00, 0x80); + /* * Set Codec into Master or Slave mode */ - regv = Es8311ReadReg(ES8311_RESET_REG00); - /* set master/slave audio interface */ - switch (es8311_priv->master_slave_mode) { - case MASTER_MODE: /* MASTER MODE */ - ESP_LOGI(TAG, "ES8311 in Master mode\n"); - regv |= 0x40; - break; - case SLAVE_MODE: /* SLAVE MODE */ - ESP_LOGI(TAG, "ES8311 in Slave mode\n"); - regv &= 0xBF; - break; - default: - regv &= 0xBF; - } - Es8311WriteReg(ES8311_RESET_REG00, regv); - Es8311WriteReg(ES8311_SYSTEM_REG0D, 0x01); - Es8311WriteReg(ES8311_CLK_MANAGER_REG01, 0x3F); - /* + regv = Es8311ReadReg(ES8311_RESET_REG00); + /* set master/slave audio interface */ + switch (es8311_priv->master_slave_mode) { + case MASTER_MODE: /* MASTER MODE */ + ESP_LOGI(TAG, "ES8311 in Master mode\n"); + regv |= 0x40; + break; + case SLAVE_MODE: /* SLAVE MODE */ + ESP_LOGI(TAG, "ES8311 in Slave mode\n"); + regv &= 0xBF; + break; + default: + regv &= 0xBF; + } + Es8311WriteReg(ES8311_RESET_REG00, regv); + Es8311WriteReg(ES8311_SYSTEM_REG0D, 0x01); + Es8311WriteReg(ES8311_CLK_MANAGER_REG01, 0x3F); + /* * select clock source for internal mclk */ - switch (es8311_priv->mclk_src) { - case FROM_MCLK_PIN: - regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG01); - regv &= 0x7F; - Es8311WriteReg(ES8311_CLK_MANAGER_REG01, regv); - break; - case FROM_SCLK_PIN: - regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG01); - regv |= 0x80; - Es8311WriteReg(ES8311_CLK_MANAGER_REG01, regv); - break; - default: - regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG01); - regv &= 0x7F; - Es8311WriteReg(ES8311_CLK_MANAGER_REG01, regv); - break; - } - es8311_pcm_hw_params(lrck_freq * 256, lrck_freq); + switch (es8311_priv->mclk_src) { + case FROM_MCLK_PIN: + regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG01); + regv &= 0x7F; + Es8311WriteReg(ES8311_CLK_MANAGER_REG01, regv); + break; + case FROM_SCLK_PIN: + regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG01); + regv |= 0x80; + Es8311WriteReg(ES8311_CLK_MANAGER_REG01, regv); + break; + default: + regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG01); + regv &= 0x7F; + Es8311WriteReg(ES8311_CLK_MANAGER_REG01, regv); + break; + } + es8311_pcm_hw_params(lrck_freq * 256, lrck_freq); - /* + /* * mclk inverted or not */ - if (es8311_priv->mclkinv == true) { - regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG01); - regv |= 0x40; - Es8311WriteReg(ES8311_CLK_MANAGER_REG01, regv); - } else { - regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG01); - regv &= ~(0x40); - Es8311WriteReg(ES8311_CLK_MANAGER_REG01, regv); - } - /* + if (es8311_priv->mclkinv == true) { + regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG01); + regv |= 0x40; + Es8311WriteReg(ES8311_CLK_MANAGER_REG01, regv); + } else { + regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG01); + regv &= ~(0x40); + Es8311WriteReg(ES8311_CLK_MANAGER_REG01, regv); + } + /* * sclk inverted or not */ - if (es8311_priv->sclkinv == true) { - regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG06); - regv |= 0x20; - Es8311WriteReg(ES8311_CLK_MANAGER_REG06, regv); - } else { - regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG06); - regv &= ~(0x20); - Es8311WriteReg(ES8311_CLK_MANAGER_REG06, regv); - } - Es8311WriteReg(ES8311_SYSTEM_REG14, 0x1A); - /* + if (es8311_priv->sclkinv == true) { + regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG06); + regv |= 0x20; + Es8311WriteReg(ES8311_CLK_MANAGER_REG06, regv); + } else { + regv = Es8311ReadReg(ES8311_CLK_MANAGER_REG06); + regv &= ~(0x20); + Es8311WriteReg(ES8311_CLK_MANAGER_REG06, regv); + } + Es8311WriteReg(ES8311_SYSTEM_REG14, 0x1A); + /* * pdm dmic enable or disable */ - if (es8311_priv->dmic_enable == true) { - regv = Es8311ReadReg(ES8311_SYSTEM_REG14); - regv |= 0x40; - Es8311WriteReg(ES8311_SYSTEM_REG14, regv); - } else { - regv = Es8311ReadReg(ES8311_SYSTEM_REG14); - regv &= ~(0x40); - Es8311WriteReg(ES8311_SYSTEM_REG14, regv); - } + if (es8311_priv->dmic_enable == true) { + regv = Es8311ReadReg(ES8311_SYSTEM_REG14); + regv |= 0x40; + Es8311WriteReg(ES8311_SYSTEM_REG14, regv); + } else { + regv = Es8311ReadReg(ES8311_SYSTEM_REG14); + regv &= ~(0x40); + Es8311WriteReg(ES8311_SYSTEM_REG14, regv); + } - Es8311WriteReg(ES8311_SYSTEM_REG13, 0x10); - Es8311WriteReg(ES8311_SYSTEM_REG0E, 0x02); - Es8311WriteReg(ES8311_ADC_REG15, 0x40); - Es8311WriteReg(ES8311_ADC_REG1B, 0x0A); - Es8311WriteReg(ES8311_ADC_REG1C, 0x6A); - Es8311WriteReg(ES8311_DAC_REG37, 0x48); - Es8311WriteReg(ES8311_GPIO_REG44, 0x08); - Es8311WriteReg(ES8311_DAC_REG32, 0xBF); + Es8311WriteReg(ES8311_SYSTEM_REG13, 0x10); + Es8311WriteReg(ES8311_SYSTEM_REG0E, 0x02); + Es8311WriteReg(ES8311_ADC_REG15, 0x40); + Es8311WriteReg(ES8311_ADC_REG1B, 0x0A); + Es8311WriteReg(ES8311_ADC_REG1C, 0x6A); + Es8311WriteReg(ES8311_DAC_REG37, 0x48); + Es8311WriteReg(ES8311_GPIO_REG44, 0x08); + Es8311WriteReg(ES8311_DAC_REG32, 0xBF); #ifdef CONFIG_USE_ES7243 - Es7243Init(); + Es7243Init(); #endif } /* * set codec private data and initialize codec */ -void es8311_Codec_Startup(uint32_t mclk_freq, uint32_t lrck_freq) -{ - ESP_LOGI(TAG, "Enter into es8311_Codec_Startup()\n"); - es8311_priv->dmic_enable = false; - es8311_priv->mclkinv = false; - es8311_priv->sclkinv = false; - es8311_priv->pcm_format = I2S_FMT; - es8311_priv->pcm_resolution = LENGTH_16BIT; - es8311_priv->master_slave_mode = SLAVE_MODE; +void es8311_Codec_Startup(uint32_t mclk_freq, uint32_t lrck_freq) { + ESP_LOGI(TAG, "Enter into es8311_Codec_Startup()\n"); + es8311_priv->dmic_enable = false; + es8311_priv->mclkinv = false; + es8311_priv->sclkinv = false; + es8311_priv->pcm_format = I2S_FMT; + es8311_priv->pcm_resolution = LENGTH_16BIT; + es8311_priv->master_slave_mode = SLAVE_MODE; #ifdef CONFIG_ESP32_KORVO_V1_1_BOARD - es8311_priv->mclk_src = FROM_SCLK_PIN; + es8311_priv->mclk_src = FROM_SCLK_PIN; #else - es8311_priv->mclk_src = FROM_MCLK_PIN; + es8311_priv->mclk_src = FROM_MCLK_PIN; #endif - es8311_init(mclk_freq, lrck_freq); + es8311_init(mclk_freq, lrck_freq); - ESP_LOGI(TAG, "Exit es8311_Codec_Startup()\n"); + ESP_LOGI(TAG, "Exit es8311_Codec_Startup()\n"); } // static int Es8311SetAdcDacVolume(int mode, int volume, int dot) @@ -596,201 +653,186 @@ void es8311_Codec_Startup(uint32_t mclk_freq, uint32_t lrck_freq) // return res; // } -esp_err_t Es8311GetRef(bool flag) -{ - esp_err_t ret = ESP_OK; - uint8_t regv = 0; - if (flag) { - regv = Es8311ReadReg(ES8311_GPIO_REG44); - regv |= 0x50; - ret |= Es8311WriteReg(ES8311_GPIO_REG44, regv); - } else { - ret |= Es8311WriteReg(ES8311_GPIO_REG44, 0x08); - } - return ret; +esp_err_t Es8311GetRef(bool flag) { + esp_err_t ret = ESP_OK; + uint8_t regv = 0; + if (flag) { + regv = Es8311ReadReg(ES8311_GPIO_REG44); + regv |= 0x50; + ret |= Es8311WriteReg(ES8311_GPIO_REG44, regv); + } else { + ret |= Es8311WriteReg(ES8311_GPIO_REG44, 0x08); + } + return ret; } -int Es8311Init(Es8311Config *cfg) -{ - es8311_priv = calloc(1, sizeof(struct es8311_private)); - I2cInit(&cfg->i2c_cfg, cfg->i2c_port_num); // ESP32 in master mode - es8311_Codec_Startup(11289600, 44100); - return 0; +int Es8311Init(Es8311Config* cfg) { + es8311_priv = calloc(1, sizeof(struct es8311_private)); + I2cInit(&cfg->i2c_cfg, cfg->i2c_port_num); // ESP32 in master mode + es8311_Codec_Startup(11289600, 44100); + return 0; } -void Es8311Uninit() -{ - Es8311WriteReg(ES8311_RESET_REG00, 0x3f); - free(es8311_priv); - es8311_priv = NULL; +void Es8311Uninit() { + Es8311WriteReg(ES8311_RESET_REG00, 0x3f); + free(es8311_priv); + es8311_priv = NULL; } -int Es8311ConfigFmt(ESCodecModule mode, ESCodecI2SFmt fmt) -{ - int res = 0; - uint8_t regAdc = 0, regDac = 0; - if (mode == ES_MODULE_ADC || mode == ES_MODULE_ADC_DAC) { - res |= Es8311WriteReg(ES8311_ADC_REG17, 0xBF); - } - if (mode == ES_MODULE_DAC || mode == ES_MODULE_ADC_DAC) { - res |= Es8311WriteReg(ES8311_SYSTEM_REG12, 0x00); - } - regAdc = Es8311ReadReg(ES8311_SDPIN_REG09); - regDac = Es8311ReadReg(ES8311_SDPOUT_REG0A); - switch (fmt) { - case ES_I2S_NORMAL: - ESP_LOGI(TAG, "ES8311 in I2S Format"); - regAdc &= ~0x03; - regDac &= ~0x03; - break; - case ES_I2S_LEFT: - case ES_I2S_RIGHT: - ESP_LOGI(TAG, "ES8311 in LJ Format"); - regAdc &= ~0x03; - regAdc |= 0x01; - regDac &= ~0x03; - regDac |= 0x01; - break; - case ES_I2S_DSP: - ESP_LOGI(TAG, "ES8311 in DSP Format"); - regAdc |= 0x03; - regDac |= 0x03; - break; - default: - ESP_LOGE(TAG, "Not Supported Format"); - break; - } - res |= Es8311WriteReg(ES8311_SDPIN_REG09, regAdc); - res |= Es8311WriteReg(ES8311_SDPOUT_REG0A, regDac); - return res; +int Es8311ConfigFmt(ESCodecModule mode, ESCodecI2SFmt fmt) { + int res = 0; + uint8_t regAdc = 0, regDac = 0; + if (mode == ES_MODULE_ADC || mode == ES_MODULE_ADC_DAC) { + res |= Es8311WriteReg(ES8311_ADC_REG17, 0xBF); + } + if (mode == ES_MODULE_DAC || mode == ES_MODULE_ADC_DAC) { + res |= Es8311WriteReg(ES8311_SYSTEM_REG12, 0x00); + } + regAdc = Es8311ReadReg(ES8311_SDPIN_REG09); + regDac = Es8311ReadReg(ES8311_SDPOUT_REG0A); + switch (fmt) { + case ES_I2S_NORMAL: + ESP_LOGI(TAG, "ES8311 in I2S Format"); + regAdc &= ~0x03; + regDac &= ~0x03; + break; + case ES_I2S_LEFT: + case ES_I2S_RIGHT: + ESP_LOGI(TAG, "ES8311 in LJ Format"); + regAdc &= ~0x03; + regAdc |= 0x01; + regDac &= ~0x03; + regDac |= 0x01; + break; + case ES_I2S_DSP: + ESP_LOGI(TAG, "ES8311 in DSP Format"); + regAdc |= 0x03; + regDac |= 0x03; + break; + default: + ESP_LOGE(TAG, "Not Supported Format"); + break; + } + res |= Es8311WriteReg(ES8311_SDPIN_REG09, regAdc); + res |= Es8311WriteReg(ES8311_SDPOUT_REG0A, regDac); + return res; } -int Es8311I2sConfigClock(ESCodecI2sClock cfg) -{ - int res = 0; - return res; +int Es8311I2sConfigClock(ESCodecI2sClock cfg) { + int res = 0; + return res; } -int Es8311SetBitsPerSample(ESCodecModule mode, BitsLength bitPerSample) -{ - int res = 0; - uint8_t reg = 0; - int bits = (int)bitPerSample; +int Es8311SetBitsPerSample(ESCodecModule mode, BitsLength bitPerSample) { + int res = 0; + uint8_t reg = 0; + int bits = (int)bitPerSample; - if (mode == ES_MODULE_ADC || mode == ES_MODULE_ADC_DAC) { - reg = Es8311ReadReg(ES8311_SDPIN_REG09); - reg = reg & 0xe3; - res |= Es8311WriteReg(ES8311_SDPIN_REG09, reg | (bits << 2)); - } - if (mode == ES_MODULE_DAC || mode == ES_MODULE_ADC_DAC) { - reg = Es8311ReadReg(ES8311_SDPOUT_REG0A); - reg = reg & 0xe3; - res |= Es8311WriteReg(ES8311_SDPOUT_REG0A, reg | (bits << 2)); - } - return res; + if (mode == ES_MODULE_ADC || mode == ES_MODULE_ADC_DAC) { + reg = Es8311ReadReg(ES8311_SDPIN_REG09); + reg = reg & 0xe3; + res |= Es8311WriteReg(ES8311_SDPIN_REG09, reg | (bits << 2)); + } + if (mode == ES_MODULE_DAC || mode == ES_MODULE_ADC_DAC) { + reg = Es8311ReadReg(ES8311_SDPOUT_REG0A); + reg = reg & 0xe3; + res |= Es8311WriteReg(ES8311_SDPOUT_REG0A, reg | (bits << 2)); + } + return res; } -int Es8311Start(ESCodecModule mode) -{ - int res = 0; - if (mode == ES_MODULE_ADC || mode == ES_MODULE_ADC_DAC) { - res |= Es8311WriteReg(ES8311_ADC_REG17, 0xBF); - } - if (mode == ES_MODULE_DAC || mode == ES_MODULE_ADC_DAC) { - res |= Es8311WriteReg(ES8311_SYSTEM_REG12, Es8311ReadReg(ES8311_SYSTEM_REG12) & 0xfd); - } - return res; +int Es8311Start(ESCodecModule mode) { + int res = 0; + if (mode == ES_MODULE_ADC || mode == ES_MODULE_ADC_DAC) { + res |= Es8311WriteReg(ES8311_ADC_REG17, 0xBF); + } + if (mode == ES_MODULE_DAC || mode == ES_MODULE_ADC_DAC) { + res |= Es8311WriteReg(ES8311_SYSTEM_REG12, + Es8311ReadReg(ES8311_SYSTEM_REG12) & 0xfd); + } + return res; } -int Es8311Stop(ESCodecModule mode) -{ - int res = 0; - if (mode == ES_MODULE_ADC || mode == ES_MODULE_ADC_DAC) { - res |= Es8311WriteReg(ES8311_ADC_REG17, 0x00); - } - if (mode == ES_MODULE_DAC || mode == ES_MODULE_ADC_DAC) { - res |= Es8311WriteReg(ES8311_SYSTEM_REG12, Es8311ReadReg(ES8311_SYSTEM_REG12) | 0x02); - } - return res; +int Es8311Stop(ESCodecModule mode) { + int res = 0; + if (mode == ES_MODULE_ADC || mode == ES_MODULE_ADC_DAC) { + res |= Es8311WriteReg(ES8311_ADC_REG17, 0x00); + } + if (mode == ES_MODULE_DAC || mode == ES_MODULE_ADC_DAC) { + res |= Es8311WriteReg(ES8311_SYSTEM_REG12, + Es8311ReadReg(ES8311_SYSTEM_REG12) | 0x02); + } + return res; } -int Es8311SetVoiceVolume(int volume) -{ - int res = 0; +int Es8311SetVoiceVolume(int volume) { + int res = 0; - if (volume == 0) { - volume = 1; - } + if (volume == 0) { + volume = 1; + } - Es8311WriteReg(ES8311_DAC_REG32, volume); - return res; + Es8311WriteReg(ES8311_DAC_REG32, volume); + return res; } -int Es8311GetVoiceVolume(int *volume) -{ - int res = ESP_OK; - int regv = Es8311ReadReg(ES8311_DAC_REG32); - if (regv == ESP_FAIL) { - *volume = 0; - res = ESP_FAIL; - } else { - *volume = regv * 100 / 256; - } - ESP_LOGI(TAG, "GET: res:%d, volume:%d\n", regv, *volume); - return res; +int Es8311GetVoiceVolume(int* volume) { + int res = ESP_OK; + int regv = Es8311ReadReg(ES8311_DAC_REG32); + if (regv == ESP_FAIL) { + *volume = 0; + res = ESP_FAIL; + } else { + *volume = regv * 100 / 256; + } + ESP_LOGI(TAG, "GET: res:%d, volume:%d\n", regv, *volume); + return res; } -int Es8311SetVoiceMute(int enable) -{ - int res = 0; - ESP_LOGI(TAG, "Es8311SetVoiceMute volume:%d\n", enable); - es8311_mute(enable); - return res; +int Es8311SetVoiceMute(int enable) { + int res = 0; + ESP_LOGI(TAG, "Es8311SetVoiceMute volume:%d\n", enable); + es8311_mute(enable); + return res; } -int Es8311GetVoiceMute(int *mute) -{ - int res = -1; - uint8_t reg = 0; - res = Es8311ReadReg(ES8311_DAC_REG31); - if (res != ESP_FAIL) { - reg = (res & 0x20) >> 5; - } - *mute = reg; - return res; +int Es8311GetVoiceMute(int* mute) { + int res = -1; + uint8_t reg = 0; + res = Es8311ReadReg(ES8311_DAC_REG31); + if (res != ESP_FAIL) { + reg = (res & 0x20) >> 5; + } + *mute = reg; + return res; } -int Es8311SetMicGain(MicGain gain) -{ - int res = 0; - uint8_t gain_n = Es8311ReadReg(ES8311_ADC_REG16) & 0x07; - gain_n |= gain / 6; - res = Es8311WriteReg(ES8311_ADC_REG16, gain_n); // MIC gain scale - return res; +int Es8311SetMicGain(MicGain gain) { + int res = 0; + uint8_t gain_n = Es8311ReadReg(ES8311_ADC_REG16) & 0x07; + gain_n |= gain / 6; + res = Es8311WriteReg(ES8311_ADC_REG16, gain_n); // MIC gain scale + return res; } -int Es8311ConfigAdcInput(AdcInput input) -{ - int res = 0; - return res; +int Es8311ConfigAdcInput(AdcInput input) { + int res = 0; + return res; } -int Es8311SetAdcVolume(uint8_t adc_vol) -{ - int res = 0; - res = Es8311WriteReg(ES8311_ADC_REG17, adc_vol); // MIC ADC Volume - return res; +int Es8311SetAdcVolume(uint8_t adc_vol) { + int res = 0; + res = Es8311WriteReg(ES8311_ADC_REG17, adc_vol); // MIC ADC Volume + return res; } -int ES8311WriteReg(uint8_t regAdd, uint8_t data) -{ - return Es8311WriteReg(regAdd, data); +int ES8311WriteReg(uint8_t regAdd, uint8_t data) { + return Es8311WriteReg(regAdd, data); } -void Es8311ReadAll() -{ - for (int i = 0; i < 0x4A; i++) { - uint8_t reg = Es8311ReadReg(i); - // ets_printf("REG:%02x, %02x\n", reg, i); - } +void Es8311ReadAll() { + for (int i = 0; i < 0x4A; i++) { + uint8_t reg = Es8311ReadReg(i); + // ets_printf("REG:%02x, %02x\n", reg, i); + } } \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/AudioSink.h b/components/spotify/cspot/bell/main/audio-sinks/include/AudioSink.h index 96432513..65b4fd9c 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/AudioSink.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/AudioSink.h @@ -5,21 +5,23 @@ #include #include -class AudioSink -{ - public: - AudioSink() {} - virtual ~AudioSink() {} - virtual void feedPCMFrames(const uint8_t *buffer, size_t bytes) = 0; - virtual void volumeChanged(uint16_t volume) {} - // Return false if the sink doesn't support reconfiguration. - virtual bool setParams(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) { return false; } - // Deprecated. Implement/use setParams() instead. - virtual inline bool setRate(uint16_t sampleRate) { - return setParams(sampleRate, 2, 16); - } - bool softwareVolumeControl = true; - bool usign = false; +class AudioSink { + public: + AudioSink() {} + virtual ~AudioSink() {} + virtual void feedPCMFrames(const uint8_t* buffer, size_t bytes) = 0; + virtual void volumeChanged(uint16_t volume) {} + // Return false if the sink doesn't support reconfiguration. + virtual bool setParams(uint32_t sampleRate, uint8_t channelCount, + uint8_t bitDepth) { + return false; + } + // Deprecated. Implement/use setParams() instead. + virtual inline bool setRate(uint16_t sampleRate) { + return setParams(sampleRate, 2, 16); + } + bool softwareVolumeControl = true; + bool usign = false; }; #endif \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/esp/AC101AudioSink.h b/components/spotify/cspot/bell/main/audio-sinks/include/esp/AC101AudioSink.h index 98c1c0ff..695bcf8a 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/esp/AC101AudioSink.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/esp/AC101AudioSink.h @@ -1,26 +1,26 @@ #ifndef AC101AUDIOSINK_H #define AC101AUDIOSINK_H -#include -#include -#include "BufferedAudioSink.h" #include #include -#include #include -#include "esp_err.h" -#include "esp_log.h" +#include +#include +#include +#include "BufferedAudioSink.h" #include "ac101.h" #include "adac.h" +#include "esp_err.h" +#include "esp_log.h" -class AC101AudioSink : public BufferedAudioSink -{ -public: - AC101AudioSink(); - ~AC101AudioSink(); - void volumeChanged(uint16_t volume); -private: - adac_s *dac; +class AC101AudioSink : public BufferedAudioSink { + public: + AC101AudioSink(); + ~AC101AudioSink(); + void volumeChanged(uint16_t volume); + + private: + adac_s* dac; }; #endif \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/esp/BufferedAudioSink.h b/components/spotify/cspot/bell/main/audio-sinks/include/esp/BufferedAudioSink.h index eb63caf9..f5dc2319 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/esp/BufferedAudioSink.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/esp/BufferedAudioSink.h @@ -1,25 +1,27 @@ #ifndef BUFFEREDAUDIOSINK_H #define BUFFEREDAUDIOSINK_H -#include -#include -#include "AudioSink.h" #include #include -#include #include +#include +#include +#include +#include "AudioSink.h" #include "esp_err.h" #include "esp_log.h" -class BufferedAudioSink : public AudioSink -{ -public: - void feedPCMFrames(const uint8_t *buffer, size_t bytes) override; - bool setParams(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) override; -protected: - void startI2sFeed(size_t buf_size = 4096 * 8); - void feedPCMFramesInternal(const void *pvItem, size_t xItemSize); -private: +class BufferedAudioSink : public AudioSink { + public: + void feedPCMFrames(const uint8_t* buffer, size_t bytes) override; + bool setParams(uint32_t sampleRate, uint8_t channelCount, + uint8_t bitDepth) override; + + protected: + void startI2sFeed(size_t buf_size = 4096 * 8); + void feedPCMFramesInternal(const void* pvItem, size_t xItemSize); + + private: }; #endif \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/esp/ES8311AudioSink.h b/components/spotify/cspot/bell/main/audio-sinks/include/esp/ES8311AudioSink.h index 697e81b7..6e756ab9 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/esp/ES8311AudioSink.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/esp/ES8311AudioSink.h @@ -1,28 +1,28 @@ #ifndef ES8311AUDIOSINK_H #define ES8311AUDIOSINK_H -#include "driver/i2s.h" -#include -#include -#include "BufferedAudioSink.h" #include #include +#include +#include +#include +#include +#include "BufferedAudioSink.h" #include "driver/gpio.h" #include "driver/i2c.h" -#include -#include +#include "driver/i2s.h" #include "esp_err.h" #include "esp_log.h" -class ES8311AudioSink : public BufferedAudioSink -{ -public: - ES8311AudioSink(); - ~ES8311AudioSink(); - void writeReg(uint8_t reg_add, uint8_t data); - void volumeChanged(uint16_t volume); - void setSampleRate(uint32_t sampleRate); -private: +class ES8311AudioSink : public BufferedAudioSink { + public: + ES8311AudioSink(); + ~ES8311AudioSink(); + void writeReg(uint8_t reg_add, uint8_t data); + void volumeChanged(uint16_t volume); + void setSampleRate(uint32_t sampleRate); + + private: }; #endif \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/esp/ES8388AudioSink.h b/components/spotify/cspot/bell/main/audio-sinks/include/esp/ES8388AudioSink.h index 77c917c8..92743647 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/esp/ES8388AudioSink.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/esp/ES8388AudioSink.h @@ -1,23 +1,22 @@ #ifndef ES8388AUDIOSINK_H #define ES8388AUDIOSINK_H -#include "driver/i2s.h" #include -#include -#include -#include "BufferedAudioSink.h" +#include #include #include -#include -#include #include +#include +#include +#include +#include "BufferedAudioSink.h" +#include "driver/i2s.h" #include "esp_err.h" #include "esp_log.h" - #define ES8388_ADDR 0x20 -#define ACK_CHECK_EN 0x1 +#define ACK_CHECK_EN 0x1 /* ES8388 register */ #define ES8388_CONTROL1 0x00 @@ -78,28 +77,27 @@ #define ES8388_DACCONTROL29 0x33 #define ES8388_DACCONTROL30 0x34 -class ES8388AudioSink : public BufferedAudioSink -{ -public: - ES8388AudioSink(); - ~ES8388AudioSink(); - - bool begin(int sda = -1, int scl = -1, uint32_t frequency = 400000U); +class ES8388AudioSink : public BufferedAudioSink { + public: + ES8388AudioSink(); + ~ES8388AudioSink(); - enum ES8388_OUT - { - ES_MAIN, // this is the DAC output volume (both outputs) - ES_OUT1, // this is the additional gain for OUT1 - ES_OUT2 // this is the additional gain for OUT2 - }; + bool begin(int sda = -1, int scl = -1, uint32_t frequency = 400000U); - void mute(const ES8388_OUT out, const bool muted); - void volume(const ES8388_OUT out, const uint8_t vol); + enum ES8388_OUT { + ES_MAIN, // this is the DAC output volume (both outputs) + ES_OUT1, // this is the additional gain for OUT1 + ES_OUT2 // this is the additional gain for OUT2 + }; - void writeReg(uint8_t reg_add, uint8_t data); -private: - i2c_config_t i2c_config; - i2c_port_t i2c_port = 0; + void mute(const ES8388_OUT out, const bool muted); + void volume(const ES8388_OUT out, const uint8_t vol); + + void writeReg(uint8_t reg_add, uint8_t data); + + private: + i2c_config_t i2c_config; + i2c_port_t i2c_port = 0; }; #endif \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/esp/ES9018AudioSink.h b/components/spotify/cspot/bell/main/audio-sinks/include/esp/ES9018AudioSink.h index 986b53fa..a0ec5317 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/esp/ES9018AudioSink.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/esp/ES9018AudioSink.h @@ -1,22 +1,22 @@ #ifndef ES9018AUDIOSINK_H #define ES9018AUDIOSINK_H -#include -#include -#include "BufferedAudioSink.h" #include #include -#include #include +#include +#include +#include +#include "BufferedAudioSink.h" #include "esp_err.h" #include "esp_log.h" -class ES9018AudioSink : public BufferedAudioSink -{ -public: - ES9018AudioSink(); - ~ES9018AudioSink(); -private: +class ES9018AudioSink : public BufferedAudioSink { + public: + ES9018AudioSink(); + ~ES9018AudioSink(); + + private: }; #endif \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/esp/InternalAudioSink.h b/components/spotify/cspot/bell/main/audio-sinks/include/esp/InternalAudioSink.h index 9c98523c..c4622bf4 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/esp/InternalAudioSink.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/esp/InternalAudioSink.h @@ -1,22 +1,22 @@ #ifndef INTERNALAUDIOSINK_H #define INTERNALAUDIOSINK_H -#include -#include -#include "BufferedAudioSink.h" #include #include -#include #include +#include +#include +#include +#include "BufferedAudioSink.h" #include "esp_err.h" #include "esp_log.h" -class InternalAudioSink : public BufferedAudioSink -{ -public: - InternalAudioSink(); - ~InternalAudioSink(); -private: +class InternalAudioSink : public BufferedAudioSink { + public: + InternalAudioSink(); + ~InternalAudioSink(); + + private: }; #endif diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/esp/PCM5102AudioSink.h b/components/spotify/cspot/bell/main/audio-sinks/include/esp/PCM5102AudioSink.h index 77096a23..4f751064 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/esp/PCM5102AudioSink.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/esp/PCM5102AudioSink.h @@ -1,22 +1,22 @@ #ifndef PCM5102AUDIOSINK_H #define PCM5102AUDIOSINK_H -#include -#include -#include "BufferedAudioSink.h" #include #include -#include #include +#include +#include +#include +#include "BufferedAudioSink.h" #include "esp_err.h" #include "esp_log.h" -class PCM5102AudioSink : public BufferedAudioSink -{ -public: - PCM5102AudioSink(); - ~PCM5102AudioSink(); -private: +class PCM5102AudioSink : public BufferedAudioSink { + public: + PCM5102AudioSink(); + ~PCM5102AudioSink(); + + private: }; #endif \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/esp/SPDIFAudioSink.h b/components/spotify/cspot/bell/main/audio-sinks/include/esp/SPDIFAudioSink.h index 91ca110b..c92d983c 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/esp/SPDIFAudioSink.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/esp/SPDIFAudioSink.h @@ -1,26 +1,28 @@ #ifndef SPDIFAUDIOSINK_H #define SPDIFAUDIOSINK_H -#include -#include -#include "BufferedAudioSink.h" #include #include -#include #include +#include +#include +#include +#include "BufferedAudioSink.h" #include "esp_err.h" #include "esp_log.h" -class SPDIFAudioSink : public BufferedAudioSink -{ -private: - uint8_t spdifPin; -public: - explicit SPDIFAudioSink(uint8_t spdifPin); - ~SPDIFAudioSink() override; - void feedPCMFrames(const uint8_t *buffer, size_t bytes) override; - bool setParams(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) override; -private: +class SPDIFAudioSink : public BufferedAudioSink { + private: + uint8_t spdifPin; + + public: + explicit SPDIFAudioSink(uint8_t spdifPin); + ~SPDIFAudioSink() override; + void feedPCMFrames(const uint8_t* buffer, size_t bytes) override; + bool setParams(uint32_t sampleRate, uint8_t channelCount, + uint8_t bitDepth) override; + + private: }; #endif \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/esp/TAS5711AudioSink.h b/components/spotify/cspot/bell/main/audio-sinks/include/esp/TAS5711AudioSink.h index 23a705d8..b4319c3a 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/esp/TAS5711AudioSink.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/esp/TAS5711AudioSink.h @@ -1,30 +1,28 @@ #ifndef TAS5711AUDIOSINK_H #define TAS5711AUDIOSINK_H - -#include "driver/i2s.h" #include -#include -#include -#include "BufferedAudioSink.h" #include #include -#include #include +#include +#include +#include +#include "BufferedAudioSink.h" +#include "driver/i2s.h" #include "esp_err.h" #include "esp_log.h" -class TAS5711AudioSink : public BufferedAudioSink -{ -public: - TAS5711AudioSink(); - ~TAS5711AudioSink(); +class TAS5711AudioSink : public BufferedAudioSink { + public: + TAS5711AudioSink(); + ~TAS5711AudioSink(); + void writeReg(uint8_t reg, uint8_t value); - void writeReg(uint8_t reg, uint8_t value); -private: - i2c_config_t i2c_config; - i2c_port_t i2c_port = 0; + private: + i2c_config_t i2c_config; + i2c_port_t i2c_port = 0; }; #endif \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/esp/ac101.h b/components/spotify/cspot/bell/main/audio-sinks/include/esp/ac101.h index 39c17379..2ef3900b 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/esp/ac101.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/esp/ac101.h @@ -21,156 +21,156 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * */ - + #ifndef __AC101_H__ #define __AC101_H__ #include "esp_types.h" -#define AC101_ADDR 0x1a /*!< Device address*/ +#define AC101_ADDR 0x1a /*!< Device address*/ -#define WRITE_BIT I2C_MASTER_WRITE /*!< I2C master write */ -#define READ_BIT I2C_MASTER_READ /*!< I2C master read */ -#define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/ -#define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */ -#define ACK_VAL 0x0 /*!< I2C ack value */ -#define NACK_VAL 0x1 /*!< I2C nack value */ +#define WRITE_BIT I2C_MASTER_WRITE /*!< I2C master write */ +#define READ_BIT I2C_MASTER_READ /*!< I2C master read */ +#define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/ +#define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */ +#define ACK_VAL 0x0 /*!< I2C ack value */ +#define NACK_VAL 0x1 /*!< I2C nack value */ -#define CHIP_AUDIO_RS 0x00 -#define PLL_CTRL1 0x01 -#define PLL_CTRL2 0x02 -#define SYSCLK_CTRL 0x03 -#define MOD_CLK_ENA 0x04 -#define MOD_RST_CTRL 0x05 -#define I2S_SR_CTRL 0x06 -#define I2S1LCK_CTRL 0x10 -#define I2S1_SDOUT_CTRL 0x11 -#define I2S1_SDIN_CTRL 0x12 -#define I2S1_MXR_SRC 0x13 -#define I2S1_VOL_CTRL1 0x14 -#define I2S1_VOL_CTRL2 0x15 -#define I2S1_VOL_CTRL3 0x16 -#define I2S1_VOL_CTRL4 0x17 -#define I2S1_MXR_GAIN 0x18 -#define ADC_DIG_CTRL 0x40 -#define ADC_VOL_CTRL 0x41 -#define HMIC_CTRL1 0x44 -#define HMIC_CTRL2 0x45 -#define HMIC_STATUS 0x46 -#define DAC_DIG_CTRL 0x48 -#define DAC_VOL_CTRL 0x49 -#define DAC_MXR_SRC 0x4c -#define DAC_MXR_GAIN 0x4d -#define ADC_ANA_CTRL 0x50 -#define ADC_SRC 0x51 -#define ADC_SRCBST_CTRL 0x52 -#define OMIXER_DACA_CTRL 0x53 -#define OMIXER_SR 0x54 -#define OMIXER_BST1_CTRL 0x55 -#define HPOUT_CTRL 0x56 -#define SPKOUT_CTRL 0x58 -#define AC_DAC_DAPCTRL 0xa0 -#define AC_DAC_DAPHHPFC 0xa1 -#define AC_DAC_DAPLHPFC 0xa2 -#define AC_DAC_DAPLHAVC 0xa3 -#define AC_DAC_DAPLLAVC 0xa4 -#define AC_DAC_DAPRHAVC 0xa5 -#define AC_DAC_DAPRLAVC 0xa6 -#define AC_DAC_DAPHGDEC 0xa7 -#define AC_DAC_DAPLGDEC 0xa8 -#define AC_DAC_DAPHGATC 0xa9 -#define AC_DAC_DAPLGATC 0xaa -#define AC_DAC_DAPHETHD 0xab -#define AC_DAC_DAPLETHD 0xac -#define AC_DAC_DAPHGKPA 0xad -#define AC_DAC_DAPLGKPA 0xae -#define AC_DAC_DAPHGOPA 0xaf -#define AC_DAC_DAPLGOPA 0xb0 -#define AC_DAC_DAPOPT 0xb1 -#define DAC_DAP_ENA 0xb5 +#define CHIP_AUDIO_RS 0x00 +#define PLL_CTRL1 0x01 +#define PLL_CTRL2 0x02 +#define SYSCLK_CTRL 0x03 +#define MOD_CLK_ENA 0x04 +#define MOD_RST_CTRL 0x05 +#define I2S_SR_CTRL 0x06 +#define I2S1LCK_CTRL 0x10 +#define I2S1_SDOUT_CTRL 0x11 +#define I2S1_SDIN_CTRL 0x12 +#define I2S1_MXR_SRC 0x13 +#define I2S1_VOL_CTRL1 0x14 +#define I2S1_VOL_CTRL2 0x15 +#define I2S1_VOL_CTRL3 0x16 +#define I2S1_VOL_CTRL4 0x17 +#define I2S1_MXR_GAIN 0x18 +#define ADC_DIG_CTRL 0x40 +#define ADC_VOL_CTRL 0x41 +#define HMIC_CTRL1 0x44 +#define HMIC_CTRL2 0x45 +#define HMIC_STATUS 0x46 +#define DAC_DIG_CTRL 0x48 +#define DAC_VOL_CTRL 0x49 +#define DAC_MXR_SRC 0x4c +#define DAC_MXR_GAIN 0x4d +#define ADC_ANA_CTRL 0x50 +#define ADC_SRC 0x51 +#define ADC_SRCBST_CTRL 0x52 +#define OMIXER_DACA_CTRL 0x53 +#define OMIXER_SR 0x54 +#define OMIXER_BST1_CTRL 0x55 +#define HPOUT_CTRL 0x56 +#define SPKOUT_CTRL 0x58 +#define AC_DAC_DAPCTRL 0xa0 +#define AC_DAC_DAPHHPFC 0xa1 +#define AC_DAC_DAPLHPFC 0xa2 +#define AC_DAC_DAPLHAVC 0xa3 +#define AC_DAC_DAPLLAVC 0xa4 +#define AC_DAC_DAPRHAVC 0xa5 +#define AC_DAC_DAPRLAVC 0xa6 +#define AC_DAC_DAPHGDEC 0xa7 +#define AC_DAC_DAPLGDEC 0xa8 +#define AC_DAC_DAPHGATC 0xa9 +#define AC_DAC_DAPLGATC 0xaa +#define AC_DAC_DAPHETHD 0xab +#define AC_DAC_DAPLETHD 0xac +#define AC_DAC_DAPHGKPA 0xad +#define AC_DAC_DAPLGKPA 0xae +#define AC_DAC_DAPHGOPA 0xaf +#define AC_DAC_DAPLGOPA 0xb0 +#define AC_DAC_DAPOPT 0xb1 +#define DAC_DAP_ENA 0xb5 -typedef enum{ - SAMPLE_RATE_8000 = 0x0000, - SAMPLE_RATE_11052 = 0x1000, - SAMPLE_RATE_12000 = 0x2000, - SAMPLE_RATE_16000 = 0x3000, - SAMPLE_RATE_22050 = 0x4000, - SAMPLE_RATE_24000 = 0x5000, - SAMPLE_RATE_32000 = 0x6000, - SAMPLE_RATE_44100 = 0x7000, - SAMPLE_RATE_48000 = 0x8000, - SAMPLE_RATE_96000 = 0x9000, - SAMPLE_RATE_192000 = 0xa000, +typedef enum { + SAMPLE_RATE_8000 = 0x0000, + SAMPLE_RATE_11052 = 0x1000, + SAMPLE_RATE_12000 = 0x2000, + SAMPLE_RATE_16000 = 0x3000, + SAMPLE_RATE_22050 = 0x4000, + SAMPLE_RATE_24000 = 0x5000, + SAMPLE_RATE_32000 = 0x6000, + SAMPLE_RATE_44100 = 0x7000, + SAMPLE_RATE_48000 = 0x8000, + SAMPLE_RATE_96000 = 0x9000, + SAMPLE_RATE_192000 = 0xa000, } ac_adda_fs_i2s1_t; -typedef enum{ - BCLK_DIV_1 = 0x0, - BCLK_DIV_2 = 0x1, - BCLK_DIV_4 = 0x2, - BCLK_DIV_6 = 0x3, - BCLK_DIV_8 = 0x4, - BCLK_DIV_12 = 0x5, - BCLK_DIV_16 = 0x6, - BCLK_DIV_24 = 0x7, - BCLK_DIV_32 = 0x8, - BCLK_DIV_48 = 0x9, - BCLK_DIV_64 = 0xa, - BCLK_DIV_96 = 0xb, - BCLK_DIV_128 = 0xc, - BCLK_DIV_192 = 0xd, +typedef enum { + BCLK_DIV_1 = 0x0, + BCLK_DIV_2 = 0x1, + BCLK_DIV_4 = 0x2, + BCLK_DIV_6 = 0x3, + BCLK_DIV_8 = 0x4, + BCLK_DIV_12 = 0x5, + BCLK_DIV_16 = 0x6, + BCLK_DIV_24 = 0x7, + BCLK_DIV_32 = 0x8, + BCLK_DIV_48 = 0x9, + BCLK_DIV_64 = 0xa, + BCLK_DIV_96 = 0xb, + BCLK_DIV_128 = 0xc, + BCLK_DIV_192 = 0xd, } ac_i2s1_bclk_div_t; -typedef enum{ - LRCK_DIV_16 =0x0, - LRCK_DIV_32 =0x1, - LRCK_DIV_64 =0x2, - LRCK_DIV_128 =0x3, - LRCK_DIV_256 =0x4, +typedef enum { + LRCK_DIV_16 = 0x0, + LRCK_DIV_32 = 0x1, + LRCK_DIV_64 = 0x2, + LRCK_DIV_128 = 0x3, + LRCK_DIV_256 = 0x4, } ac_i2s1_lrck_div_t; typedef enum { - BIT_LENGTH_8_BITS = 0x00, - BIT_LENGTH_16_BITS = 0x01, - BIT_LENGTH_20_BITS = 0x02, - BIT_LENGTH_24_BITS = 0x03, + BIT_LENGTH_8_BITS = 0x00, + BIT_LENGTH_16_BITS = 0x01, + BIT_LENGTH_20_BITS = 0x02, + BIT_LENGTH_24_BITS = 0x03, } ac_bits_length_t; typedef enum { - AC_MODE_MIN = -1, - AC_MODE_SLAVE = 0x00, - AC_MODE_MASTER = 0x01, - AC_MODE_MAX, + AC_MODE_MIN = -1, + AC_MODE_SLAVE = 0x00, + AC_MODE_MASTER = 0x01, + AC_MODE_MAX, } ac_mode_sm_t; typedef enum { - AC_MODULE_MIN = -1, - AC_MODULE_ADC = 0x01, - AC_MODULE_DAC = 0x02, - AC_MODULE_ADC_DAC = 0x03, - AC_MODULE_LINE = 0x04, - AC_MODULE_MAX + AC_MODULE_MIN = -1, + AC_MODULE_ADC = 0x01, + AC_MODULE_DAC = 0x02, + AC_MODULE_ADC_DAC = 0x03, + AC_MODULE_LINE = 0x04, + AC_MODULE_MAX } ac_module_t; -typedef enum{ - SRC_MIC1 = 1, - SRC_MIC2 = 2, - SRC_LINEIN = 3, -}ac_output_mixer_source_t; +typedef enum { + SRC_MIC1 = 1, + SRC_MIC2 = 2, + SRC_LINEIN = 3, +} ac_output_mixer_source_t; typedef enum { - GAIN_N45DB = 0, - GAIN_N30DB = 1, - GAIN_N15DB = 2, - GAIN_0DB = 3, - GAIN_15DB = 4, - GAIN_30DB = 5, - GAIN_45DB = 6, - GAIN_60DB = 7, + GAIN_N45DB = 0, + GAIN_N30DB = 1, + GAIN_N15DB = 2, + GAIN_0DB = 3, + GAIN_15DB = 4, + GAIN_30DB = 5, + GAIN_45DB = 6, + GAIN_60DB = 7, } ac_output_mixer_gain_t; typedef struct { - ac_i2s1_bclk_div_t bclk_div; /*!< bits clock divide */ - ac_i2s1_lrck_div_t lclk_div; /*!< WS clock divide */ + ac_i2s1_bclk_div_t bclk_div; /*!< bits clock divide */ + ac_i2s1_lrck_div_t lclk_div; /*!< WS clock divide */ } ac_i2s_clock_t; #endif \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/esp/adac.h b/components/spotify/cspot/bell/main/audio-sinks/include/esp/adac.h index 1b1a2668..53d5a2bb 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/esp/adac.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/esp/adac.h @@ -9,18 +9,18 @@ * */ -#include "freertos/FreeRTOS.h" #include "driver/i2s.h" +#include "freertos/FreeRTOS.h" typedef enum { ADAC_ON = 0, ADAC_STANDBY, ADAC_OFF } adac_power_e; struct adac_s { - bool (*init)(int i2c_port_num, int i2s_num, i2s_config_t *config); - void (*deinit)(void); - void (*power)(adac_power_e mode); - void (*speaker)(bool active); - void (*headset)(bool active); - void (*volume)(unsigned left, unsigned right); + bool (*init)(int i2c_port_num, int i2s_num, i2s_config_t* config); + void (*deinit)(void); + void (*power)(adac_power_e mode); + void (*speaker)(bool active); + void (*headset)(bool active); + void (*volume)(unsigned left, unsigned right); }; extern struct adac_s dac_tas57xx; diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/esp/es8311.h b/components/spotify/cspot/bell/main/audio-sinks/include/esp/es8311.h index e576dedd..17913d73 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/esp/es8311.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/esp/es8311.h @@ -18,80 +18,80 @@ /* * ES8311_REGISTER NAME_REG_REGISTER ADDRESS */ -#define ES8311_RESET_REG00 0x00 /*reset digital,csm,clock manager etc.*/ +#define ES8311_RESET_REG00 0x00 /*reset digital,csm,clock manager etc.*/ /* * Clock Scheme Register definition */ -#define ES8311_CLK_MANAGER_REG01 0x01 /* select clk src for mclk, enable clock for codec */ -#define ES8311_CLK_MANAGER_REG02 0x02 /* clk divider and clk multiplier */ -#define ES8311_CLK_MANAGER_REG03 0x03 /* adc fsmode and osr */ -#define ES8311_CLK_MANAGER_REG04 0x04 /* dac osr */ -#define ES8311_CLK_MANAGER_REG05 0x05 /* clk divier for adc and dac */ -#define ES8311_CLK_MANAGER_REG06 0x06 /* bclk inverter and divider */ -#define ES8311_CLK_MANAGER_REG07 0x07 /* tri-state, lrck divider */ -#define ES8311_CLK_MANAGER_REG08 0x08 /* lrck divider */ -#define ES8311_SDPIN_REG09 0x09 /* dac serial digital port */ -#define ES8311_SDPOUT_REG0A 0x0A /* adc serial digital port */ -#define ES8311_SYSTEM_REG0B 0x0B /* system */ -#define ES8311_SYSTEM_REG0C 0x0C /* system */ -#define ES8311_SYSTEM_REG0D 0x0D /* system, power up/down */ -#define ES8311_SYSTEM_REG0E 0x0E /* system, power up/down */ -#define ES8311_SYSTEM_REG0F 0x0F /* system, low power */ -#define ES8311_SYSTEM_REG10 0x10 /* system */ -#define ES8311_SYSTEM_REG11 0x11 /* system */ -#define ES8311_SYSTEM_REG12 0x12 /* system, Enable DAC */ -#define ES8311_SYSTEM_REG13 0x13 /* system */ -#define ES8311_SYSTEM_REG14 0x14 /* system, select DMIC, select analog pga gain */ -#define ES8311_ADC_REG15 0x15 /* ADC, adc ramp rate, dmic sense */ -#define ES8311_ADC_REG16 0x16 /* ADC */ -#define ES8311_ADC_REG17 0x17 /* ADC, volume */ -#define ES8311_ADC_REG18 0x18 /* ADC, alc enable and winsize */ -#define ES8311_ADC_REG19 0x19 /* ADC, alc maxlevel */ -#define ES8311_ADC_REG1A 0x1A /* ADC, alc automute */ -#define ES8311_ADC_REG1B 0x1B /* ADC, alc automute, adc hpf s1 */ -#define ES8311_ADC_REG1C 0x1C /* ADC, equalizer, hpf s2 */ -#define ES8311_DAC_REG31 0x31 /* DAC, mute */ -#define ES8311_DAC_REG32 0x32 /* DAC, volume */ -#define ES8311_DAC_REG33 0x33 /* DAC, offset */ -#define ES8311_DAC_REG34 0x34 /* DAC, drc enable, drc winsize */ -#define ES8311_DAC_REG35 0x35 /* DAC, drc maxlevel, minilevel */ -#define ES8311_DAC_REG37 0x37 /* DAC, ramprate */ -#define ES8311_GPIO_REG44 0x44 /* GPIO, dac2adc for test */ -#define ES8311_GP_REG45 0x45 /* GP CONTROL */ -#define ES8311_CHD1_REGFD 0xFD /* CHIP ID1 */ -#define ES8311_CHD2_REGFE 0xFE /* CHIP ID2 */ -#define ES8311_CHVER_REGFF 0xFF /* VERSION */ -#define ES8311_CHD1_REGFD 0xFD /* CHIP ID1 */ - -#define ES8311_MAX_REGISTER 0xFF +#define ES8311_CLK_MANAGER_REG01 \ + 0x01 /* select clk src for mclk, enable clock for codec */ +#define ES8311_CLK_MANAGER_REG02 0x02 /* clk divider and clk multiplier */ +#define ES8311_CLK_MANAGER_REG03 0x03 /* adc fsmode and osr */ +#define ES8311_CLK_MANAGER_REG04 0x04 /* dac osr */ +#define ES8311_CLK_MANAGER_REG05 0x05 /* clk divier for adc and dac */ +#define ES8311_CLK_MANAGER_REG06 0x06 /* bclk inverter and divider */ +#define ES8311_CLK_MANAGER_REG07 0x07 /* tri-state, lrck divider */ +#define ES8311_CLK_MANAGER_REG08 0x08 /* lrck divider */ +#define ES8311_SDPIN_REG09 0x09 /* dac serial digital port */ +#define ES8311_SDPOUT_REG0A 0x0A /* adc serial digital port */ +#define ES8311_SYSTEM_REG0B 0x0B /* system */ +#define ES8311_SYSTEM_REG0C 0x0C /* system */ +#define ES8311_SYSTEM_REG0D 0x0D /* system, power up/down */ +#define ES8311_SYSTEM_REG0E 0x0E /* system, power up/down */ +#define ES8311_SYSTEM_REG0F 0x0F /* system, low power */ +#define ES8311_SYSTEM_REG10 0x10 /* system */ +#define ES8311_SYSTEM_REG11 0x11 /* system */ +#define ES8311_SYSTEM_REG12 0x12 /* system, Enable DAC */ +#define ES8311_SYSTEM_REG13 0x13 /* system */ +#define ES8311_SYSTEM_REG14 \ + 0x14 /* system, select DMIC, select analog pga gain */ +#define ES8311_ADC_REG15 0x15 /* ADC, adc ramp rate, dmic sense */ +#define ES8311_ADC_REG16 0x16 /* ADC */ +#define ES8311_ADC_REG17 0x17 /* ADC, volume */ +#define ES8311_ADC_REG18 0x18 /* ADC, alc enable and winsize */ +#define ES8311_ADC_REG19 0x19 /* ADC, alc maxlevel */ +#define ES8311_ADC_REG1A 0x1A /* ADC, alc automute */ +#define ES8311_ADC_REG1B 0x1B /* ADC, alc automute, adc hpf s1 */ +#define ES8311_ADC_REG1C 0x1C /* ADC, equalizer, hpf s2 */ +#define ES8311_DAC_REG31 0x31 /* DAC, mute */ +#define ES8311_DAC_REG32 0x32 /* DAC, volume */ +#define ES8311_DAC_REG33 0x33 /* DAC, offset */ +#define ES8311_DAC_REG34 0x34 /* DAC, drc enable, drc winsize */ +#define ES8311_DAC_REG35 0x35 /* DAC, drc maxlevel, minilevel */ +#define ES8311_DAC_REG37 0x37 /* DAC, ramprate */ +#define ES8311_GPIO_REG44 0x44 /* GPIO, dac2adc for test */ +#define ES8311_GP_REG45 0x45 /* GP CONTROL */ +#define ES8311_CHD1_REGFD 0xFD /* CHIP ID1 */ +#define ES8311_CHD2_REGFE 0xFE /* CHIP ID2 */ +#define ES8311_CHVER_REGFF 0xFF /* VERSION */ +#define ES8311_CHD1_REGFD 0xFD /* CHIP ID1 */ +#define ES8311_MAX_REGISTER 0xFF typedef struct { - ESCodecMode esMode; - i2c_port_t i2c_port_num; - i2c_config_t i2c_cfg; - DacOutput dacOutput; - AdcInput adcInput; + ESCodecMode esMode; + i2c_port_t i2c_port_num; + i2c_config_t i2c_cfg; + DacOutput dacOutput; + AdcInput adcInput; } Es8311Config; +#define AUDIO_CODEC_ES8311_DEFAULT() \ + { \ + .esMode = ES_MODE_SLAVE, \ + .i2c_port_num = I2C_NUM_0, \ + .i2c_cfg = {.mode = I2C_MODE_MASTER, \ + .sda_io_num = IIC_DATA, \ + .scl_io_num = IIC_CLK, \ + .sda_pullup_en = GPIO_PULLUP_ENABLE, \ + .scl_pullup_en = GPIO_PULLUP_ENABLE, \ + .master.clk_speed = 100000}, \ + .adcInput = ADC_INPUT_LINPUT1_RINPUT1, \ + .dacOutput = DAC_OUTPUT_LOUT1 | DAC_OUTPUT_LOUT2 | DAC_OUTPUT_ROUT1 | \ + DAC_OUTPUT_ROUT2, \ + }; -#define AUDIO_CODEC_ES8311_DEFAULT(){ \ - .esMode = ES_MODE_SLAVE, \ - .i2c_port_num = I2C_NUM_0, \ - .i2c_cfg = { \ - .mode = I2C_MODE_MASTER, \ - .sda_io_num = IIC_DATA, \ - .scl_io_num = IIC_CLK, \ - .sda_pullup_en = GPIO_PULLUP_ENABLE,\ - .scl_pullup_en = GPIO_PULLUP_ENABLE,\ - .master.clk_speed = 100000\ - }, \ - .adcInput = ADC_INPUT_LINPUT1_RINPUT1,\ - .dacOutput = DAC_OUTPUT_LOUT1 | DAC_OUTPUT_LOUT2 | DAC_OUTPUT_ROUT1 | DAC_OUTPUT_ROUT2,\ -}; - -int Es8311Init(Es8311Config *cfg); +int Es8311Init(Es8311Config* cfg); void Es8311Uninit(); esp_err_t Es8311GetRef(bool flag); esp_err_t Es7243Init(void); @@ -107,9 +107,9 @@ int Es8311Start(ESCodecModule mode); int Es8311Stop(ESCodecModule mode); int Es8311SetVoiceVolume(int volume); -int Es8311GetVoiceVolume(int *volume); +int Es8311GetVoiceVolume(int* volume); int Es8311SetVoiceMute(int enable); -int Es8311GetVoiceMute(int *mute); +int Es8311GetVoiceMute(int* mute); int Es8311SetMicGain(MicGain gain); int Es8311ConfigAdcInput(AdcInput input); diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/esp/esxxx_common.h b/components/spotify/cspot/bell/main/audio-sinks/include/esp/esxxx_common.h index bf8b35aa..3b4624ba 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/esp/esxxx_common.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/esp/esxxx_common.h @@ -2,165 +2,165 @@ #define __ESCODEC_COMMON_H__ typedef enum BitsLength { - BIT_LENGTH_MIN = -1, - BIT_LENGTH_16BITS = 0x03, - BIT_LENGTH_18BITS = 0x02, - BIT_LENGTH_20BITS = 0x01, - BIT_LENGTH_24BITS = 0x00, - BIT_LENGTH_32BITS = 0x04, - BIT_LENGTH_MAX, + BIT_LENGTH_MIN = -1, + BIT_LENGTH_16BITS = 0x03, + BIT_LENGTH_18BITS = 0x02, + BIT_LENGTH_20BITS = 0x01, + BIT_LENGTH_24BITS = 0x00, + BIT_LENGTH_32BITS = 0x04, + BIT_LENGTH_MAX, } BitsLength; typedef enum { - SAMPLE_RATE_MIN = -1, - SAMPLE_RATE_16K, - SAMPLE_RATE_32K, - SAMPLE_RATE_44_1K, - SAMPLE_RATE_MAX, + SAMPLE_RATE_MIN = -1, + SAMPLE_RATE_16K, + SAMPLE_RATE_32K, + SAMPLE_RATE_44_1K, + SAMPLE_RATE_MAX, } SampleRate; typedef enum { - MclkDiv_MIN = -1, - MclkDiv_1 = 1, - MclkDiv_2 = 2, - MclkDiv_3 = 3, - MclkDiv_4 = 4, - MclkDiv_6 = 5, - MclkDiv_8 = 6, - MclkDiv_9 = 7, - MclkDiv_11 = 8, - MclkDiv_12 = 9, - MclkDiv_16 = 10, - MclkDiv_18 = 11, - MclkDiv_22 = 12, - MclkDiv_24 = 13, - MclkDiv_33 = 14, - MclkDiv_36 = 15, - MclkDiv_44 = 16, - MclkDiv_48 = 17, - MclkDiv_66 = 18, - MclkDiv_72 = 19, - MclkDiv_5 = 20, - MclkDiv_10 = 21, - MclkDiv_15 = 22, - MclkDiv_17 = 23, - MclkDiv_20 = 24, - MclkDiv_25 = 25, - MclkDiv_30 = 26, - MclkDiv_32 = 27, - MclkDiv_34 = 28, - MclkDiv_7 = 29, - MclkDiv_13 = 30, - MclkDiv_14 = 31, - MclkDiv_MAX, + MclkDiv_MIN = -1, + MclkDiv_1 = 1, + MclkDiv_2 = 2, + MclkDiv_3 = 3, + MclkDiv_4 = 4, + MclkDiv_6 = 5, + MclkDiv_8 = 6, + MclkDiv_9 = 7, + MclkDiv_11 = 8, + MclkDiv_12 = 9, + MclkDiv_16 = 10, + MclkDiv_18 = 11, + MclkDiv_22 = 12, + MclkDiv_24 = 13, + MclkDiv_33 = 14, + MclkDiv_36 = 15, + MclkDiv_44 = 16, + MclkDiv_48 = 17, + MclkDiv_66 = 18, + MclkDiv_72 = 19, + MclkDiv_5 = 20, + MclkDiv_10 = 21, + MclkDiv_15 = 22, + MclkDiv_17 = 23, + MclkDiv_20 = 24, + MclkDiv_25 = 25, + MclkDiv_30 = 26, + MclkDiv_32 = 27, + MclkDiv_34 = 28, + MclkDiv_7 = 29, + MclkDiv_13 = 30, + MclkDiv_14 = 31, + MclkDiv_MAX, } SclkDiv; typedef enum { - LclkDiv_MIN = -1, - LclkDiv_128 = 0, - LclkDiv_192 = 1, - LclkDiv_256 = 2, - LclkDiv_384 = 3, - LclkDiv_512 = 4, - LclkDiv_576 = 5, - LclkDiv_768 = 6, - LclkDiv_1024 = 7, - LclkDiv_1152 = 8, - LclkDiv_1408 = 9, - LclkDiv_1536 = 10, - LclkDiv_2112 = 11, - LclkDiv_2304 = 12, + LclkDiv_MIN = -1, + LclkDiv_128 = 0, + LclkDiv_192 = 1, + LclkDiv_256 = 2, + LclkDiv_384 = 3, + LclkDiv_512 = 4, + LclkDiv_576 = 5, + LclkDiv_768 = 6, + LclkDiv_1024 = 7, + LclkDiv_1152 = 8, + LclkDiv_1408 = 9, + LclkDiv_1536 = 10, + LclkDiv_2112 = 11, + LclkDiv_2304 = 12, - LclkDiv_125 = 16, - LclkDiv_136 = 17, - LclkDiv_250 = 18, - LclkDiv_272 = 19, - LclkDiv_375 = 20, - LclkDiv_500 = 21, - LclkDiv_544 = 22, - LclkDiv_750 = 23, - LclkDiv_1000 = 24, - LclkDiv_1088 = 25, - LclkDiv_1496 = 26, - LclkDiv_1500 = 27, - LclkDiv_MAX, + LclkDiv_125 = 16, + LclkDiv_136 = 17, + LclkDiv_250 = 18, + LclkDiv_272 = 19, + LclkDiv_375 = 20, + LclkDiv_500 = 21, + LclkDiv_544 = 22, + LclkDiv_750 = 23, + LclkDiv_1000 = 24, + LclkDiv_1088 = 25, + LclkDiv_1496 = 26, + LclkDiv_1500 = 27, + LclkDiv_MAX, } LclkDiv; typedef enum { - ADC_INPUT_MIN = -1, - ADC_INPUT_LINPUT1_RINPUT1 = 0x00, - ADC_INPUT_MIC1 = 0x05, - ADC_INPUT_MIC2 = 0x06, - ADC_INPUT_LINPUT2_RINPUT2 = 0x50, - ADC_INPUT_DIFFERENCE = 0xf0, - ADC_INPUT_MAX, + ADC_INPUT_MIN = -1, + ADC_INPUT_LINPUT1_RINPUT1 = 0x00, + ADC_INPUT_MIC1 = 0x05, + ADC_INPUT_MIC2 = 0x06, + ADC_INPUT_LINPUT2_RINPUT2 = 0x50, + ADC_INPUT_DIFFERENCE = 0xf0, + ADC_INPUT_MAX, } AdcInput; typedef enum { - DAC_OUTPUT_MIN = -1, - DAC_OUTPUT_LOUT1 = 0x04, - DAC_OUTPUT_LOUT2 = 0x08, - DAC_OUTPUT_SPK = 0x09, - DAC_OUTPUT_ROUT1 = 0x10, - DAC_OUTPUT_ROUT2 = 0x20, - DAC_OUTPUT_ALL = 0x3c, - DAC_OUTPUT_MAX, + DAC_OUTPUT_MIN = -1, + DAC_OUTPUT_LOUT1 = 0x04, + DAC_OUTPUT_LOUT2 = 0x08, + DAC_OUTPUT_SPK = 0x09, + DAC_OUTPUT_ROUT1 = 0x10, + DAC_OUTPUT_ROUT2 = 0x20, + DAC_OUTPUT_ALL = 0x3c, + DAC_OUTPUT_MAX, } DacOutput; typedef enum { - D2SE_PGA_GAIN_MIN = -1, - D2SE_PGA_GAIN_DIS = 0, - D2SE_PGA_GAIN_EN = 1, - D2SE_PGA_GAIN_MAX = 2, + D2SE_PGA_GAIN_MIN = -1, + D2SE_PGA_GAIN_DIS = 0, + D2SE_PGA_GAIN_EN = 1, + D2SE_PGA_GAIN_MAX = 2, } D2SEPGA; typedef enum { - MIC_GAIN_MIN = -1, - MIC_GAIN_0DB = 0, - MIC_GAIN_3DB = 3, - MIC_GAIN_6DB = 6, - MIC_GAIN_9DB = 9, - MIC_GAIN_12DB = 12, - MIC_GAIN_15DB = 15, - MIC_GAIN_18DB = 18, - MIC_GAIN_21DB = 21, - MIC_GAIN_24DB = 24, + MIC_GAIN_MIN = -1, + MIC_GAIN_0DB = 0, + MIC_GAIN_3DB = 3, + MIC_GAIN_6DB = 6, + MIC_GAIN_9DB = 9, + MIC_GAIN_12DB = 12, + MIC_GAIN_15DB = 15, + MIC_GAIN_18DB = 18, + MIC_GAIN_21DB = 21, + MIC_GAIN_24DB = 24, #if defined CONFIG_CODEC_CHIP_IS_ES8311 - MIC_GAIN_30DB = 30, - MIC_GAIN_36DB = 36, - MIC_GAIN_42DB = 42, + MIC_GAIN_30DB = 30, + MIC_GAIN_36DB = 36, + MIC_GAIN_42DB = 42, #endif - MIC_GAIN_MAX, + MIC_GAIN_MAX, } MicGain; typedef enum { - ES_MODULE_MIN = -1, - ES_MODULE_ADC = 0x01, - ES_MODULE_DAC = 0x02, - ES_MODULE_ADC_DAC = 0x03, - ES_MODULE_LINE = 0x04, - ES_MODULE_MAX + ES_MODULE_MIN = -1, + ES_MODULE_ADC = 0x01, + ES_MODULE_DAC = 0x02, + ES_MODULE_ADC_DAC = 0x03, + ES_MODULE_LINE = 0x04, + ES_MODULE_MAX } ESCodecModule; typedef enum { - ES_MODE_MIN = -1, - ES_MODE_SLAVE = 0x00, - ES_MODE_MASTER = 0x01, - ES_MODE_MAX, + ES_MODE_MIN = -1, + ES_MODE_SLAVE = 0x00, + ES_MODE_MASTER = 0x01, + ES_MODE_MAX, } ESCodecMode; typedef enum { - ES_ = -1, - ES_I2S_NORMAL = 0, - ES_I2S_LEFT = 1, - ES_I2S_RIGHT = 2, - ES_I2S_DSP = 3, - ES_I2S_MAX + ES_ = -1, + ES_I2S_NORMAL = 0, + ES_I2S_LEFT = 1, + ES_I2S_RIGHT = 2, + ES_I2S_DSP = 3, + ES_I2S_MAX } ESCodecI2SFmt; typedef struct { - SclkDiv sclkDiv; - LclkDiv lclkDiv; + SclkDiv sclkDiv; + LclkDiv lclkDiv; } ESCodecI2sClock; -#endif //__ESCODEC_COMMON_H__ +#endif //__ESCODEC_COMMON_H__ diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/unix/ALSAAudioSink.h b/components/spotify/cspot/bell/main/audio-sinks/include/unix/ALSAAudioSink.h index 1b1bd548..3a553e14 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/unix/ALSAAudioSink.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/unix/ALSAAudioSink.h @@ -1,124 +1,106 @@ #pragma once -#include -#include -#include "AudioSink.h" +#include #include #include -#include #include +#include #include #include +#include +#include "AudioSink.h" #define PCM_DEVICE "default" template -class RingbufferPointer -{ - typedef std::unique_ptr TPointer; +class RingbufferPointer { + typedef std::unique_ptr TPointer; -public: - explicit RingbufferPointer() - { - // create objects - for (int i = 0; i < SIZE; i++) - { - buf_[i] = std::make_unique(); - } + public: + explicit RingbufferPointer() { + // create objects + for (int i = 0; i < SIZE; i++) { + buf_[i] = std::make_unique(); + } + } + + bool push(TPointer& item) { + std::lock_guard lock(mutex_); + if (full()) + return false; + + std::swap(buf_[head_], item); + + if (full_) + tail_ = (tail_ + 1) % max_size_; + + head_ = (head_ + 1) % max_size_; + full_ = head_ == tail_; + + return true; + } + + bool pop(TPointer& item) { + std::lock_guard lock(mutex_); + if (empty()) + return false; + + std::swap(buf_[tail_], item); + + full_ = false; + tail_ = (tail_ + 1) % max_size_; + + return true; + } + + void reset() { + std::lock_guard lock(mutex_); + head_ = tail_; + full_ = false; + } + + bool empty() const { return (!full_ && (head_ == tail_)); } + + bool full() const { return full_; } + + int capacity() const { return max_size_; } + + int size() const { + int size = max_size_; + + if (!full_) { + if (head_ >= tail_) + size = head_ - tail_; + else + size = max_size_ + head_ - tail_; } - bool push(TPointer &item) - { - std::lock_guard lock(mutex_); - if (full()) - return false; + return size; + } - std::swap(buf_[head_], item); + private: + TPointer buf_[SIZE]; - if (full_) - tail_ = (tail_ + 1) % max_size_; - - head_ = (head_ + 1) % max_size_; - full_ = head_ == tail_; - - return true; - } - - bool pop(TPointer &item) - { - std::lock_guard lock(mutex_); - if (empty()) - return false; - - std::swap(buf_[tail_], item); - - full_ = false; - tail_ = (tail_ + 1) % max_size_; - - return true; - } - - void reset() - { - std::lock_guard lock(mutex_); - head_ = tail_; - full_ = false; - } - - bool empty() const - { - return (!full_ && (head_ == tail_)); - } - - bool full() const - { - return full_; - } - - int capacity() const - { - return max_size_; - } - - int size() const - { - int size = max_size_; - - if (!full_) - { - if (head_ >= tail_) - size = head_ - tail_; - else - size = max_size_ + head_ - tail_; - } - - return size; - } - -private: - TPointer buf_[SIZE]; - - std::mutex mutex_; - int head_ = 0; - int tail_ = 0; - const int max_size_ = SIZE; - bool full_ = 0; + std::mutex mutex_; + int head_ = 0; + int tail_ = 0; + const int max_size_ = SIZE; + bool full_ = 0; }; -class ALSAAudioSink : public AudioSink, public bell::Task -{ -public: - ALSAAudioSink(); - ~ALSAAudioSink(); - void feedPCMFrames(const uint8_t *buffer, size_t bytes); - void runTask(); +class ALSAAudioSink : public AudioSink, public bell::Task { + public: + ALSAAudioSink(); + ~ALSAAudioSink(); + void feedPCMFrames(const uint8_t* buffer, size_t bytes); + void runTask(); -private: - RingbufferPointer, 3> ringbuffer; - unsigned int pcm; - snd_pcm_t *pcm_handle; - snd_pcm_hw_params_t *params; - snd_pcm_uframes_t frames; - int buff_size; - std::vector buff; + private: + RingbufferPointer, 3> ringbuffer; + unsigned int pcm; + snd_pcm_t* pcm_handle; + snd_pcm_hw_params_t* params; + snd_pcm_uframes_t frames; + int buff_size; + std::vector buff; }; diff --git a/components/spotify/cspot/bell/main/audio-sinks/include/unix/NamedPipeAudioSink.h b/components/spotify/cspot/bell/main/audio-sinks/include/unix/NamedPipeAudioSink.h index cbce8884..f987df91 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/include/unix/NamedPipeAudioSink.h +++ b/components/spotify/cspot/bell/main/audio-sinks/include/unix/NamedPipeAudioSink.h @@ -1,16 +1,17 @@ #pragma once -#include -#include -#include "AudioSink.h" +#include // for size_t +#include // for uint8_t +#include // for ofstream -class NamedPipeAudioSink : public AudioSink -{ -public: - NamedPipeAudioSink(); - ~NamedPipeAudioSink(); - void feedPCMFrames(const uint8_t *buffer, size_t bytes); - -private: - std::ofstream namedPipeFile; +#include "AudioSink.h" // for AudioSink + +class NamedPipeAudioSink : public AudioSink { + public: + NamedPipeAudioSink(); + ~NamedPipeAudioSink(); + void feedPCMFrames(const uint8_t* buffer, size_t bytes); + + private: + std::ofstream namedPipeFile; }; diff --git a/components/spotify/cspot/bell/main/audio-sinks/unix/ALSAAudioSink.cpp b/components/spotify/cspot/bell/main/audio-sinks/unix/ALSAAudioSink.cpp index 0219fbbe..7b63d848 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/unix/ALSAAudioSink.cpp +++ b/components/spotify/cspot/bell/main/audio-sinks/unix/ALSAAudioSink.cpp @@ -1,101 +1,92 @@ #include "ALSAAudioSink.h" -ALSAAudioSink::ALSAAudioSink() : Task("", 0, 0, 0) -{ - /* Open the PCM device in playback mode */ - if (pcm = snd_pcm_open(&pcm_handle, PCM_DEVICE, - SND_PCM_STREAM_PLAYBACK, 0) < 0) - { - printf("ERROR: Can't open \"%s\" PCM device. %s\n", - PCM_DEVICE, snd_strerror(pcm)); +ALSAAudioSink::ALSAAudioSink() : Task("", 0, 0, 0) { + /* Open the PCM device in playback mode */ + if (pcm = snd_pcm_open(&pcm_handle, PCM_DEVICE, SND_PCM_STREAM_PLAYBACK, 0) < + 0) { + printf("ERROR: Can't open \"%s\" PCM device. %s\n", PCM_DEVICE, + snd_strerror(pcm)); + } + + /* Allocate parameters object and fill it with default values*/ + snd_pcm_hw_params_alloca(¶ms); + + snd_pcm_hw_params_any(pcm_handle, params); + + /* Set parameters */ + if (pcm = snd_pcm_hw_params_set_access(pcm_handle, params, + SND_PCM_ACCESS_RW_INTERLEAVED) < 0) + printf("ERROR: Can't set interleaved mode. %s\n", snd_strerror(pcm)); + + if (pcm = snd_pcm_hw_params_set_format(pcm_handle, params, + SND_PCM_FORMAT_S16_LE) < 0) + printf("ERROR: Can't set format. %s\n", snd_strerror(pcm)); + + if (pcm = snd_pcm_hw_params_set_channels(pcm_handle, params, 2) < 0) + printf("ERROR: Can't set channels number. %s\n", snd_strerror(pcm)); + unsigned int rate = 44100; + if (pcm = snd_pcm_hw_params_set_rate_near(pcm_handle, params, &rate, 0) < 0) + printf("ERROR: Can't set rate. %s\n", snd_strerror(pcm)); + unsigned int periodTime = 800; + int dir = -1; + snd_pcm_hw_params_set_period_time_near(pcm_handle, params, &periodTime, &dir); + /* Write parameters */ + if (pcm = snd_pcm_hw_params(pcm_handle, params) < 0) + printf("ERROR: Can't set harware parameters. %s\n", snd_strerror(pcm)); + + /* Resume information */ + printf("PCM name: '%s'\n", snd_pcm_name(pcm_handle)); + + printf("PCM state: %s\n", snd_pcm_state_name(snd_pcm_state(pcm_handle))); + unsigned int tmp; + snd_pcm_hw_params_get_channels(params, &tmp); + printf("channels: %i ", tmp); + if (tmp == 1) + printf("(mono)\n"); + else if (tmp == 2) + printf("(stereo)\n"); + + snd_pcm_hw_params_get_period_time(params, &tmp, NULL); + printf("period_time = %d\n", tmp); + snd_pcm_hw_params_get_period_size(params, &frames, 0); + + this->buff_size = frames * 2 * 2 /* 2 -> sample size */; + printf("required buff_size: %d\n", buff_size); + this->startTask(); +} + +ALSAAudioSink::~ALSAAudioSink() { + snd_pcm_drain(pcm_handle); + snd_pcm_close(pcm_handle); +} + +void ALSAAudioSink::runTask() { + std::unique_ptr> dataPtr; + while (true) { + if (!this->ringbuffer.pop(dataPtr)) { + usleep(100); + continue; } + if (pcm = snd_pcm_writei(pcm_handle, dataPtr->data(), this->frames) == + -EPIPE) { - /* Allocate parameters object and fill it with default values*/ - snd_pcm_hw_params_alloca(¶ms); - - snd_pcm_hw_params_any(pcm_handle, params); - - /* Set parameters */ - if (pcm = snd_pcm_hw_params_set_access(pcm_handle, params, - SND_PCM_ACCESS_RW_INTERLEAVED) < 0) - printf("ERROR: Can't set interleaved mode. %s\n", snd_strerror(pcm)); - - if (pcm = snd_pcm_hw_params_set_format(pcm_handle, params, - SND_PCM_FORMAT_S16_LE) < 0) - printf("ERROR: Can't set format. %s\n", snd_strerror(pcm)); - - if (pcm = snd_pcm_hw_params_set_channels(pcm_handle, params, 2) < 0) - printf("ERROR: Can't set channels number. %s\n", snd_strerror(pcm)); - unsigned int rate = 44100; - if (pcm = snd_pcm_hw_params_set_rate_near(pcm_handle, params, &rate, 0) < 0) - printf("ERROR: Can't set rate. %s\n", snd_strerror(pcm)); - unsigned int periodTime = 800; - int dir = -1; - snd_pcm_hw_params_set_period_time_near(pcm_handle, params, &periodTime, &dir); - /* Write parameters */ - if (pcm = snd_pcm_hw_params(pcm_handle, params) < 0) - printf("ERROR: Can't set harware parameters. %s\n", snd_strerror(pcm)); - - /* Resume information */ - printf("PCM name: '%s'\n", snd_pcm_name(pcm_handle)); - - printf("PCM state: %s\n", snd_pcm_state_name(snd_pcm_state(pcm_handle))); - unsigned int tmp; - snd_pcm_hw_params_get_channels(params, &tmp); - printf("channels: %i ", tmp); - if (tmp == 1) - printf("(mono)\n"); - else if (tmp == 2) - printf("(stereo)\n"); - - snd_pcm_hw_params_get_period_time(params, &tmp, NULL); - printf("period_time = %d\n", tmp); - snd_pcm_hw_params_get_period_size(params, &frames, 0); - - this->buff_size = frames * 2 * 2 /* 2 -> sample size */; - printf("required buff_size: %d\n", buff_size); - this->startTask(); -} - -ALSAAudioSink::~ALSAAudioSink() -{ - snd_pcm_drain(pcm_handle); - snd_pcm_close(pcm_handle); -} - -void ALSAAudioSink::runTask() -{ - std::unique_ptr> dataPtr; - while (true) - { - if (!this->ringbuffer.pop(dataPtr)) - { - usleep(100); - continue; - } - if (pcm = snd_pcm_writei(pcm_handle, dataPtr->data(), this->frames) == -EPIPE) - { - - snd_pcm_prepare(pcm_handle); - } - else if (pcm < 0) - { - printf("ERROR. Can't write to PCM device. %s\n", snd_strerror(pcm)); - } + snd_pcm_prepare(pcm_handle); + } else if (pcm < 0) { + printf("ERROR. Can't write to PCM device. %s\n", snd_strerror(pcm)); } + } } -void ALSAAudioSink::feedPCMFrames(const uint8_t *buffer, size_t bytes) -{ +void ALSAAudioSink::feedPCMFrames(const uint8_t* buffer, size_t bytes) { - buff.insert(buff.end(), buffer, buffer + bytes); - while (buff.size() > this->buff_size) - { - auto ptr = std::make_unique>(this->buff.begin(), this->buff.begin() + this->buff_size); - this->buff = std::vector(this->buff.begin() + this->buff_size, this->buff.end()); - while (!this->ringbuffer.push(ptr)) - { - usleep(100); - }; - } + buff.insert(buff.end(), buffer, buffer + bytes); + while (buff.size() > this->buff_size) { + auto ptr = std::make_unique>( + this->buff.begin(), this->buff.begin() + this->buff_size); + this->buff = std::vector(this->buff.begin() + this->buff_size, + this->buff.end()); + while (!this->ringbuffer.push(ptr)) { + usleep(100); + }; + } } diff --git a/components/spotify/cspot/bell/main/audio-sinks/unix/NamedPipeAudioSink.cpp b/components/spotify/cspot/bell/main/audio-sinks/unix/NamedPipeAudioSink.cpp index 2c310786..90bb1422 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/unix/NamedPipeAudioSink.cpp +++ b/components/spotify/cspot/bell/main/audio-sinks/unix/NamedPipeAudioSink.cpp @@ -1,21 +1,19 @@ #include "NamedPipeAudioSink.h" -NamedPipeAudioSink::NamedPipeAudioSink() -{ - printf("Start\n"); - this->namedPipeFile = std::ofstream("outputFifo", std::ios::binary); - printf("stop\n"); +#include // for printf +NamedPipeAudioSink::NamedPipeAudioSink() { + printf("Start\n"); + this->namedPipeFile = std::ofstream("outputFifo", std::ios::binary); + printf("stop\n"); } -NamedPipeAudioSink::~NamedPipeAudioSink() -{ - this->namedPipeFile.close(); +NamedPipeAudioSink::~NamedPipeAudioSink() { + this->namedPipeFile.close(); } -void NamedPipeAudioSink::feedPCMFrames(const uint8_t *buffer, size_t bytes) -{ - // Write the actual data - this->namedPipeFile.write((char*)buffer, (long)bytes); - this->namedPipeFile.flush(); +void NamedPipeAudioSink::feedPCMFrames(const uint8_t* buffer, size_t bytes) { + // Write the actual data + this->namedPipeFile.write((char*)buffer, (long)bytes); + this->namedPipeFile.flush(); } diff --git a/components/spotify/cspot/bell/main/audio-sinks/unix/PortAudioSink.cpp b/components/spotify/cspot/bell/main/audio-sinks/unix/PortAudioSink.cpp index 51ff7ed3..4fec893f 100644 --- a/components/spotify/cspot/bell/main/audio-sinks/unix/PortAudioSink.cpp +++ b/components/spotify/cspot/bell/main/audio-sinks/unix/PortAudioSink.cpp @@ -1,65 +1,57 @@ #include "PortAudioSink.h" -PortAudioSink::PortAudioSink() -{ - Pa_Initialize(); - this->setParams(44100, 2, 16); +PortAudioSink::PortAudioSink() { + Pa_Initialize(); + this->setParams(44100, 2, 16); } -bool PortAudioSink::setParams(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) { - if (stream) { - Pa_StopStream(stream); - } - PaStreamParameters outputParameters; - outputParameters.device = Pa_GetDefaultOutputDevice(); - if (outputParameters.device == paNoDevice) { - printf("PortAudio: Default audio device not found!\n"); - // exit(0); - } - printf("PortAudio: Default audio device not found!\n"); - - outputParameters.channelCount = channelCount; - switch (bitDepth) { - case 32: - outputParameters.sampleFormat = paInt32; - break; - case 24: - outputParameters.sampleFormat = paInt24; - break; - case 16: - outputParameters.sampleFormat = paInt16; - break; - case 8: - outputParameters.sampleFormat = paInt8; - break; - default: - outputParameters.sampleFormat = paInt16; - break; - } - outputParameters.suggestedLatency = 0.050; - outputParameters.hostApiSpecificStreamInfo = NULL; - - PaError err = Pa_OpenStream( - &stream, - NULL, - &outputParameters, - sampleRate, - 4096 / (channelCount * bitDepth / 8), - paClipOff, - NULL, // blocking api - NULL - ); - Pa_StartStream(stream); - return !err; -} - -PortAudioSink::~PortAudioSink() -{ +bool PortAudioSink::setParams(uint32_t sampleRate, uint8_t channelCount, + uint8_t bitDepth) { + if (stream) { Pa_StopStream(stream); - Pa_Terminate(); + } + PaStreamParameters outputParameters; + outputParameters.device = Pa_GetDefaultOutputDevice(); + if (outputParameters.device == paNoDevice) { + printf("PortAudio: Default audio device not found!\n"); + // exit(0); + } + printf("PortAudio: Default audio device not found!\n"); + + outputParameters.channelCount = channelCount; + switch (bitDepth) { + case 32: + outputParameters.sampleFormat = paInt32; + break; + case 24: + outputParameters.sampleFormat = paInt24; + break; + case 16: + outputParameters.sampleFormat = paInt16; + break; + case 8: + outputParameters.sampleFormat = paInt8; + break; + default: + outputParameters.sampleFormat = paInt16; + break; + } + outputParameters.suggestedLatency = 0.050; + outputParameters.hostApiSpecificStreamInfo = NULL; + + PaError err = Pa_OpenStream(&stream, NULL, &outputParameters, sampleRate, + 4096 / (channelCount * bitDepth / 8), paClipOff, + NULL, // blocking api + NULL); + Pa_StartStream(stream); + return !err; } -void PortAudioSink::feedPCMFrames(const uint8_t *buffer, size_t bytes) -{ - Pa_WriteStream(stream, buffer, bytes / 4); +PortAudioSink::~PortAudioSink() { + Pa_StopStream(stream); + Pa_Terminate(); +} + +void PortAudioSink::feedPCMFrames(const uint8_t* buffer, size_t bytes) { + Pa_WriteStream(stream, buffer, bytes / 4); } diff --git a/components/spotify/cspot/bell/main/io/BellHTTPServer.cpp b/components/spotify/cspot/bell/main/io/BellHTTPServer.cpp index d6c7d4eb..2b24c82d 100644 --- a/components/spotify/cspot/bell/main/io/BellHTTPServer.cpp +++ b/components/spotify/cspot/bell/main/io/BellHTTPServer.cpp @@ -1,8 +1,14 @@ #include "BellHTTPServer.h" -#include -#include -#include "CivetServer.h" -#include "civetweb.h" + +#include // for memcpy +#include // for assert +#include // for exception +#include // for scoped_lock +#include // for sregex_token_iterator, regex + +#include "BellLogger.h" // for AbstractLogger, BELL_LOG, bell +#include "CivetServer.h" // for CivetServer, CivetWebSocketHandler +#include "civetweb.h" // for mg_get_request_info, mg_printf, mg_set_user... using namespace bell; @@ -195,7 +201,8 @@ std::unique_ptr BellHTTPServer::makeJsonResponse( return response; } -std::unique_ptr BellHTTPServer::makeEmptyResponse() { +std::unique_ptr +BellHTTPServer::makeEmptyResponse() { auto response = std::make_unique(); return response; } @@ -225,8 +232,10 @@ void BellHTTPServer::registerNotFound(HTTPHandler handler) { std::unordered_map BellHTTPServer::extractParams( struct mg_connection* conn) { + void* data = mg_get_user_connection_data(conn); + assert(data != nullptr); std::unordered_map& params = - *(std::unordered_map*) - mg_get_user_connection_data(conn); + *(std::unordered_map*)data; + return params; } diff --git a/components/spotify/cspot/bell/main/io/BellTar.cpp b/components/spotify/cspot/bell/main/io/BellTar.cpp index edebfc50..61b41039 100644 --- a/components/spotify/cspot/bell/main/io/BellTar.cpp +++ b/components/spotify/cspot/bell/main/io/BellTar.cpp @@ -1,14 +1,18 @@ #include "BellTar.h" -#include -#include + +#include // for mkdir using namespace bell::BellTar; -#include -#include // for sprintf, snprintf and sscanf -#include // for rand -#include // for strlen and memset -#include // for time +#include // for min +#include // for assert +#include // for uint8_t +#include // for sprintf, size_t, sscanf, EOF, NULL +#include // for rand +#include // for memset, strlen +#include // for time +#include // for ofstream +#include // for vector #ifdef _WIN32 #include #endif @@ -59,7 +63,7 @@ void header_set_metadata(tar_header* header) { std::memset(header, 0, sizeof(tar_header)); std::sprintf(header->magic, "ustar"); - std::sprintf(header->mtime, "%011lo", (unsigned long) std::time(NULL)); + std::sprintf(header->mtime, "%011lo", (unsigned long)std::time(NULL)); std::sprintf(header->mode, "%07o", 0644); std::sprintf(header->uname, "unkown"); // ... a bit random std::sprintf(header->gname, "users"); @@ -284,7 +288,11 @@ void reader::extract_all_files(std::string dest_directory) { auto fileName = get_next_file_name(); // 0 is the normal file type, skip apple's ._ files +#if __cplusplus >= 202002L if (fileType == '0' && !fileName.starts_with("._")) { +#else + if (fileType == '0' && fileName.find("._") != 0) { +#endif std::string path = dest_directory + "/" + fileName; size_t pos = 0; diff --git a/components/spotify/cspot/bell/main/io/BinaryReader.cpp b/components/spotify/cspot/bell/main/io/BinaryReader.cpp index f7ef1f29..13aab639 100644 --- a/components/spotify/cspot/bell/main/io/BinaryReader.cpp +++ b/components/spotify/cspot/bell/main/io/BinaryReader.cpp @@ -1,72 +1,69 @@ #include "BinaryReader.h" -#include + +#include // for size_t +#include // for uint8_t +#include // for remove_extent_t + +#include "ByteStream.h" // for ByteStream bell::BinaryReader::BinaryReader(std::shared_ptr stream) { - this->stream = stream; + this->stream = stream; } size_t bell::BinaryReader::position() { - return stream->position(); + return stream->position(); } size_t bell::BinaryReader::size() { - return stream->size(); + return stream->size(); } void bell::BinaryReader::close() { - stream->close(); + stream->close(); } void bell::BinaryReader::skip(size_t pos) { - std::vector b(pos); - stream->read(&b[0], pos); + std::vector b(pos); + stream->read(&b[0], pos); } int32_t bell::BinaryReader::readInt() { - uint8_t b[4]; - if (stream->read((uint8_t *) b,4) != 4) - return 0; - - return static_cast( - (b[3]) | - (b[2] << 8) | - (b[1] << 16)| - (b[0] << 24) ); + uint8_t b[4]; + if (stream->read((uint8_t*)b, 4) != 4) + return 0; + + return static_cast((b[3]) | (b[2] << 8) | (b[1] << 16) | + (b[0] << 24)); } int16_t bell::BinaryReader::readShort() { - uint8_t b[2]; - if (stream->read((uint8_t *) b,2) != 2) - return 0; - - return static_cast( - (b[1]) | - (b[0] << 8)); + uint8_t b[2]; + if (stream->read((uint8_t*)b, 2) != 2) + return 0; + + return static_cast((b[1]) | (b[0] << 8)); } - uint32_t bell::BinaryReader::readUInt() { - return readInt() & 0xffffffffL; + return readInt() & 0xffffffffL; } uint8_t bell::BinaryReader::readByte() { - uint8_t b[1]; - if (stream->read((uint8_t *) b,1) != 1) - return 0; - return b[0]; + uint8_t b[1]; + if (stream->read((uint8_t*)b, 1) != 1) + return 0; + return b[0]; } std::vector bell::BinaryReader::readBytes(size_t size) { - std::vector data(size); - stream->read(&data[0], size); - return data; + std::vector data(size); + stream->read(&data[0], size); + return data; } long long bell::BinaryReader::readLong() { - long high = readInt(); - long low = readInt(); + long high = readInt(); + long low = readInt(); - return static_cast( - ((long long) high << 32) | low ); + return static_cast(((long long)high << 32) | low); } - diff --git a/components/spotify/cspot/bell/main/io/BinaryStream.cpp b/components/spotify/cspot/bell/main/io/BinaryStream.cpp index a5a445ee..38a7a9ff 100644 --- a/components/spotify/cspot/bell/main/io/BinaryStream.cpp +++ b/components/spotify/cspot/bell/main/io/BinaryStream.cpp @@ -1,5 +1,5 @@ #include -#include +#include // for runtime_error using namespace bell; diff --git a/components/spotify/cspot/bell/main/io/BufferedStream.cpp b/components/spotify/cspot/bell/main/io/BufferedStream.cpp index a41f2439..4af2c3dd 100644 --- a/components/spotify/cspot/bell/main/io/BufferedStream.cpp +++ b/components/spotify/cspot/bell/main/io/BufferedStream.cpp @@ -1,172 +1,182 @@ #include "BufferedStream.h" -#include -BufferedStream::BufferedStream( - const std::string &taskName, - uint32_t bufferSize, - uint32_t readThreshold, - uint32_t readSize, - uint32_t readyThreshold, - uint32_t notReadyThreshold, - bool waitForReady) - : bell::Task(taskName, 4096, 5, 0) { - this->bufferSize = bufferSize; - this->readAt = bufferSize - readThreshold; - this->readSize = readSize; - this->readyThreshold = readyThreshold; - this->notReadyThreshold = notReadyThreshold; - this->waitForReady = waitForReady; - this->buf = static_cast(malloc(bufferSize)); - this->bufEnd = buf + bufferSize; - reset(); +#include // for free, malloc +#include // for min +#include // for uint32_t +#include // for memcpy +#include // for remove_extent_t + +BufferedStream::BufferedStream(const std::string& taskName, uint32_t bufferSize, + uint32_t readThreshold, uint32_t readSize, + uint32_t readyThreshold, + uint32_t notReadyThreshold, bool waitForReady) + : bell::Task(taskName, 4096, 5, 0) { + this->bufferSize = bufferSize; + this->readAt = bufferSize - readThreshold; + this->readSize = readSize; + this->readyThreshold = readyThreshold; + this->notReadyThreshold = notReadyThreshold; + this->waitForReady = waitForReady; + this->buf = static_cast(malloc(bufferSize)); + this->bufEnd = buf + bufferSize; + reset(); } BufferedStream::~BufferedStream() { - this->close(); - free(buf); + this->close(); + free(buf); } void BufferedStream::close() { - this->terminate = true; - this->readSem.give(); // force a read operation - const std::lock_guard lock(runningMutex); - if (this->source) - this->source->close(); - this->source = nullptr; + this->terminate = true; + this->readSem.give(); // force a read operation + const std::lock_guard lock(runningMutex); + if (this->source) + this->source->close(); + this->source = nullptr; } void BufferedStream::reset() { - this->bufReadPtr = this->buf; - this->bufWritePtr = this->buf; - this->readTotal = 0; - this->bufferTotal = 0; - this->readAvailable = 0; - this->terminate = false; + this->bufReadPtr = this->buf; + this->bufWritePtr = this->buf; + this->readTotal = 0; + this->bufferTotal = 0; + this->readAvailable = 0; + this->terminate = false; } -bool BufferedStream::open(const std::shared_ptr &stream) { - if (this->running) - this->close(); - reset(); - this->source = stream; - startTask(); - return source.get(); +bool BufferedStream::open(const std::shared_ptr& stream) { + if (this->running) + this->close(); + reset(); + this->source = stream; + startTask(); + return source.get(); } -bool BufferedStream::open(const StreamReader &newReader, uint32_t initialOffset) { - if (this->running) - this->close(); - reset(); - this->reader = newReader; - this->bufferTotal = initialOffset; - startTask(); - return source.get(); +bool BufferedStream::open(const StreamReader& newReader, + uint32_t initialOffset) { + if (this->running) + this->close(); + reset(); + this->reader = newReader; + this->bufferTotal = initialOffset; + startTask(); + return source.get(); } bool BufferedStream::isReady() const { - return readAvailable >= readyThreshold; + return readAvailable >= readyThreshold; } bool BufferedStream::isNotReady() const { - return readAvailable < notReadyThreshold; + return readAvailable < notReadyThreshold; } size_t BufferedStream::skip(size_t len) { - return read(nullptr, len); + return read(nullptr, len); } size_t BufferedStream::position() { - return readTotal; + return readTotal; } size_t BufferedStream::size() { - return source->size(); + return source->size(); } -uint32_t BufferedStream::lengthBetween(uint8_t *me, uint8_t *other) { - const std::lock_guard lock(readMutex); - if (other <= me) { - // buf .... other ...... me ........ bufEnd - // buf .... me/other ........ bufEnd - return bufEnd - me; - } else { - // buf ........ me ........ other .... bufEnd - return other - me; - } +uint32_t BufferedStream::lengthBetween(uint8_t* me, uint8_t* other) { + const std::lock_guard lock(readMutex); + if (other <= me) { + // buf .... other ...... me ........ bufEnd + // buf .... me/other ........ bufEnd + return bufEnd - me; + } else { + // buf ........ me ........ other .... bufEnd + return other - me; + } } -size_t BufferedStream::read(uint8_t *dst, size_t len) { - if (waitForReady && isNotReady()) { - while ((source || reader) && !isReady()) {} // end waiting after termination - } - if (!running && !readAvailable) { - reset(); - return 0; - } - uint32_t read = 0; - uint32_t toReadTotal = std::min(readAvailable.load(), static_cast(len)); - while (toReadTotal > 0) { - uint32_t toRead = std::min(toReadTotal, lengthBetween(bufReadPtr, bufWritePtr)); - if (dst) { - memcpy(dst, bufReadPtr, toRead); - dst += toRead; - } - readAvailable -= toRead; - bufReadPtr += toRead; - if (bufReadPtr >= bufEnd) - bufReadPtr = buf; - toReadTotal -= toRead; - read += toRead; - readTotal += toRead; - } - this->readSem.give(); - return read; +size_t BufferedStream::read(uint8_t* dst, size_t len) { + if (waitForReady && isNotReady()) { + while ((source || reader) && !isReady()) { + } // end waiting after termination + } + if (!running && !readAvailable) { + reset(); + return 0; + } + uint32_t read = 0; + uint32_t toReadTotal = + std::min(readAvailable.load(), static_cast(len)); + while (toReadTotal > 0) { + uint32_t toRead = + std::min(toReadTotal, lengthBetween(bufReadPtr, bufWritePtr)); + if (dst) { + memcpy(dst, bufReadPtr, toRead); + dst += toRead; + } + readAvailable -= toRead; + bufReadPtr += toRead; + if (bufReadPtr >= bufEnd) + bufReadPtr = buf; + toReadTotal -= toRead; + read += toRead; + readTotal += toRead; + } + this->readSem.give(); + return read; } void BufferedStream::runTask() { - const std::lock_guard lock(runningMutex); - running = true; - if (!source && reader) { - // get the initial request on the task's thread - source = reader(this->bufferTotal); - } - while (!terminate) { - if (!source) - break; - if (isReady()) { - // buffer ready, wait for any read operations - this->readSem.wait(); - } - if (terminate) - break; - if (readAvailable > readAt) - continue; - // here, the buffer needs re-filling - uint32_t len; - bool wasReady = isReady(); - do { - uint32_t toRead = std::min(readSize, lengthBetween(bufWritePtr, bufReadPtr)); - if (!source) { - len = 0; - break; - } - len = source->read(bufWritePtr, toRead); - readAvailable += len; - bufferTotal += len; - bufWritePtr += len; - if (bufWritePtr >= bufEnd) // TODO is == enough here? - bufWritePtr = buf; - } while (len && readSize < bufferSize - readAvailable); // loop until there's no more free space in the buffer - if (!len && reader) - source = reader(bufferTotal); - else if (!len) - terminate = true; - // signal that buffer is ready for reading - if (!wasReady && isReady()) { - this->readySem.give(); - } - } - source = nullptr; - reader = nullptr; - running = false; + const std::lock_guard lock(runningMutex); + running = true; + if (!source && reader) { + // get the initial request on the task's thread + source = reader(this->bufferTotal); + } + while (!terminate) { + if (!source) + break; + if (isReady()) { + // buffer ready, wait for any read operations + this->readSem.wait(); + } + if (terminate) + break; + if (readAvailable > readAt) + continue; + // here, the buffer needs re-filling + uint32_t len; + bool wasReady = isReady(); + do { + uint32_t toRead = + std::min(readSize, lengthBetween(bufWritePtr, bufReadPtr)); + if (!source) { + len = 0; + break; + } + len = source->read(bufWritePtr, toRead); + readAvailable += len; + bufferTotal += len; + bufWritePtr += len; + if (bufWritePtr >= bufEnd) // TODO is == enough here? + bufWritePtr = buf; + } while ( + len && + readSize < + bufferSize - + readAvailable); // loop until there's no more free space in the buffer + if (!len && reader) + source = reader(bufferTotal); + else if (!len) + terminate = true; + // signal that buffer is ready for reading + if (!wasReady && isReady()) { + this->readySem.give(); + } + } + source = nullptr; + reader = nullptr; + running = false; } diff --git a/components/spotify/cspot/bell/main/io/CircularBuffer.cpp b/components/spotify/cspot/bell/main/io/CircularBuffer.cpp index 700bf0e4..2e5eff2b 100644 --- a/components/spotify/cspot/bell/main/io/CircularBuffer.cpp +++ b/components/spotify/cspot/bell/main/io/CircularBuffer.cpp @@ -1,89 +1,85 @@ #include "CircularBuffer.h" +#include // for min + using namespace bell; -CircularBuffer::CircularBuffer(size_t dataCapacity) -{ - this->dataCapacity = dataCapacity; - buffer = std::vector(dataCapacity); - this->dataSemaphore = std::make_unique(5); +CircularBuffer::CircularBuffer(size_t dataCapacity) { + this->dataCapacity = dataCapacity; + buffer = std::vector(dataCapacity); + this->dataSemaphore = std::make_unique(5); }; -size_t CircularBuffer::write(const uint8_t *data, size_t bytes) -{ - if (bytes == 0) - return 0; +size_t CircularBuffer::write(const uint8_t* data, size_t bytes) { + if (bytes == 0) + return 0; - std::lock_guard guard(bufferMutex); - size_t bytesToWrite = std::min(bytes, dataCapacity - dataSize); - // Write in a single step - if (bytesToWrite <= dataCapacity - endIndex) - { - memcpy(buffer.data() + endIndex, data, bytesToWrite); - endIndex += bytesToWrite; - if (endIndex == dataCapacity) - endIndex = 0; - } + std::lock_guard guard(bufferMutex); + size_t bytesToWrite = std::min(bytes, dataCapacity - dataSize); + // Write in a single step + if (bytesToWrite <= dataCapacity - endIndex) { + memcpy(buffer.data() + endIndex, data, bytesToWrite); + endIndex += bytesToWrite; + if (endIndex == dataCapacity) + endIndex = 0; + } - // Write in two steps - else { - size_t firstChunkSize = dataCapacity - endIndex; - memcpy(buffer.data() + endIndex, data, firstChunkSize); - size_t secondChunkSize = bytesToWrite - firstChunkSize; - memcpy(buffer.data(), data + firstChunkSize, secondChunkSize); - endIndex = secondChunkSize; - } + // Write in two steps + else { + size_t firstChunkSize = dataCapacity - endIndex; + memcpy(buffer.data() + endIndex, data, firstChunkSize); + size_t secondChunkSize = bytesToWrite - firstChunkSize; + memcpy(buffer.data(), data + firstChunkSize, secondChunkSize); + endIndex = secondChunkSize; + } - dataSize += bytesToWrite; + dataSize += bytesToWrite; - // this->dataSemaphore->give(); - return bytesToWrite; + // this->dataSemaphore->give(); + return bytesToWrite; } void CircularBuffer::emptyBuffer() { - std::lock_guard guard(bufferMutex); - begIndex = 0; - dataSize = 0; - endIndex = 0; + std::lock_guard guard(bufferMutex); + begIndex = 0; + dataSize = 0; + endIndex = 0; } void CircularBuffer::emptyExcept(size_t sizeToSet) { - std::lock_guard guard(bufferMutex); - if (sizeToSet > dataSize) - sizeToSet = dataSize; - dataSize = sizeToSet; - endIndex = begIndex + sizeToSet; - if (endIndex > dataCapacity) { - endIndex -= dataCapacity; - } + std::lock_guard guard(bufferMutex); + if (sizeToSet > dataSize) + sizeToSet = dataSize; + dataSize = sizeToSet; + endIndex = begIndex + sizeToSet; + if (endIndex > dataCapacity) { + endIndex -= dataCapacity; + } } -size_t CircularBuffer::read(uint8_t *data, size_t bytes) -{ - if (bytes == 0) - return 0; +size_t CircularBuffer::read(uint8_t* data, size_t bytes) { + if (bytes == 0) + return 0; - std::lock_guard guard(bufferMutex); - size_t bytesToRead = std::min(bytes, dataSize); + std::lock_guard guard(bufferMutex); + size_t bytesToRead = std::min(bytes, dataSize); - // Read in a single step - if (bytesToRead <= dataCapacity - begIndex) - { - memcpy(data, buffer.data() + begIndex, bytesToRead); - begIndex += bytesToRead; - if (begIndex == dataCapacity) - begIndex = 0; - } - // Read in two steps - else - { - size_t firstChunkSize = dataCapacity - begIndex; - memcpy(data, buffer.data() + begIndex, firstChunkSize); - size_t secondChunkSize = bytesToRead - firstChunkSize; - memcpy(data + firstChunkSize, buffer.data(), secondChunkSize); - begIndex = secondChunkSize; - } + // Read in a single step + if (bytesToRead <= dataCapacity - begIndex) { + memcpy(data, buffer.data() + begIndex, bytesToRead); + begIndex += bytesToRead; + if (begIndex == dataCapacity) + begIndex = 0; + } + // Read in two steps + else { + size_t firstChunkSize = dataCapacity - begIndex; + memcpy(data, buffer.data() + begIndex, firstChunkSize); + size_t secondChunkSize = bytesToRead - firstChunkSize; + memcpy(data + firstChunkSize, buffer.data(), secondChunkSize); + begIndex = secondChunkSize; + } - dataSize -= bytesToRead; - return bytesToRead; + dataSize -= bytesToRead; + return bytesToRead; } diff --git a/components/spotify/cspot/bell/main/io/EncodedAudioStream.cpp b/components/spotify/cspot/bell/main/io/EncodedAudioStream.cpp index d8e2e99d..6d80eb8e 100644 --- a/components/spotify/cspot/bell/main/io/EncodedAudioStream.cpp +++ b/components/spotify/cspot/bell/main/io/EncodedAudioStream.cpp @@ -1,6 +1,14 @@ #include "EncodedAudioStream.h" -#include +#include // for memcpy, memmove +#include // for runtime_error +#include // for remove_extent_t +#include // for move + +#include "BellLogger.h" // for AbstractLogger, BELL_LOG, bell +#include "ByteStream.h" // for ByteStream +#include "DecoderGlobals.h" // for DecodersInstance, decodersInstance, AAC_... + using namespace bell; EncodedAudioStream::EncodedAudioStream() { @@ -171,5 +179,4 @@ void EncodedAudioStream::guessDataFormat() { } } -void EncodedAudioStream::readFully(uint8_t* dst, size_t nbytes) { -} +void EncodedAudioStream::readFully(uint8_t* dst, size_t nbytes) {} diff --git a/components/spotify/cspot/bell/main/io/FileStream.cpp b/components/spotify/cspot/bell/main/io/FileStream.cpp index 02db8956..e1d3aaa2 100644 --- a/components/spotify/cspot/bell/main/io/FileStream.cpp +++ b/components/spotify/cspot/bell/main/io/FileStream.cpp @@ -1,70 +1,61 @@ #include "FileStream.h" +#include // for runtime_error + +#include "BellLogger.h" // for bell + using namespace bell; -FileStream::FileStream(const std::string& path, std::string read) -{ - file = fopen(path.c_str(), "rb"); - if (file == NULL) - { - throw std::runtime_error("Could not open file: " + path); - } +FileStream::FileStream(const std::string& path, std::string read) { + file = fopen(path.c_str(), "rb"); + if (file == NULL) { + throw std::runtime_error("Could not open file: " + path); + } } -FileStream::~FileStream() -{ - close(); +FileStream::~FileStream() { + close(); } -size_t FileStream::read(uint8_t *buf, size_t nbytes) -{ - if (file == NULL) - { - throw std::runtime_error("Stream is closed"); - } +size_t FileStream::read(uint8_t* buf, size_t nbytes) { + if (file == NULL) { + throw std::runtime_error("Stream is closed"); + } - return fread(buf, 1, nbytes, file); + return fread(buf, 1, nbytes, file); } -size_t FileStream::skip(size_t nbytes) -{ - if (file == NULL) - { - throw std::runtime_error("Stream is closed"); - } +size_t FileStream::skip(size_t nbytes) { + if (file == NULL) { + throw std::runtime_error("Stream is closed"); + } - return fseek(file, nbytes, SEEK_CUR); + return fseek(file, nbytes, SEEK_CUR); } -size_t FileStream::position() -{ - if (file == NULL) - { - throw std::runtime_error("Stream is closed"); - } +size_t FileStream::position() { + if (file == NULL) { + throw std::runtime_error("Stream is closed"); + } - return ftell(file); + return ftell(file); } -size_t FileStream::size() -{ - if (file == NULL) - { - throw std::runtime_error("Stream is closed"); - } +size_t FileStream::size() { + if (file == NULL) { + throw std::runtime_error("Stream is closed"); + } - size_t pos = ftell(file); - fseek(file, 0, SEEK_END); - size_t size = ftell(file); - fseek(file, pos, SEEK_SET); - return size; + size_t pos = ftell(file); + fseek(file, 0, SEEK_END); + size_t size = ftell(file); + fseek(file, pos, SEEK_SET); + return size; } -void FileStream::close() -{ - if (file != NULL) - { - fclose(file); - file = NULL; - } +void FileStream::close() { + if (file != NULL) { + fclose(file); + file = NULL; + } } \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/io/HTTPClient.cpp b/components/spotify/cspot/bell/main/io/HTTPClient.cpp index b78b46cd..f91ad907 100644 --- a/components/spotify/cspot/bell/main/io/HTTPClient.cpp +++ b/components/spotify/cspot/bell/main/io/HTTPClient.cpp @@ -1,5 +1,14 @@ #include "HTTPClient.h" +#include // for memcpy +#include // for transform +#include // for assert +#include // for tolower +#include // for operator<<, basic_ostream +#include // for runtime_error + +#include "BellSocket.h" // for bell + using namespace bell; void HTTPClient::Response::connect(const std::string& url) { diff --git a/components/spotify/cspot/bell/main/io/SocketStream.cpp b/components/spotify/cspot/bell/main/io/SocketStream.cpp index 2a3bc5a6..2b2b2cab 100644 --- a/components/spotify/cspot/bell/main/io/SocketStream.cpp +++ b/components/spotify/cspot/bell/main/io/SocketStream.cpp @@ -1,9 +1,17 @@ #include "SocketStream.h" +#include // for uint8_t +#include // for NULL, ssize_t + +#include "TCPSocket.h" // for TCPSocket +#include "TLSSocket.h" // for TLSSocket + using namespace bell; int SocketBuffer::open(const std::string& hostname, int port, bool isSSL) { - if (internalSocket != nullptr) { close(); } + if (internalSocket != nullptr) { + close(); + } if (isSSL) { internalSocket = std::make_unique(); } else { diff --git a/components/spotify/cspot/bell/main/io/TLSSocket.cpp b/components/spotify/cspot/bell/main/io/TLSSocket.cpp index 96b5e823..489748d3 100644 --- a/components/spotify/cspot/bell/main/io/TLSSocket.cpp +++ b/components/spotify/cspot/bell/main/io/TLSSocket.cpp @@ -1,5 +1,14 @@ #include "TLSSocket.h" -#include "X509Bundle.h" + +#include // for mbedtls_ctr_drbg_free, mbedtls_ctr_... +#include // for mbedtls_entropy_free, mbedtls_entro... +#include // for mbedtls_net_connect, mbedtls_net_free +#include // for mbedtls_ssl_conf_authmode, mbedtls_... +#include // for strlen, NULL +#include // for runtime_error + +#include "BellLogger.h" // for AbstractLogger, BELL_LOG +#include "X509Bundle.h" // for shouldVerify, attach /** * Platform TLSSocket implementation for the mbedtls diff --git a/components/spotify/cspot/bell/main/io/URLParser.cpp b/components/spotify/cspot/bell/main/io/URLParser.cpp index 55fd21c5..4a50ab1e 100644 --- a/components/spotify/cspot/bell/main/io/URLParser.cpp +++ b/components/spotify/cspot/bell/main/io/URLParser.cpp @@ -4,40 +4,50 @@ namespace bell { #ifdef BELL_DISABLE_REGEX void URLParser::parse(const char* url, std::vector& match) { - match[0] = url; - char scratch[512]; + match[0] = url; + char scratch[512]; - /* Parsing the following (http|https://[host][/path][?query]#hash] as in regex + /* Parsing the following (http|https://[host][/path][?query]#hash] as in regex * below. This needs to be changed if you update that regex */ - - // get the schema - if (sscanf(url, "%[^:]:/", scratch) > 0) match[1] = scratch; - - // get the host - if (sscanf(url, "htt%*[^:]://%512[^/#?]", scratch) > 0) match[2] = scratch; - // get the path - url = strstr(url, match[2].c_str()) + match[2].size(); - if (sscanf(url, "/%512[^?]", scratch) > 0) match[3] = scratch; - else if (*url && *url != '?' && *url != '#') url++; - - // get the query - if (match[3].size()) url += match[3].size() + 1; - if (sscanf(url, "?%512[^#]", scratch) > 0) match[4] = scratch; + // get the schema + if (sscanf(url, "%[^:]:/", scratch) > 0) + match[1] = scratch; - // get the hash - if (match[4].size()) url += match[4].size() + 1; - if (sscanf(url, "#%512s", scratch) > 0) match[5] = scratch; + // get the host + if (sscanf(url, "htt%*[^:]://%512[^/#?]", scratch) > 0) + match[2] = scratch; - // fix the acquired items - match[3] = "/" + match[3]; - if (match[4].size()) match[4] = "?" + match[4]; + // get the path + url = strstr(url, match[2].c_str()) + match[2].size(); + if (sscanf(url, "/%512[^?]", scratch) > 0) + match[3] = scratch; + else if (*url && *url != '?' && *url != '#') + url++; - // need at least schema and host - if (match[1].size() == 0 || match[2].size() == 0) match.clear(); -} -#else + // get the query + if (match[3].size()) + url += match[3].size() + 1; + if (sscanf(url, "?%512[^#]", scratch) > 0) + match[4] = scratch; + + // get the hash + if (match[4].size()) + url += match[4].size() + 1; + if (sscanf(url, "#%512s", scratch) > 0) + match[5] = scratch; + + // fix the acquired items + match[3] = "/" + match[3]; + if (match[4].size()) + match[4] = "?" + match[4]; + + // need at least schema and host + if (match[1].size() == 0 || match[2].size() == 0) + match.clear(); +} +#else const std::regex URLParser::urlParseRegex = std::regex( - "^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(\\?(?:[^#]*))?(#(?:.*))?"); + "^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(\\?(?:[^#]*))?(#(?:.*))?"); #endif -} +} // namespace bell diff --git a/components/spotify/cspot/bell/main/io/X509Bundle.cpp b/components/spotify/cspot/bell/main/io/X509Bundle.cpp index 8b7b757b..ffcdd134 100644 --- a/components/spotify/cspot/bell/main/io/X509Bundle.cpp +++ b/components/spotify/cspot/bell/main/io/X509Bundle.cpp @@ -1,5 +1,15 @@ #include "X509Bundle.h" +#include // for mbedtls_md, mbedtls_md_get_size +#include // for mbedtls_pk_can_do, mbedtls_pk_pa... +#include // for mbedtls_ssl_conf_ca_chain, mbedt... +#include // for mbedtls_x509_buf, MBEDTLS_ERR_X5... +#include // for free, calloc +#include // for memcmp, memcpy +#include // for runtime_error + +#include "BellLogger.h" // for AbstractLogger, BELL_LOG + using namespace bell::X509Bundle; static mbedtls_x509_crt s_dummy_crt; @@ -21,7 +31,8 @@ int bell::X509Bundle::crtCheckCertificate(mbedtls_x509_crt* child, if ((ret = mbedtls_pk_parse_public_key(&parent.pk, pub_key_buf, pub_key_len)) != 0) { - BELL_LOG(error, TAG, "PK parse failed with error %X", ret); + BELL_LOG(error, TAG, "PK parse failed with error 0x%04x, key len = %d", ret, + pub_key_len); goto cleanup; } @@ -110,6 +121,8 @@ int bell::X509Bundle::crtVerifyCallback(void* buf, mbedtls_x509_crt* crt, ret = crtCheckCertificate( child, s_crt_bundle.crts[middle] + CRT_HEADER_OFFSET + name_len, key_len); + } else { + BELL_LOG(error, TAG, "Certificate not found in bundle"); } if (ret == 0) { @@ -138,10 +151,13 @@ void bell::X509Bundle::init(const uint8_t* x509_bundle, size_t bundle_size) { throw std::runtime_error("Unable to allocate memory for bundle"); } + bundleBytes.resize(bundle_size); + memcpy(bundleBytes.data(), x509_bundle, bundle_size); + const uint8_t* cur_crt; /* This is the maximum region that is allowed to access */ - const uint8_t* bundle_end = x509_bundle + bundle_size; - cur_crt = x509_bundle + BUNDLE_HEADER_OFFSET; + const uint8_t* bundle_end = bundleBytes.data() + bundle_size; + cur_crt = bundleBytes.data() + BUNDLE_HEADER_OFFSET; for (int i = 0; i < num_certs; i++) { crts[i] = cur_crt; diff --git a/components/spotify/cspot/bell/main/io/include/BellHTTPServer.h b/components/spotify/cspot/bell/main/io/include/BellHTTPServer.h index fc58dd96..c65a3587 100644 --- a/components/spotify/cspot/bell/main/io/include/BellHTTPServer.h +++ b/components/spotify/cspot/bell/main/io/include/BellHTTPServer.h @@ -1,22 +1,18 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "CivetServer.h" -#include "civetweb.h" +#include // for bell +#include // for uint8_t +#include // for free, size_t +#include // for function +#include // for map +#include // for unique_ptr +#include // for mutex +#include // for string, hash, operator==, operator< +#include // for unordered_map +#include // for pair +#include // for vector + +#include "CivetServer.h" // for CivetServer, CivetHandler using namespace bell; namespace bell { @@ -46,7 +42,9 @@ class BellHTTPServer : public CivetHandler { } } }; - typedef std::function(struct mg_connection* conn)> HTTPHandler; + typedef std::function( + struct mg_connection* conn)> + HTTPHandler; typedef std::function WSStateHandler; typedef std::function @@ -79,7 +77,8 @@ class BellHTTPServer : public CivetHandler { std::vector getListeningPorts() { return server->getListeningPorts(); }; void close() { server->close(); } - std::unique_ptr makeJsonResponse(const std::string& json, int status = 200); + std::unique_ptr makeJsonResponse(const std::string& json, + int status = 200); std::unique_ptr makeEmptyResponse(); void registerNotFound(HTTPHandler handler); @@ -88,7 +87,8 @@ class BellHTTPServer : public CivetHandler { void registerWS(const std::string&, WSDataHandler dataHandler, WSStateHandler stateHandler); - static std::unordered_map extractParams(struct mg_connection* conn); + static std::unordered_map extractParams( + struct mg_connection* conn); private: std::unique_ptr server; diff --git a/components/spotify/cspot/bell/main/io/include/BellSocket.h b/components/spotify/cspot/bell/main/io/include/BellSocket.h index 5c887377..5eff94eb 100644 --- a/components/spotify/cspot/bell/main/io/include/BellSocket.h +++ b/components/spotify/cspot/bell/main/io/include/BellSocket.h @@ -14,6 +14,6 @@ class Socket { virtual size_t read(uint8_t* buf, size_t len) = 0; virtual bool isOpen() = 0; virtual void close() = 0; + virtual int getFd() = 0; }; } // namespace bell - diff --git a/components/spotify/cspot/bell/main/io/include/BellTar.h b/components/spotify/cspot/bell/main/io/include/BellTar.h index 1f4af0d4..ef1b7e6a 100644 --- a/components/spotify/cspot/bell/main/io/include/BellTar.h +++ b/components/spotify/cspot/bell/main/io/include/BellTar.h @@ -1,8 +1,7 @@ #pragma once -#include -#include -#include +#include // for istream, ostream +#include // for string namespace bell::BellTar { typedef long long unsigned file_size_t; diff --git a/components/spotify/cspot/bell/main/io/include/BinaryReader.h b/components/spotify/cspot/bell/main/io/include/BinaryReader.h index dd0a4f9a..25befc9a 100644 --- a/components/spotify/cspot/bell/main/io/include/BinaryReader.h +++ b/components/spotify/cspot/bell/main/io/include/BinaryReader.h @@ -1,31 +1,28 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include "ByteStream.h" +#include // for uint8_t, int16_t, int32_t, uint32_t +#include // for size_t +#include // for shared_ptr +#include // for vector -namespace bell -{ - class BinaryReader - { - std::shared_ptr stream; - size_t currentPos = 0; +namespace bell { +class ByteStream; - public: - BinaryReader(std::shared_ptr stream); - int32_t readInt(); - int16_t readShort(); - uint32_t readUInt(); - long long readLong(); - void close(); - uint8_t readByte(); - size_t size(); - size_t position(); - std::vector readBytes(size_t); - void skip(size_t); - }; -} \ No newline at end of file +class BinaryReader { + std::shared_ptr stream; + size_t currentPos = 0; + + public: + BinaryReader(std::shared_ptr stream); + int32_t readInt(); + int16_t readShort(); + uint32_t readUInt(); + long long readLong(); + void close(); + uint8_t readByte(); + size_t size(); + size_t position(); + std::vector readBytes(size_t); + void skip(size_t); +}; +} // namespace bell \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/io/include/BinaryStream.h b/components/spotify/cspot/bell/main/io/include/BinaryStream.h index 1da7677a..78cdd66a 100644 --- a/components/spotify/cspot/bell/main/io/include/BinaryStream.h +++ b/components/spotify/cspot/bell/main/io/include/BinaryStream.h @@ -1,10 +1,11 @@ #pragma once #ifndef ESP_PLATFORM -#include +#include // for endian #endif -#include -#include +#include // for int16_t, int32_t, int64_t, uint16_t, uint32_t +#include // for byte +#include // for istream, ostream namespace bell { class BinaryStream { diff --git a/components/spotify/cspot/bell/main/io/include/BufferedStream.h b/components/spotify/cspot/bell/main/io/include/BufferedStream.h index 94041062..2acce8f8 100644 --- a/components/spotify/cspot/bell/main/io/include/BufferedStream.h +++ b/components/spotify/cspot/bell/main/io/include/BufferedStream.h @@ -1,12 +1,16 @@ #pragma once -#include "ByteStream.h" -#include "BellTask.h" -#include "WrappedSemaphore.h" -#include -#include -#include -#include +#include // for size_t +#include // for uint32_t, uint8_t +#include // for atomic +#include // for function +#include // for shared_ptr +#include // for mutex +#include // for string + +#include "BellTask.h" // for Task +#include "ByteStream.h" // for ByteStream +#include "WrappedSemaphore.h" // for WrappedSemaphore /** * This class implements a wrapper around an arbitrary bell::ByteStream, @@ -26,12 +30,12 @@ * method correctly, such as that 0 is returned if, and only if the stream ends. */ class BufferedStream : public bell::ByteStream, bell::Task { - public: - typedef std::shared_ptr StreamPtr; - typedef std::function StreamReader; + public: + typedef std::shared_ptr StreamPtr; + typedef std::function StreamReader; - public: - /** + public: + /** * @param taskName name to use for the reading task * @param bufferSize total size of the reading buffer * @param readThreshold how much can be read before refilling the buffer @@ -41,22 +45,18 @@ class BufferedStream : public bell::ByteStream, bell::Task { * @param waitForReady whether to wait for the buffer to be ready during reading * @param endWithSource whether to end the streaming as soon as source returns 0 from read() */ - BufferedStream( - const std::string &taskName, - uint32_t bufferSize, - uint32_t readThreshold, - uint32_t readSize, - uint32_t readyThreshold, - uint32_t notReadyThreshold, - bool waitForReady = false); - ~BufferedStream() override; - bool open(const StreamPtr &stream); - bool open(const StreamReader &newReader, uint32_t initialOffset = 0); - void close() override; + BufferedStream(const std::string& taskName, uint32_t bufferSize, + uint32_t readThreshold, uint32_t readSize, + uint32_t readyThreshold, uint32_t notReadyThreshold, + bool waitForReady = false); + ~BufferedStream() override; + bool open(const StreamPtr& stream); + bool open(const StreamReader& newReader, uint32_t initialOffset = 0); + void close() override; - // inherited methods - public: - /** + // inherited methods + public: + /** * Read len bytes from the buffer to dst. If waitForReady is enabled * and readAvailable is lower than notReadyThreshold, the function * will block until readyThreshold bytes is available. @@ -65,61 +65,63 @@ class BufferedStream : public bell::ByteStream, bell::Task { * if the buffer does not contain len bytes available), or 0 if the source * stream is already closed and there is no reader attached. */ - size_t read(uint8_t *dst, size_t len) override; - size_t skip(size_t len) override; - size_t position() override; - size_t size() override; + size_t read(uint8_t* dst, size_t len) override; + size_t skip(size_t len) override; + size_t position() override; + size_t size() override; - // stream status - public: - /** + // stream status + public: + /** * Total amount of bytes served to read(). */ - uint32_t readTotal; - /** + uint32_t readTotal; + /** * Total amount of bytes read from source. */ - uint32_t bufferTotal; - /** + uint32_t bufferTotal; + /** * Amount of bytes available to read from the buffer. */ - std::atomic readAvailable; - /** + std::atomic readAvailable; + /** * Whether the caller should start reading the data. This indicates that a safe * amount (determined by readyThreshold) of data is available in the buffer. */ - bool isReady() const; - /** + bool isReady() const; + /** * Whether the caller should stop reading the data. This indicates that the amount of data * available for reading is decreasing to a non-safe value, as data is being read * faster than it can be buffered. */ - bool isNotReady() const; - /** + bool isNotReady() const; + /** * Semaphore that is given when the buffer becomes ready (isReady() == true). Caller can * wait for the semaphore instead of continuously querying isReady(). */ - bell::WrappedSemaphore readySem; + bell::WrappedSemaphore readySem; - private: - std::mutex runningMutex; - bool running = false; - bool terminate = false; - bell::WrappedSemaphore readSem; // signal to start writing to buffer after reading from it - std::mutex readMutex; // mutex for locking read operations during writing, and vice versa - uint32_t bufferSize; - uint32_t readAt; - uint32_t readSize; - uint32_t readyThreshold; - uint32_t notReadyThreshold; - bool waitForReady; - uint8_t *buf; - uint8_t *bufEnd; - uint8_t *bufReadPtr; - uint8_t *bufWritePtr; - StreamPtr source; - StreamReader reader; - void runTask() override; - void reset(); - uint32_t lengthBetween(uint8_t *me, uint8_t *other); + private: + std::mutex runningMutex; + bool running = false; + bool terminate = false; + bell::WrappedSemaphore + readSem; // signal to start writing to buffer after reading from it + std::mutex + readMutex; // mutex for locking read operations during writing, and vice versa + uint32_t bufferSize; + uint32_t readAt; + uint32_t readSize; + uint32_t readyThreshold; + uint32_t notReadyThreshold; + bool waitForReady; + uint8_t* buf; + uint8_t* bufEnd; + uint8_t* bufReadPtr; + uint8_t* bufWritePtr; + StreamPtr source; + StreamReader reader; + void runTask() override; + void reset(); + uint32_t lengthBetween(uint8_t* me, uint8_t* other); }; diff --git a/components/spotify/cspot/bell/main/io/include/ByteStream.h b/components/spotify/cspot/bell/main/io/include/ByteStream.h index 21b09420..aad1ebf1 100644 --- a/components/spotify/cspot/bell/main/io/include/ByteStream.h +++ b/components/spotify/cspot/bell/main/io/include/ByteStream.h @@ -1,27 +1,25 @@ #ifndef BELL_BYTE_READER_H #define BELL_BYTE_READER_H -#include #include +#include /** * A class for reading bytes from a stream. Further implemented in HTTPStream.h */ -namespace bell -{ - class ByteStream - { - public: - ByteStream(){}; - virtual ~ByteStream() = default; +namespace bell { +class ByteStream { + public: + ByteStream(){}; + virtual ~ByteStream() = default; - virtual size_t read(uint8_t *buf, size_t nbytes) = 0; - virtual size_t skip(size_t nbytes) = 0; + virtual size_t read(uint8_t* buf, size_t nbytes) = 0; + virtual size_t skip(size_t nbytes) = 0; - virtual size_t position() = 0; - virtual size_t size() = 0; - virtual void close() = 0; - }; -} + virtual size_t position() = 0; + virtual size_t size() = 0; + virtual void close() = 0; +}; +} // namespace bell #endif diff --git a/components/spotify/cspot/bell/main/io/include/CircularBuffer.h b/components/spotify/cspot/bell/main/io/include/CircularBuffer.h index df4e4a17..08a66b05 100644 --- a/components/spotify/cspot/bell/main/io/include/CircularBuffer.h +++ b/components/spotify/cspot/bell/main/io/include/CircularBuffer.h @@ -1,39 +1,35 @@ #pragma once -#include -#include -#include -#include -#include -#include "WrappedSemaphore.h" +#include // for uint8_t +#include // for size_t +#include // for unique_ptr +#include // for mutex +#include // for vector +#include "WrappedSemaphore.h" // for WrappedSemaphore namespace bell { class CircularBuffer { - public: - CircularBuffer(size_t dataCapacity); + public: + CircularBuffer(size_t dataCapacity); - std::unique_ptr dataSemaphore; + std::unique_ptr dataSemaphore; - size_t size() const { - return dataSize; - } + size_t size() const { return dataSize; } - size_t capacity() const { - return dataCapacity; - } + size_t capacity() const { return dataCapacity; } - size_t write(const uint8_t *data, size_t bytes); - size_t read(uint8_t *data, size_t bytes); - void emptyBuffer(); - void emptyExcept(size_t size); + size_t write(const uint8_t* data, size_t bytes); + size_t read(uint8_t* data, size_t bytes); + void emptyBuffer(); + void emptyExcept(size_t size); - private: - std::mutex bufferMutex; - size_t begIndex = 0; - size_t endIndex = 0; - size_t dataSize = 0; - size_t dataCapacity = 0; - std::vector buffer; + private: + std::mutex bufferMutex; + size_t begIndex = 0; + size_t endIndex = 0; + size_t dataSize = 0; + size_t dataCapacity = 0; + std::vector buffer; }; -} // namespace bell \ No newline at end of file +} // namespace bell \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/io/include/EncodedAudioStream.h b/components/spotify/cspot/bell/main/io/include/EncodedAudioStream.h index 2938f9d4..ce8a682e 100644 --- a/components/spotify/cspot/bell/main/io/include/EncodedAudioStream.h +++ b/components/spotify/cspot/bell/main/io/include/EncodedAudioStream.h @@ -1,15 +1,17 @@ #pragma once -#include -#include +#include // for size_t +#include // for uint8_t +#include // for shared_ptr, unique_ptr +#include // for basic_string, string +#include // for vector -#include "BellLogger.h" -#include "ByteStream.h" -#include "DecoderGlobals.h" -#include "aacdec.h" -#include "mp3dec.h" +#include "aacdec.h" // for AACFrameInfo +#include "mp3dec.h" // for MP3FrameInfo namespace bell { +class ByteStream; + class EncodedAudioStream { public: EncodedAudioStream(); diff --git a/components/spotify/cspot/bell/main/io/include/FileStream.h b/components/spotify/cspot/bell/main/io/include/FileStream.h index 4456affd..ae3ae514 100644 --- a/components/spotify/cspot/bell/main/io/include/FileStream.h +++ b/components/spotify/cspot/bell/main/io/include/FileStream.h @@ -1,10 +1,9 @@ #pragma once -#include -#include -#include -#include -#include +#include // for ByteStream +#include // for uint8_t +#include // for size_t, FILE +#include // for string /* * FileStream @@ -12,17 +11,15 @@ * A class for reading and writing to files implementing the ByteStream interface. * */ -namespace bell -{ - class FileStream : public ByteStream - { - public: - FileStream(const std::string& path, std::string mode); - ~FileStream(); +namespace bell { +class FileStream : public ByteStream { + public: + FileStream(const std::string& path, std::string mode); + ~FileStream(); - FILE* file; + FILE* file; - /* + /* * Reads data from the stream. * * @param buf The buffer to read data into. @@ -30,18 +27,18 @@ namespace bell * @return The number of bytes read. * @throws std::runtime_error if the stream is closed. */ - size_t read(uint8_t *buf, size_t nbytes); + size_t read(uint8_t* buf, size_t nbytes); - /* + /* * Skips nbytes bytes in the stream. */ - size_t skip(size_t nbytes); + size_t skip(size_t nbytes); - size_t position(); + size_t position(); - size_t size(); + size_t size(); - // Closes the connection - void close(); - }; -} + // Closes the connection + void close(); +}; +} // namespace bell diff --git a/components/spotify/cspot/bell/main/io/include/HTTPClient.h b/components/spotify/cspot/bell/main/io/include/HTTPClient.h index 7ce164d9..052ce6c6 100644 --- a/components/spotify/cspot/bell/main/io/include/HTTPClient.h +++ b/components/spotify/cspot/bell/main/io/include/HTTPClient.h @@ -1,24 +1,20 @@ #pragma once -#include +#include // for size_t +#include // for uint8_t, int32_t +#include // for make_unique, unique_ptr +#include // for string +#include // for string_view +#include // for pair +#include // for vector -#include -#include -#include -#include -#include -#include -#include - -#include "BellSocket.h" -#include "ByteStream.h" -#include "SocketStream.h" -#include "URLParser.h" +#include "SocketStream.h" // for SocketStream +#include "URLParser.h" // for URLParser #ifndef BELL_DISABLE_FMT -#include "fmt/core.h" +#include "fmt/core.h" // for format #endif -#include "picohttpparser.h" +#include "picohttpparser.h" // for phr_header namespace bell { class HTTPClient { @@ -31,19 +27,20 @@ class HTTPClient { // Helper over ValueHeader, formatting a HTTP bytes range struct RangeHeader { static ValueHeader range(int32_t from, int32_t to) { -#ifndef BELL_DISABLE_FMT +#ifndef BELL_DISABLE_FMT return ValueHeader{"Range", fmt::format("bytes={}-{}", from, to)}; -#else - return ValueHeader{"Range", "bytes=" + std::to_string(from) + "-" + std::to_string(to)}; -#endif +#else + return ValueHeader{ + "Range", "bytes=" + std::to_string(from) + "-" + std::to_string(to)}; +#endif } static ValueHeader last(int32_t nbytes) { -#ifndef BELL_DISABLE_FMT +#ifndef BELL_DISABLE_FMT return ValueHeader{"Range", fmt::format("bytes=-{}", nbytes)}; -#else +#else return ValueHeader{"Range", "bytes=-" + std::to_string(nbytes)}; -#endif +#endif } }; diff --git a/components/spotify/cspot/bell/main/io/include/SocketStream.h b/components/spotify/cspot/bell/main/io/include/SocketStream.h index 3b7030f1..7ed6770f 100644 --- a/components/spotify/cspot/bell/main/io/include/SocketStream.h +++ b/components/spotify/cspot/bell/main/io/include/SocketStream.h @@ -1,8 +1,10 @@ #pragma once -#include -#include "TCPSocket.h" -#include "TLSSocket.h" +#include // for streamsize, basic_streambuf<>::int_type, ios... +#include // for unique_ptr, operator!= +#include // for char_traits, string + +#include "BellSocket.h" // for Socket namespace bell { class SocketBuffer : public std::streambuf { diff --git a/components/spotify/cspot/bell/main/io/include/TCPSocket.h b/components/spotify/cspot/bell/main/io/include/TCPSocket.h index e28f0e76..73ae6ebf 100644 --- a/components/spotify/cspot/bell/main/io/include/TCPSocket.h +++ b/components/spotify/cspot/bell/main/io/include/TCPSocket.h @@ -39,6 +39,8 @@ class TCPSocket : public bell::Socket { TCPSocket(){}; ~TCPSocket() { close(); }; + int getFd() { return sockFd; } + void open(const std::string& host, uint16_t port) { int err; int domain = AF_INET; @@ -101,7 +103,9 @@ class TCPSocket : public bell::Socket { #endif return value; } - bool isOpen() { return !isClosed; } + bool isOpen() { + return !isClosed; + } void close() { if (!isClosed) { diff --git a/components/spotify/cspot/bell/main/io/include/TLSSocket.h b/components/spotify/cspot/bell/main/io/include/TLSSocket.h index e3d5f6a0..c3c43a5c 100644 --- a/components/spotify/cspot/bell/main/io/include/TLSSocket.h +++ b/components/spotify/cspot/bell/main/io/include/TLSSocket.h @@ -1,34 +1,21 @@ #ifndef BELL_TLS_SOCKET_H #define BELL_TLS_SOCKET_H -#include -#include -#include -#include -#include -#include "BellLogger.h" -#include "BellSocket.h" +#include // for uint8_t, uint16_t + +#include "BellSocket.h" // for Socket #ifdef _WIN32 #include #include #else -#include -#include -#include -#include -#include #endif -#include -#include -#include -#include -#include +#include // for size_t +#include // for string -#include "mbedtls/ctr_drbg.h" -#include "mbedtls/debug.h" -#include "mbedtls/entropy.h" -#include "mbedtls/net_sockets.h" -#include "mbedtls/ssl.h" +#include "mbedtls/ctr_drbg.h" // for mbedtls_ctr_drbg_context +#include "mbedtls/entropy.h" // for mbedtls_entropy_context +#include "mbedtls/net_sockets.h" // for mbedtls_net_context +#include "mbedtls/ssl.h" // for mbedtls_ssl_config, mbedtls_ssl_con... namespace bell { class TLSSocket : public bell::Socket { @@ -53,6 +40,7 @@ class TLSSocket : public bell::Socket { bool isOpen(); void close(); + int getFd() { return server_fd.fd; } }; } // namespace bell diff --git a/components/spotify/cspot/bell/main/io/include/URLParser.h b/components/spotify/cspot/bell/main/io/include/URLParser.h index 3778c91b..98a59929 100644 --- a/components/spotify/cspot/bell/main/io/include/URLParser.h +++ b/components/spotify/cspot/bell/main/io/include/URLParser.h @@ -1,9 +1,9 @@ #pragma once -#include -#include -#include -#include +#include // for strtol, size_t +#include // for match_results, match_results<>::value_type, sub... +#include // for invalid_argument +#include // for string, allocator, operator+, char_traits, oper... namespace bell { class URLParser { @@ -60,7 +60,7 @@ class URLParser { std::string path; #ifdef BELL_DISABLE_REGEX void parse(const char* url, std::vector& match); -#else +#else static const std::regex urlParseRegex; #endif @@ -71,10 +71,10 @@ class URLParser { #ifdef BELL_DISABLE_REGEX std::vector match(6); parser.parse(url.c_str(), match); -#else +#else std::cmatch match; std::regex_match(url.c_str(), match, parser.urlParseRegex); -#endif +#endif if (match.size() < 3) { throw std::invalid_argument("Invalid URL"); diff --git a/components/spotify/cspot/bell/main/io/include/X509Bundle.h b/components/spotify/cspot/bell/main/io/include/X509Bundle.h index d9ed17cd..118dbcdc 100644 --- a/components/spotify/cspot/bell/main/io/include/X509Bundle.h +++ b/components/spotify/cspot/bell/main/io/include/X509Bundle.h @@ -1,8 +1,11 @@ #pragma once -#include -#include "BellLogger.h" -#include "mbedtls/ssl.h" +#include // for mbedtls_x509_crt +#include // for size_t +#include // for uint8_t, uint16_t, uint32_t +#include // for vector + +#include "mbedtls/ssl.h" // for mbedtls_ssl_config namespace bell::X509Bundle { @@ -13,6 +16,7 @@ typedef struct crt_bundle_t { } crt_bundle_t; static crt_bundle_t s_crt_bundle; +static std::vector bundleBytes; static constexpr auto TAG = "X509Bundle"; static constexpr auto CRT_HEADER_OFFSET = 4; diff --git a/components/spotify/cspot/bell/main/io/include/picohttpparser.h b/components/spotify/cspot/bell/main/io/include/picohttpparser.h index 2c685928..b0f663e6 100644 --- a/components/spotify/cspot/bell/main/io/include/picohttpparser.h +++ b/components/spotify/cspot/bell/main/io/include/picohttpparser.h @@ -40,30 +40,35 @@ extern "C" { /* contains name and value of a header (name == NULL if is a continuing line * of a multiline header */ struct phr_header { - const char *name; - size_t name_len; - const char *value; - size_t value_len; + const char* name; + size_t name_len; + const char* value; + size_t value_len; }; /* returns number of bytes consumed if successful, -2 if request is partial, * -1 if failed */ -int phr_parse_request(const char *buf, size_t len, const char **method, size_t *method_len, const char **path, size_t *path_len, - int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len); +int phr_parse_request(const char* buf, size_t len, const char** method, + size_t* method_len, const char** path, size_t* path_len, + int* minor_version, struct phr_header* headers, + size_t* num_headers, size_t last_len); /* ditto */ -int phr_parse_response(const char *_buf, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len, - struct phr_header *headers, size_t *num_headers, size_t last_len); +int phr_parse_response(const char* _buf, size_t len, int* minor_version, + int* status, const char** msg, size_t* msg_len, + struct phr_header* headers, size_t* num_headers, + size_t last_len); /* ditto */ -int phr_parse_headers(const char *buf, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len); +int phr_parse_headers(const char* buf, size_t len, struct phr_header* headers, + size_t* num_headers, size_t last_len); /* should be zero-filled before start */ struct phr_chunked_decoder { - size_t bytes_left_in_chunk; /* number of bytes left in current chunk */ - char consume_trailer; /* if trailing headers should be consumed */ - char _hex_count; - char _state; + size_t bytes_left_in_chunk; /* number of bytes left in current chunk */ + char consume_trailer; /* if trailing headers should be consumed */ + char _hex_count; + char _state; }; /* the function rewrites the buffer given as (buf, bufsz) removing the chunked- @@ -75,10 +80,11 @@ struct phr_chunked_decoder { * octets left undecoded, that starts from the offset returned by `*bufsz`. * Returns -1 on error. */ -ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *bufsz); +ssize_t phr_decode_chunked(struct phr_chunked_decoder* decoder, char* buf, + size_t* bufsz); /* returns if the chunked decoder is in middle of chunked data */ -int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder); +int phr_decode_chunked_is_in_data(struct phr_chunked_decoder* decoder); #ifdef __cplusplus } diff --git a/components/spotify/cspot/bell/main/io/picohttpparser.c b/components/spotify/cspot/bell/main/io/picohttpparser.c index 5e5783ab..b4070466 100644 --- a/components/spotify/cspot/bell/main/io/picohttpparser.c +++ b/components/spotify/cspot/bell/main/io/picohttpparser.c @@ -24,9 +24,10 @@ * IN THE SOFTWARE. */ -#include -#include -#include +#include // for assert +#include // for NULL, size_t +#include // for memmove +#include // for ssize_t #ifdef __SSE4_2__ #ifdef _MSC_VER #include @@ -34,7 +35,7 @@ #include #endif #endif -#include "picohttpparser.h" +#include "picohttpparser.h" // for phr_chunked_decoder, phr_header, phr_dec... #if __GNUC__ >= 3 #define likely(x) __builtin_expect(!!(x), 1) @@ -52,612 +53,634 @@ #define IS_PRINTABLE_ASCII(c) ((unsigned char)(c)-040u < 0137u) -#define CHECK_EOF() \ - if (buf == buf_end) { \ - *ret = -2; \ - return NULL; \ - } +#define CHECK_EOF() \ + if (buf == buf_end) { \ + *ret = -2; \ + return NULL; \ + } -#define EXPECT_CHAR_NO_CHECK(ch) \ - if (*buf++ != ch) { \ - *ret = -1; \ - return NULL; \ - } +#define EXPECT_CHAR_NO_CHECK(ch) \ + if (*buf++ != ch) { \ + *ret = -1; \ + return NULL; \ + } -#define EXPECT_CHAR(ch) \ - CHECK_EOF(); \ - EXPECT_CHAR_NO_CHECK(ch); +#define EXPECT_CHAR(ch) \ + CHECK_EOF(); \ + EXPECT_CHAR_NO_CHECK(ch); -#define ADVANCE_TOKEN(tok, toklen) \ - do { \ - const char *tok_start = buf; \ - static const char ALIGNED(16) ranges2[16] = "\000\040\177\177"; \ - int found2; \ - buf = findchar_fast(buf, buf_end, ranges2, 4, &found2); \ - if (!found2) { \ - CHECK_EOF(); \ - } \ - while (1) { \ - if (*buf == ' ') { \ - break; \ - } else if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { \ - if ((unsigned char)*buf < '\040' || *buf == '\177') { \ - *ret = -1; \ - return NULL; \ - } \ - } \ - ++buf; \ - CHECK_EOF(); \ - } \ - tok = tok_start; \ - toklen = buf - tok_start; \ - } while (0) +#define ADVANCE_TOKEN(tok, toklen) \ + do { \ + const char* tok_start = buf; \ + static const char ALIGNED(16) ranges2[16] = "\000\040\177\177"; \ + int found2; \ + buf = findchar_fast(buf, buf_end, ranges2, 4, &found2); \ + if (!found2) { \ + CHECK_EOF(); \ + } \ + while (1) { \ + if (*buf == ' ') { \ + break; \ + } else if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { \ + if ((unsigned char)*buf < '\040' || *buf == '\177') { \ + *ret = -1; \ + return NULL; \ + } \ + } \ + ++buf; \ + CHECK_EOF(); \ + } \ + tok = tok_start; \ + toklen = buf - tok_start; \ + } while (0) -static const char *token_char_map = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - "\0\1\0\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0" - "\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\1\1" - "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\1\0\1\0" - "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; +static const char* token_char_map = + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\1\0\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0" + "\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\1\1" + "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\1\0\1\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; -static const char *findchar_fast(const char *buf, const char *buf_end, const char *ranges, size_t ranges_size, int *found) -{ - *found = 0; +static const char* findchar_fast(const char* buf, const char* buf_end, + const char* ranges, size_t ranges_size, + int* found) { + *found = 0; #if __SSE4_2__ - if (likely(buf_end - buf >= 16)) { - __m128i ranges16 = _mm_loadu_si128((const __m128i *)ranges); + if (likely(buf_end - buf >= 16)) { + __m128i ranges16 = _mm_loadu_si128((const __m128i*)ranges); - size_t left = (buf_end - buf) & ~15; - do { - __m128i b16 = _mm_loadu_si128((const __m128i *)buf); - int r = _mm_cmpestri(ranges16, ranges_size, b16, 16, _SIDD_LEAST_SIGNIFICANT | _SIDD_CMP_RANGES | _SIDD_UBYTE_OPS); - if (unlikely(r != 16)) { - buf += r; - *found = 1; - break; - } - buf += 16; - left -= 16; - } while (likely(left != 0)); - } + size_t left = (buf_end - buf) & ~15; + do { + __m128i b16 = _mm_loadu_si128((const __m128i*)buf); + int r = _mm_cmpestri( + ranges16, ranges_size, b16, 16, + _SIDD_LEAST_SIGNIFICANT | _SIDD_CMP_RANGES | _SIDD_UBYTE_OPS); + if (unlikely(r != 16)) { + buf += r; + *found = 1; + break; + } + buf += 16; + left -= 16; + } while (likely(left != 0)); + } #else - /* suppress unused parameter warning */ - (void)buf_end; - (void)ranges; - (void)ranges_size; + /* suppress unused parameter warning */ + (void)buf_end; + (void)ranges; + (void)ranges_size; #endif - return buf; + return buf; } -static const char *get_token_to_eol(const char *buf, const char *buf_end, const char **token, size_t *token_len, int *ret) -{ - const char *token_start = buf; +static const char* get_token_to_eol(const char* buf, const char* buf_end, + const char** token, size_t* token_len, + int* ret) { + const char* token_start = buf; #ifdef __SSE4_2__ - static const char ALIGNED(16) ranges1[16] = "\0\010" /* allow HT */ - "\012\037" /* allow SP and up to but not including DEL */ - "\177\177"; /* allow chars w. MSB set */ - int found; - buf = findchar_fast(buf, buf_end, ranges1, 6, &found); - if (found) - goto FOUND_CTL; + static const char ALIGNED(16) ranges1[16] = + "\0\010" /* allow HT */ + "\012\037" /* allow SP and up to but not including DEL */ + "\177\177"; /* allow chars w. MSB set */ + int found; + buf = findchar_fast(buf, buf_end, ranges1, 6, &found); + if (found) + goto FOUND_CTL; #else - /* find non-printable char within the next 8 bytes, this is the hottest code; manually inlined */ - while (likely(buf_end - buf >= 8)) { -#define DOIT() \ - do { \ - if (unlikely(!IS_PRINTABLE_ASCII(*buf))) \ - goto NonPrintable; \ - ++buf; \ - } while (0) - DOIT(); - DOIT(); - DOIT(); - DOIT(); - DOIT(); - DOIT(); - DOIT(); - DOIT(); + /* find non-printable char within the next 8 bytes, this is the hottest code; manually inlined */ + while (likely(buf_end - buf >= 8)) { +#define DOIT() \ + do { \ + if (unlikely(!IS_PRINTABLE_ASCII(*buf))) \ + goto NonPrintable; \ + ++buf; \ + } while (0) + DOIT(); + DOIT(); + DOIT(); + DOIT(); + DOIT(); + DOIT(); + DOIT(); + DOIT(); #undef DOIT - continue; - NonPrintable: - if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) { - goto FOUND_CTL; - } - ++buf; + continue; + NonPrintable: + if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || + unlikely(*buf == '\177')) { + goto FOUND_CTL; } + ++buf; + } #endif - for (;; ++buf) { - CHECK_EOF(); - if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { - if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) { - goto FOUND_CTL; - } - } + for (;; ++buf) { + CHECK_EOF(); + if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { + if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || + unlikely(*buf == '\177')) { + goto FOUND_CTL; + } } + } FOUND_CTL: - if (likely(*buf == '\015')) { - ++buf; - EXPECT_CHAR('\012'); - *token_len = buf - 2 - token_start; - } else if (*buf == '\012') { - *token_len = buf - token_start; - ++buf; - } else { - *ret = -1; - return NULL; - } - *token = token_start; - - return buf; -} - -static const char *is_complete(const char *buf, const char *buf_end, size_t last_len, int *ret) -{ - int ret_cnt = 0; - buf = last_len < 3 ? buf : buf + last_len - 3; - - while (1) { - CHECK_EOF(); - if (*buf == '\015') { - ++buf; - CHECK_EOF(); - EXPECT_CHAR('\012'); - ++ret_cnt; - } else if (*buf == '\012') { - ++buf; - ++ret_cnt; - } else { - ++buf; - ret_cnt = 0; - } - if (ret_cnt == 2) { - return buf; - } - } - - *ret = -2; + if (likely(*buf == '\015')) { + ++buf; + EXPECT_CHAR('\012'); + *token_len = buf - 2 - token_start; + } else if (*buf == '\012') { + *token_len = buf - token_start; + ++buf; + } else { + *ret = -1; return NULL; + } + *token = token_start; + + return buf; } -#define PARSE_INT(valp_, mul_) \ - if (*buf < '0' || '9' < *buf) { \ - buf++; \ - *ret = -1; \ - return NULL; \ - } \ - *(valp_) = (mul_) * (*buf++ - '0'); +static const char* is_complete(const char* buf, const char* buf_end, + size_t last_len, int* ret) { + int ret_cnt = 0; + buf = last_len < 3 ? buf : buf + last_len - 3; -#define PARSE_INT_3(valp_) \ - do { \ - int res_ = 0; \ - PARSE_INT(&res_, 100) \ - *valp_ = res_; \ - PARSE_INT(&res_, 10) \ - *valp_ += res_; \ - PARSE_INT(&res_, 1) \ - *valp_ += res_; \ - } while (0) - -/* returned pointer is always within [buf, buf_end), or null */ -static const char *parse_token(const char *buf, const char *buf_end, const char **token, size_t *token_len, char next_char, - int *ret) -{ - /* We use pcmpestri to detect non-token characters. This instruction can take no more than eight character ranges (8*2*8=128 - * bits that is the size of a SSE register). Due to this restriction, characters `|` and `~` are handled in the slow loop. */ - static const char ALIGNED(16) ranges[] = "\x00 " /* control chars and up to SP */ - "\"\"" /* 0x22 */ - "()" /* 0x28,0x29 */ - ",," /* 0x2c */ - "//" /* 0x2f */ - ":@" /* 0x3a-0x40 */ - "[]" /* 0x5b-0x5d */ - "{\xff"; /* 0x7b-0xff */ - const char *buf_start = buf; - int found; - buf = findchar_fast(buf, buf_end, ranges, sizeof(ranges) - 1, &found); - if (!found) { - CHECK_EOF(); - } - while (1) { - if (*buf == next_char) { - break; - } else if (!token_char_map[(unsigned char)*buf]) { - *ret = -1; - return NULL; - } - ++buf; - CHECK_EOF(); - } - *token = buf_start; - *token_len = buf - buf_start; - return buf; -} - -/* returned pointer is always within [buf, buf_end), or null */ -static const char *parse_http_version(const char *buf, const char *buf_end, int *minor_version, int *ret) -{ - /* we want at least [HTTP/1.] to try to parse */ - if (buf_end - buf < 9) { - *ret = -2; - return NULL; - } - EXPECT_CHAR_NO_CHECK('H'); - EXPECT_CHAR_NO_CHECK('T'); - EXPECT_CHAR_NO_CHECK('T'); - EXPECT_CHAR_NO_CHECK('P'); - EXPECT_CHAR_NO_CHECK('/'); - EXPECT_CHAR_NO_CHECK('1'); - EXPECT_CHAR_NO_CHECK('.'); - PARSE_INT(minor_version, 1); - return buf; -} - -static const char *parse_headers(const char *buf, const char *buf_end, struct phr_header *headers, size_t *num_headers, - size_t max_headers, int *ret) -{ - for (;; ++*num_headers) { - CHECK_EOF(); - if (*buf == '\015') { - ++buf; - EXPECT_CHAR('\012'); - break; - } else if (*buf == '\012') { - ++buf; - break; - } - if (*num_headers == max_headers) { - *ret = -1; - return NULL; - } - if (!(*num_headers != 0 && (*buf == ' ' || *buf == '\t'))) { - /* parsing name, but do not discard SP before colon, see - * http://www.mozilla.org/security/announce/2006/mfsa2006-33.html */ - if ((buf = parse_token(buf, buf_end, &headers[*num_headers].name, &headers[*num_headers].name_len, ':', ret)) == NULL) { - return NULL; - } - if (headers[*num_headers].name_len == 0) { - *ret = -1; - return NULL; - } - ++buf; - for (;; ++buf) { - CHECK_EOF(); - if (!(*buf == ' ' || *buf == '\t')) { - break; - } - } - } else { - headers[*num_headers].name = NULL; - headers[*num_headers].name_len = 0; - } - const char *value; - size_t value_len; - if ((buf = get_token_to_eol(buf, buf_end, &value, &value_len, ret)) == NULL) { - return NULL; - } - /* remove trailing SPs and HTABs */ - const char *value_end = value + value_len; - for (; value_end != value; --value_end) { - const char c = *(value_end - 1); - if (!(c == ' ' || c == '\t')) { - break; - } - } - headers[*num_headers].value = value; - headers[*num_headers].value_len = value_end - value; - } - return buf; -} - -static const char *parse_request(const char *buf, const char *buf_end, const char **method, size_t *method_len, const char **path, - size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers, - size_t max_headers, int *ret) -{ - /* skip first empty line (some clients add CRLF after POST content) */ + while (1) { CHECK_EOF(); if (*buf == '\015') { - ++buf; - EXPECT_CHAR('\012'); + ++buf; + CHECK_EOF(); + EXPECT_CHAR('\012'); + ++ret_cnt; } else if (*buf == '\012') { - ++buf; + ++buf; + ++ret_cnt; + } else { + ++buf; + ret_cnt = 0; } + if (ret_cnt == 2) { + return buf; + } + } - /* parse request line */ - if ((buf = parse_token(buf, buf_end, method, method_len, ' ', ret)) == NULL) { - return NULL; - } - do { - ++buf; - CHECK_EOF(); - } while (*buf == ' '); - ADVANCE_TOKEN(*path, *path_len); - do { - ++buf; - CHECK_EOF(); - } while (*buf == ' '); - if (*method_len == 0 || *path_len == 0) { - *ret = -1; - return NULL; - } - if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) { - return NULL; + *ret = -2; + return NULL; +} + +#define PARSE_INT(valp_, mul_) \ + if (*buf < '0' || '9' < *buf) { \ + buf++; \ + *ret = -1; \ + return NULL; \ + } \ + *(valp_) = (mul_) * (*buf++ - '0'); + +#define PARSE_INT_3(valp_) \ + do { \ + int res_ = 0; \ + PARSE_INT(&res_, 100) \ + *valp_ = res_; \ + PARSE_INT(&res_, 10) \ + *valp_ += res_; \ + PARSE_INT(&res_, 1) \ + *valp_ += res_; \ + } while (0) + +/* returned pointer is always within [buf, buf_end), or null */ +static const char* parse_token(const char* buf, const char* buf_end, + const char** token, size_t* token_len, + char next_char, int* ret) { + /* We use pcmpestri to detect non-token characters. This instruction can take no more than eight character ranges (8*2*8=128 + * bits that is the size of a SSE register). Due to this restriction, characters `|` and `~` are handled in the slow loop. */ + static const char ALIGNED(16) ranges[] = + "\x00 " /* control chars and up to SP */ + "\"\"" /* 0x22 */ + "()" /* 0x28,0x29 */ + ",," /* 0x2c */ + "//" /* 0x2f */ + ":@" /* 0x3a-0x40 */ + "[]" /* 0x5b-0x5d */ + "{\xff"; /* 0x7b-0xff */ + const char* buf_start = buf; + int found; + buf = findchar_fast(buf, buf_end, ranges, sizeof(ranges) - 1, &found); + if (!found) { + CHECK_EOF(); + } + while (1) { + if (*buf == next_char) { + break; + } else if (!token_char_map[(unsigned char)*buf]) { + *ret = -1; + return NULL; } + ++buf; + CHECK_EOF(); + } + *token = buf_start; + *token_len = buf - buf_start; + return buf; +} + +/* returned pointer is always within [buf, buf_end), or null */ +static const char* parse_http_version(const char* buf, const char* buf_end, + int* minor_version, int* ret) { + /* we want at least [HTTP/1.] to try to parse */ + if (buf_end - buf < 9) { + *ret = -2; + return NULL; + } + EXPECT_CHAR_NO_CHECK('H'); + EXPECT_CHAR_NO_CHECK('T'); + EXPECT_CHAR_NO_CHECK('T'); + EXPECT_CHAR_NO_CHECK('P'); + EXPECT_CHAR_NO_CHECK('/'); + EXPECT_CHAR_NO_CHECK('1'); + EXPECT_CHAR_NO_CHECK('.'); + PARSE_INT(minor_version, 1); + return buf; +} + +static const char* parse_headers(const char* buf, const char* buf_end, + struct phr_header* headers, + size_t* num_headers, size_t max_headers, + int* ret) { + for (;; ++*num_headers) { + CHECK_EOF(); if (*buf == '\015') { - ++buf; - EXPECT_CHAR('\012'); + ++buf; + EXPECT_CHAR('\012'); + break; } else if (*buf == '\012') { - ++buf; - } else { + ++buf; + break; + } + if (*num_headers == max_headers) { + *ret = -1; + return NULL; + } + if (!(*num_headers != 0 && (*buf == ' ' || *buf == '\t'))) { + /* parsing name, but do not discard SP before colon, see + * http://www.mozilla.org/security/announce/2006/mfsa2006-33.html */ + if ((buf = parse_token(buf, buf_end, &headers[*num_headers].name, + &headers[*num_headers].name_len, ':', ret)) == + NULL) { + return NULL; + } + if (headers[*num_headers].name_len == 0) { *ret = -1; return NULL; - } - - return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret); -} - -int phr_parse_request(const char *buf_start, size_t len, const char **method, size_t *method_len, const char **path, - size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len) -{ - const char *buf = buf_start, *buf_end = buf_start + len; - size_t max_headers = *num_headers; - int r; - - *method = NULL; - *method_len = 0; - *path = NULL; - *path_len = 0; - *minor_version = -1; - *num_headers = 0; - - /* if last_len != 0, check if the request is complete (a fast countermeasure - againt slowloris */ - if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { - return r; - } - - if ((buf = parse_request(buf, buf_end, method, method_len, path, path_len, minor_version, headers, num_headers, max_headers, - &r)) == NULL) { - return r; - } - - return (int)(buf - buf_start); -} - -static const char *parse_response(const char *buf, const char *buf_end, int *minor_version, int *status, const char **msg, - size_t *msg_len, struct phr_header *headers, size_t *num_headers, size_t max_headers, int *ret) -{ - /* parse "HTTP/1.x" */ - if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) { - return NULL; - } - /* skip space */ - if (*buf != ' ') { - *ret = -1; - return NULL; - } - do { - ++buf; + } + ++buf; + for (;; ++buf) { CHECK_EOF(); - } while (*buf == ' '); - /* parse status code, we want at least [:digit:][:digit:][:digit:] to try to parse */ - if (buf_end - buf < 4) { - *ret = -2; - return NULL; - } - PARSE_INT_3(status); - - /* get message including preceding space */ - if ((buf = get_token_to_eol(buf, buf_end, msg, msg_len, ret)) == NULL) { - return NULL; - } - if (*msg_len == 0) { - /* ok */ - } else if (**msg == ' ') { - /* Remove preceding space. Successful return from `get_token_to_eol` guarantees that we would hit something other than SP - * before running past the end of the given buffer. */ - do { - ++*msg; - --*msg_len; - } while (**msg == ' '); + if (!(*buf == ' ' || *buf == '\t')) { + break; + } + } } else { - /* garbage found after status code */ - *ret = -1; - return NULL; + headers[*num_headers].name = NULL; + headers[*num_headers].name_len = 0; } - - return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret); + const char* value; + size_t value_len; + if ((buf = get_token_to_eol(buf, buf_end, &value, &value_len, ret)) == + NULL) { + return NULL; + } + /* remove trailing SPs and HTABs */ + const char* value_end = value + value_len; + for (; value_end != value; --value_end) { + const char c = *(value_end - 1); + if (!(c == ' ' || c == '\t')) { + break; + } + } + headers[*num_headers].value = value; + headers[*num_headers].value_len = value_end - value; + } + return buf; } -int phr_parse_response(const char *buf_start, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len, - struct phr_header *headers, size_t *num_headers, size_t last_len) -{ - const char *buf = buf_start, *buf_end = buf + len; - size_t max_headers = *num_headers; - int r; +static const char* parse_request(const char* buf, const char* buf_end, + const char** method, size_t* method_len, + const char** path, size_t* path_len, + int* minor_version, struct phr_header* headers, + size_t* num_headers, size_t max_headers, + int* ret) { + /* skip first empty line (some clients add CRLF after POST content) */ + CHECK_EOF(); + if (*buf == '\015') { + ++buf; + EXPECT_CHAR('\012'); + } else if (*buf == '\012') { + ++buf; + } - *minor_version = -1; - *status = 0; - *msg = NULL; - *msg_len = 0; - *num_headers = 0; + /* parse request line */ + if ((buf = parse_token(buf, buf_end, method, method_len, ' ', ret)) == NULL) { + return NULL; + } + do { + ++buf; + CHECK_EOF(); + } while (*buf == ' '); + ADVANCE_TOKEN(*path, *path_len); + do { + ++buf; + CHECK_EOF(); + } while (*buf == ' '); + if (*method_len == 0 || *path_len == 0) { + *ret = -1; + return NULL; + } + if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) { + return NULL; + } + if (*buf == '\015') { + ++buf; + EXPECT_CHAR('\012'); + } else if (*buf == '\012') { + ++buf; + } else { + *ret = -1; + return NULL; + } - /* if last_len != 0, check if the response is complete (a fast countermeasure - against slowloris */ - if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { - return r; - } - - if ((buf = parse_response(buf, buf_end, minor_version, status, msg, msg_len, headers, num_headers, max_headers, &r)) == NULL) { - return r; - } - - return (int)(buf - buf_start); + return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret); } -int phr_parse_headers(const char *buf_start, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len) -{ - const char *buf = buf_start, *buf_end = buf + len; - size_t max_headers = *num_headers; - int r; +int phr_parse_request(const char* buf_start, size_t len, const char** method, + size_t* method_len, const char** path, size_t* path_len, + int* minor_version, struct phr_header* headers, + size_t* num_headers, size_t last_len) { + const char *buf = buf_start, *buf_end = buf_start + len; + size_t max_headers = *num_headers; + int r; - *num_headers = 0; + *method = NULL; + *method_len = 0; + *path = NULL; + *path_len = 0; + *minor_version = -1; + *num_headers = 0; - /* if last_len != 0, check if the response is complete (a fast countermeasure + /* if last_len != 0, check if the request is complete (a fast countermeasure + againt slowloris */ + if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { + return r; + } + + if ((buf = parse_request(buf, buf_end, method, method_len, path, path_len, + minor_version, headers, num_headers, max_headers, + &r)) == NULL) { + return r; + } + + return (int)(buf - buf_start); +} + +static const char* parse_response(const char* buf, const char* buf_end, + int* minor_version, int* status, + const char** msg, size_t* msg_len, + struct phr_header* headers, + size_t* num_headers, size_t max_headers, + int* ret) { + /* parse "HTTP/1.x" */ + if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) { + return NULL; + } + /* skip space */ + if (*buf != ' ') { + *ret = -1; + return NULL; + } + do { + ++buf; + CHECK_EOF(); + } while (*buf == ' '); + /* parse status code, we want at least [:digit:][:digit:][:digit:] to try to parse */ + if (buf_end - buf < 4) { + *ret = -2; + return NULL; + } + PARSE_INT_3(status); + + /* get message including preceding space */ + if ((buf = get_token_to_eol(buf, buf_end, msg, msg_len, ret)) == NULL) { + return NULL; + } + if (*msg_len == 0) { + /* ok */ + } else if (**msg == ' ') { + /* Remove preceding space. Successful return from `get_token_to_eol` guarantees that we would hit something other than SP + * before running past the end of the given buffer. */ + do { + ++*msg; + --*msg_len; + } while (**msg == ' '); + } else { + /* garbage found after status code */ + *ret = -1; + return NULL; + } + + return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret); +} + +int phr_parse_response(const char* buf_start, size_t len, int* minor_version, + int* status, const char** msg, size_t* msg_len, + struct phr_header* headers, size_t* num_headers, + size_t last_len) { + const char *buf = buf_start, *buf_end = buf + len; + size_t max_headers = *num_headers; + int r; + + *minor_version = -1; + *status = 0; + *msg = NULL; + *msg_len = 0; + *num_headers = 0; + + /* if last_len != 0, check if the response is complete (a fast countermeasure against slowloris */ - if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { - return r; - } + if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { + return r; + } - if ((buf = parse_headers(buf, buf_end, headers, num_headers, max_headers, &r)) == NULL) { - return r; - } + if ((buf = parse_response(buf, buf_end, minor_version, status, msg, msg_len, + headers, num_headers, max_headers, &r)) == NULL) { + return r; + } - return (int)(buf - buf_start); + return (int)(buf - buf_start); +} + +int phr_parse_headers(const char* buf_start, size_t len, + struct phr_header* headers, size_t* num_headers, + size_t last_len) { + const char *buf = buf_start, *buf_end = buf + len; + size_t max_headers = *num_headers; + int r; + + *num_headers = 0; + + /* if last_len != 0, check if the response is complete (a fast countermeasure + against slowloris */ + if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { + return r; + } + + if ((buf = parse_headers(buf, buf_end, headers, num_headers, max_headers, + &r)) == NULL) { + return r; + } + + return (int)(buf - buf_start); } enum { - CHUNKED_IN_CHUNK_SIZE, - CHUNKED_IN_CHUNK_EXT, - CHUNKED_IN_CHUNK_DATA, - CHUNKED_IN_CHUNK_CRLF, - CHUNKED_IN_TRAILERS_LINE_HEAD, - CHUNKED_IN_TRAILERS_LINE_MIDDLE + CHUNKED_IN_CHUNK_SIZE, + CHUNKED_IN_CHUNK_EXT, + CHUNKED_IN_CHUNK_DATA, + CHUNKED_IN_CHUNK_CRLF, + CHUNKED_IN_TRAILERS_LINE_HEAD, + CHUNKED_IN_TRAILERS_LINE_MIDDLE }; -static int decode_hex(int ch) -{ - if ('0' <= ch && ch <= '9') { - return ch - '0'; - } else if ('A' <= ch && ch <= 'F') { - return ch - 'A' + 0xa; - } else if ('a' <= ch && ch <= 'f') { - return ch - 'a' + 0xa; - } else { - return -1; - } +static int decode_hex(int ch) { + if ('0' <= ch && ch <= '9') { + return ch - '0'; + } else if ('A' <= ch && ch <= 'F') { + return ch - 'A' + 0xa; + } else if ('a' <= ch && ch <= 'f') { + return ch - 'a' + 0xa; + } else { + return -1; + } } -ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *_bufsz) -{ - size_t dst = 0, src = 0, bufsz = *_bufsz; - ssize_t ret = -2; /* incomplete */ +ssize_t phr_decode_chunked(struct phr_chunked_decoder* decoder, char* buf, + size_t* _bufsz) { + size_t dst = 0, src = 0, bufsz = *_bufsz; + ssize_t ret = -2; /* incomplete */ - while (1) { - switch (decoder->_state) { - case CHUNKED_IN_CHUNK_SIZE: - for (;; ++src) { - int v; - if (src == bufsz) - goto Exit; - if ((v = decode_hex(buf[src])) == -1) { - if (decoder->_hex_count == 0) { - ret = -1; - goto Exit; - } - break; - } - if (decoder->_hex_count == sizeof(size_t) * 2) { - ret = -1; - goto Exit; - } - decoder->bytes_left_in_chunk = decoder->bytes_left_in_chunk * 16 + v; - ++decoder->_hex_count; + while (1) { + switch (decoder->_state) { + case CHUNKED_IN_CHUNK_SIZE: + for (;; ++src) { + int v; + if (src == bufsz) + goto Exit; + if ((v = decode_hex(buf[src])) == -1) { + if (decoder->_hex_count == 0) { + ret = -1; + goto Exit; } - decoder->_hex_count = 0; - decoder->_state = CHUNKED_IN_CHUNK_EXT; - /* fallthru */ - case CHUNKED_IN_CHUNK_EXT: - /* RFC 7230 A.2 "Line folding in chunk extensions is disallowed" */ - for (;; ++src) { - if (src == bufsz) - goto Exit; - if (buf[src] == '\012') - break; - } - ++src; - if (decoder->bytes_left_in_chunk == 0) { - if (decoder->consume_trailer) { - decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD; - break; - } else { - goto Complete; - } - } - decoder->_state = CHUNKED_IN_CHUNK_DATA; - /* fallthru */ - case CHUNKED_IN_CHUNK_DATA: { - size_t avail = bufsz - src; - if (avail < decoder->bytes_left_in_chunk) { - if (dst != src) - memmove(buf + dst, buf + src, avail); - src += avail; - dst += avail; - decoder->bytes_left_in_chunk -= avail; - goto Exit; - } - if (dst != src) - memmove(buf + dst, buf + src, decoder->bytes_left_in_chunk); - src += decoder->bytes_left_in_chunk; - dst += decoder->bytes_left_in_chunk; - decoder->bytes_left_in_chunk = 0; - decoder->_state = CHUNKED_IN_CHUNK_CRLF; - } - /* fallthru */ - case CHUNKED_IN_CHUNK_CRLF: - for (;; ++src) { - if (src == bufsz) - goto Exit; - if (buf[src] != '\015') - break; - } - if (buf[src] != '\012') { - ret = -1; - goto Exit; - } - ++src; - decoder->_state = CHUNKED_IN_CHUNK_SIZE; break; - case CHUNKED_IN_TRAILERS_LINE_HEAD: - for (;; ++src) { - if (src == bufsz) - goto Exit; - if (buf[src] != '\015') - break; - } - if (buf[src++] == '\012') - goto Complete; - decoder->_state = CHUNKED_IN_TRAILERS_LINE_MIDDLE; - /* fallthru */ - case CHUNKED_IN_TRAILERS_LINE_MIDDLE: - for (;; ++src) { - if (src == bufsz) - goto Exit; - if (buf[src] == '\012') - break; - } - ++src; + } + if (decoder->_hex_count == sizeof(size_t) * 2) { + ret = -1; + goto Exit; + } + decoder->bytes_left_in_chunk = decoder->bytes_left_in_chunk * 16 + v; + ++decoder->_hex_count; + } + decoder->_hex_count = 0; + decoder->_state = CHUNKED_IN_CHUNK_EXT; + /* fallthru */ + case CHUNKED_IN_CHUNK_EXT: + /* RFC 7230 A.2 "Line folding in chunk extensions is disallowed" */ + for (;; ++src) { + if (src == bufsz) + goto Exit; + if (buf[src] == '\012') + break; + } + ++src; + if (decoder->bytes_left_in_chunk == 0) { + if (decoder->consume_trailer) { decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD; break; - default: - assert(!"decoder is corrupt"); + } else { + goto Complete; + } } + decoder->_state = CHUNKED_IN_CHUNK_DATA; + /* fallthru */ + case CHUNKED_IN_CHUNK_DATA: { + size_t avail = bufsz - src; + if (avail < decoder->bytes_left_in_chunk) { + if (dst != src) + memmove(buf + dst, buf + src, avail); + src += avail; + dst += avail; + decoder->bytes_left_in_chunk -= avail; + goto Exit; + } + if (dst != src) + memmove(buf + dst, buf + src, decoder->bytes_left_in_chunk); + src += decoder->bytes_left_in_chunk; + dst += decoder->bytes_left_in_chunk; + decoder->bytes_left_in_chunk = 0; + decoder->_state = CHUNKED_IN_CHUNK_CRLF; + } + /* fallthru */ + case CHUNKED_IN_CHUNK_CRLF: + for (;; ++src) { + if (src == bufsz) + goto Exit; + if (buf[src] != '\015') + break; + } + if (buf[src] != '\012') { + ret = -1; + goto Exit; + } + ++src; + decoder->_state = CHUNKED_IN_CHUNK_SIZE; + break; + case CHUNKED_IN_TRAILERS_LINE_HEAD: + for (;; ++src) { + if (src == bufsz) + goto Exit; + if (buf[src] != '\015') + break; + } + if (buf[src++] == '\012') + goto Complete; + decoder->_state = CHUNKED_IN_TRAILERS_LINE_MIDDLE; + /* fallthru */ + case CHUNKED_IN_TRAILERS_LINE_MIDDLE: + for (;; ++src) { + if (src == bufsz) + goto Exit; + if (buf[src] == '\012') + break; + } + ++src; + decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD; + break; + default: + assert(!"decoder is corrupt"); } + } Complete: - ret = bufsz - src; + ret = bufsz - src; Exit: - if (dst != src) - memmove(buf + dst, buf + src, bufsz - src); - *_bufsz = dst; - return ret; + if (dst != src) + memmove(buf + dst, buf + src, bufsz - src); + *_bufsz = dst; + return ret; } -int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder) -{ - return decoder->_state == CHUNKED_IN_CHUNK_DATA; +int phr_decode_chunked_is_in_data(struct phr_chunked_decoder* decoder) { + return decoder->_state == CHUNKED_IN_CHUNK_DATA; } #undef CHECK_EOF diff --git a/components/spotify/cspot/bell/main/platform/MDNSService.h b/components/spotify/cspot/bell/main/platform/MDNSService.h index f7389a41..ad372314 100644 --- a/components/spotify/cspot/bell/main/platform/MDNSService.h +++ b/components/spotify/cspot/bell/main/platform/MDNSService.h @@ -1,23 +1,19 @@ #pragma once -#include -#include -#include +#include // for map +#include // for unique_ptr +#include // for string namespace bell { class MDNSService { -public: - virtual ~MDNSService() { } - static std::unique_ptr registerService( - const std::string &serviceName, - const std::string &serviceType, - const std::string &serviceProto, - const std::string &serviceHost, - int servicePort, - const std::map txtData - ); - virtual void unregisterService() = 0; + public: + virtual ~MDNSService() {} + static std::unique_ptr registerService( + const std::string& serviceName, const std::string& serviceType, + const std::string& serviceProto, const std::string& serviceHost, + int servicePort, const std::map txtData); + virtual void unregisterService() = 0; }; -} // namespace bell \ No newline at end of file +} // namespace bell \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/platform/WrappedSemaphore.h b/components/spotify/cspot/bell/main/platform/WrappedSemaphore.h index be27ac70..e6a5b264 100644 --- a/components/spotify/cspot/bell/main/platform/WrappedSemaphore.h +++ b/components/spotify/cspot/bell/main/platform/WrappedSemaphore.h @@ -4,7 +4,7 @@ #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #elif __APPLE__ -#include +#include // for dispatch_semaphore_t #elif _WIN32 #include #else @@ -15,24 +15,24 @@ namespace bell { class WrappedSemaphore { - private: + private: #ifdef ESP_PLATFORM - SemaphoreHandle_t semaphoreHandle; + SemaphoreHandle_t semaphoreHandle; #elif __APPLE__ - dispatch_semaphore_t semaphoreHandle; + dispatch_semaphore_t semaphoreHandle; #elif _WIN32 - HANDLE semaphoreHandle; + HANDLE semaphoreHandle; #else - sem_t semaphoreHandle; + sem_t semaphoreHandle; #endif - public: - WrappedSemaphore(int maxVal = 200); - ~WrappedSemaphore(); + public: + WrappedSemaphore(int maxVal = 200); + ~WrappedSemaphore(); - int wait(); - int twait(long milliseconds = 10); - void give(); + int wait(); + int twait(long milliseconds = 10); + void give(); }; -} // namespace bell +} // namespace bell diff --git a/components/spotify/cspot/bell/main/platform/apple/MDNSService.cpp b/components/spotify/cspot/bell/main/platform/apple/MDNSService.cpp index 1948302f..c872f656 100644 --- a/components/spotify/cspot/bell/main/platform/apple/MDNSService.cpp +++ b/components/spotify/cspot/bell/main/platform/apple/MDNSService.cpp @@ -1,16 +1,20 @@ #include "MDNSService.h" -#include "dns_sd.h" -#include + +#include // for NULL +#include // for pair + +#include "dns_sd.h" // for DNSServiceRef, DNSServiceRefDeallocate, DNS... +#include "i386/endian.h" // for htons using namespace bell; class implMDNSService : public MDNSService { -private: - DNSServiceRef* service; + private: + DNSServiceRef* service; -public: - implMDNSService(DNSServiceRef* service) : service(service) { } - void unregisterService() { DNSServiceRefDeallocate(*service); } + public: + implMDNSService(DNSServiceRef* service) : service(service) {} + void unregisterService() { DNSServiceRefDeallocate(*service); } }; /** @@ -18,33 +22,30 @@ public: * @see https://developer.apple.com/documentation/dnssd/1804733-dnsserviceregister **/ std::unique_ptr MDNSService::registerService( - const std::string& serviceName, - const std::string& serviceType, - const std::string& serviceProto, - const std::string& serviceHost, - int servicePort, - const std::map txtData -) { - DNSServiceRef* ref = new DNSServiceRef(); - TXTRecordRef txtRecord; - TXTRecordCreate(&txtRecord, 0, NULL); - for (auto& data : txtData) { - TXTRecordSetValue(&txtRecord, data.first.c_str(), data.second.size(), data.second.c_str()); - } - DNSServiceRegister( - ref, /* sdRef */ - 0, /* flags */ - 0, /* interfaceIndex */ - serviceName.c_str(), /* name */ - (serviceType + "." + serviceProto).c_str(), /* regType (_spotify-connect._tcp) */ - NULL, /* domain */ - NULL, /* host */ - htons(servicePort), /* port */ - TXTRecordGetLength(&txtRecord), /* txtLen */ - TXTRecordGetBytesPtr(&txtRecord), /* txtRecord */ - NULL, /* callBack */ - NULL /* context */ - ); - TXTRecordDeallocate(&txtRecord); - return std::make_unique(ref); -} \ No newline at end of file + const std::string& serviceName, const std::string& serviceType, + const std::string& serviceProto, const std::string& serviceHost, + int servicePort, const std::map txtData) { + DNSServiceRef* ref = new DNSServiceRef(); + TXTRecordRef txtRecord; + TXTRecordCreate(&txtRecord, 0, NULL); + for (auto& data : txtData) { + TXTRecordSetValue(&txtRecord, data.first.c_str(), data.second.size(), + data.second.c_str()); + } + DNSServiceRegister(ref, /* sdRef */ + 0, /* flags */ + 0, /* interfaceIndex */ + serviceName.c_str(), /* name */ + (serviceType + "." + serviceProto) + .c_str(), /* regType (_spotify-connect._tcp) */ + NULL, /* domain */ + NULL, /* host */ + htons(servicePort), /* port */ + TXTRecordGetLength(&txtRecord), /* txtLen */ + TXTRecordGetBytesPtr(&txtRecord), /* txtRecord */ + NULL, /* callBack */ + NULL /* context */ + ); + TXTRecordDeallocate(&txtRecord); + return std::make_unique(ref); +} diff --git a/components/spotify/cspot/bell/main/platform/apple/WrappedSemaphore.cpp b/components/spotify/cspot/bell/main/platform/apple/WrappedSemaphore.cpp index 505f974b..4fc146ff 100644 --- a/components/spotify/cspot/bell/main/platform/apple/WrappedSemaphore.cpp +++ b/components/spotify/cspot/bell/main/platform/apple/WrappedSemaphore.cpp @@ -2,30 +2,26 @@ using namespace bell; -WrappedSemaphore::WrappedSemaphore(int count) -{ - semaphoreHandle = dispatch_semaphore_create(0); +WrappedSemaphore::WrappedSemaphore(int count) { + semaphoreHandle = dispatch_semaphore_create(0); } -WrappedSemaphore::~WrappedSemaphore() -{ - dispatch_release(semaphoreHandle); +WrappedSemaphore::~WrappedSemaphore() { + dispatch_release(semaphoreHandle); } -int WrappedSemaphore::wait() -{ +int WrappedSemaphore::wait() { - return dispatch_semaphore_wait(semaphoreHandle, DISPATCH_TIME_FOREVER); + return dispatch_semaphore_wait(semaphoreHandle, DISPATCH_TIME_FOREVER); } -int WrappedSemaphore::twait(long milliseconds) -{ - dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (NSEC_PER_SEC / 1000) * milliseconds); +int WrappedSemaphore::twait(long milliseconds) { + dispatch_time_t timeout = + dispatch_time(DISPATCH_TIME_NOW, (NSEC_PER_SEC / 1000) * milliseconds); - return dispatch_semaphore_wait(semaphoreHandle, timeout); + return dispatch_semaphore_wait(semaphoreHandle, timeout); } -void WrappedSemaphore::give() -{ - dispatch_semaphore_signal(semaphoreHandle); +void WrappedSemaphore::give() { + dispatch_semaphore_signal(semaphoreHandle); } diff --git a/components/spotify/cspot/bell/main/platform/esp/MDNSService.cpp b/components/spotify/cspot/bell/main/platform/esp/MDNSService.cpp index 81f269cf..b568cecc 100644 --- a/components/spotify/cspot/bell/main/platform/esp/MDNSService.cpp +++ b/components/spotify/cspot/bell/main/platform/esp/MDNSService.cpp @@ -6,13 +6,14 @@ using namespace bell; class implMDNSService : public MDNSService { -private: - const std::string type; - const std::string proto; - void unregisterService() { mdns_service_remove(type.c_str(), proto.c_str()); } + private: + const std::string type; + const std::string proto; + void unregisterService() { mdns_service_remove(type.c_str(), proto.c_str()); } -public: - implMDNSService(std::string type, std::string proto) : type(type), proto(proto) { }; + public: + implMDNSService(std::string type, std::string proto) + : type(type), proto(proto){}; }; /** @@ -21,30 +22,25 @@ public: **/ std::unique_ptr MDNSService::registerService( - const std::string& serviceName, - const std::string& serviceType, - const std::string& serviceProto, - const std::string& serviceHost, - int servicePort, - const std::map txtData -) { - std::vector txtItems; - txtItems.reserve(txtData.size()); - for (auto& data : txtData) { - mdns_txt_item_t item; - item.key = data.first.c_str(); - item.value = data.second.c_str(); - txtItems.push_back(item); - } + const std::string& serviceName, const std::string& serviceType, + const std::string& serviceProto, const std::string& serviceHost, + int servicePort, const std::map txtData) { + std::vector txtItems; + txtItems.reserve(txtData.size()); + for (auto& data : txtData) { + mdns_txt_item_t item; + item.key = data.first.c_str(); + item.value = data.second.c_str(); + txtItems.push_back(item); + } - mdns_service_add( - serviceName.c_str(), /* instance_name */ - serviceType.c_str(), /* service_type */ - serviceProto.c_str(), /* proto */ - servicePort, /* port */ - txtItems.data(), /* txt */ - txtItems.size() /* num_items */ - ); + mdns_service_add(serviceName.c_str(), /* instance_name */ + serviceType.c_str(), /* service_type */ + serviceProto.c_str(), /* proto */ + servicePort, /* port */ + txtItems.data(), /* txt */ + txtItems.size() /* num_items */ + ); - return std::make_unique(serviceType, serviceProto); + return std::make_unique(serviceType, serviceProto); } diff --git a/components/spotify/cspot/bell/main/platform/esp/WrappedSemaphore.cpp b/components/spotify/cspot/bell/main/platform/esp/WrappedSemaphore.cpp index 01d7e4c6..af988b8f 100644 --- a/components/spotify/cspot/bell/main/platform/esp/WrappedSemaphore.cpp +++ b/components/spotify/cspot/bell/main/platform/esp/WrappedSemaphore.cpp @@ -6,36 +6,32 @@ using namespace bell; -WrappedSemaphore::WrappedSemaphore(int count) -{ - semaphoreHandle = xSemaphoreCreateCounting(count, 0); +WrappedSemaphore::WrappedSemaphore(int count) { + semaphoreHandle = xSemaphoreCreateCounting(count, 0); } -WrappedSemaphore::~WrappedSemaphore() -{ - vSemaphoreDelete(semaphoreHandle); +WrappedSemaphore::~WrappedSemaphore() { + vSemaphoreDelete(semaphoreHandle); } -int WrappedSemaphore::wait() -{ - if (xSemaphoreTake(semaphoreHandle, portMAX_DELAY) == pdTRUE) { - return 0; - } +int WrappedSemaphore::wait() { + if (xSemaphoreTake(semaphoreHandle, portMAX_DELAY) == pdTRUE) { + return 0; + } - return 1; + return 1; } -int WrappedSemaphore::twait(long milliseconds) -{ - if (xSemaphoreTake(semaphoreHandle, milliseconds / portTICK_PERIOD_MS) == pdTRUE) { - return 0; - } +int WrappedSemaphore::twait(long milliseconds) { + if (xSemaphoreTake(semaphoreHandle, milliseconds / portTICK_PERIOD_MS) == + pdTRUE) { + return 0; + } - return 1; + return 1; } -void WrappedSemaphore::give() -{ +void WrappedSemaphore::give() { - xSemaphoreGive(semaphoreHandle); + xSemaphoreGive(semaphoreHandle); } diff --git a/components/spotify/cspot/bell/main/platform/linux/MDNSService.cpp b/components/spotify/cspot/bell/main/platform/linux/MDNSService.cpp index 0109890b..0a9f36f2 100644 --- a/components/spotify/cspot/bell/main/platform/linux/MDNSService.cpp +++ b/components/spotify/cspot/bell/main/platform/linux/MDNSService.cpp @@ -1,9 +1,10 @@ -#include -#include #include +#include #include #include -#include +#include +#include +#include #if __has_include("avahi-client/client.h") #include @@ -14,40 +15,42 @@ #define BELL_DISABLE_AVAHI #endif -#include "mdnssvc.h" #include "BellLogger.h" #include "MDNSService.h" +#include "mdnssvc.h" using namespace bell; #ifndef BELL_DISABLE_AVAHI -static void groupHandler(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata) { } +static void groupHandler(AvahiEntryGroup* g, AvahiEntryGroupState state, + AVAHI_GCC_UNUSED void* userdata) {} #endif class implMDNSService : public MDNSService { -private: + private: #ifndef BELL_DISABLE_AVAHI - AvahiEntryGroup *avahiGroup; + AvahiEntryGroup* avahiGroup; #endif - struct mdns_service* service; + struct mdns_service* service; -public: + public: #ifndef BELL_DISABLE_AVAHI - static AvahiClient *avahiClient; - static AvahiSimplePoll *avahiPoll; + static AvahiClient* avahiClient; + static AvahiSimplePoll* avahiPoll; #endif - static struct mdnsd* mdnsServer; - static in_addr_t host; + static struct mdnsd* mdnsServer; + static in_addr_t host; - implMDNSService(struct mdns_service* service) : service(service) { }; -#ifndef BELL_DISABLE_AVAHI - implMDNSService(AvahiEntryGroup *avahiGroup) : avahiGroup(avahiGroup) { }; -#endif - void unregisterService(); + implMDNSService(struct mdns_service* service) : service(service){}; +#ifndef BELL_DISABLE_AVAHI + implMDNSService(AvahiEntryGroup* avahiGroup) : avahiGroup(avahiGroup){}; +#endif + void unregisterService(); }; struct mdnsd* implMDNSService::mdnsServer = NULL; in_addr_t implMDNSService::host = INADDR_ANY; +static std::mutex registerMutex; #ifndef BELL_DISABLE_AVAHI AvahiClient* implMDNSService::avahiClient = NULL; AvahiSimplePoll* implMDNSService::avahiPoll = NULL; @@ -60,130 +63,135 @@ AvahiSimplePoll* implMDNSService::avahiPoll = NULL; void implMDNSService::unregisterService() { #ifndef BELL_DISABLE_AVAHI - if (avahiGroup) { - avahi_entry_group_free(avahiGroup); - } else + if (avahiGroup) { + avahi_entry_group_free(avahiGroup); + } else #endif - { - mdns_service_remove(implMDNSService::mdnsServer, service); - } + { + mdns_service_remove(implMDNSService::mdnsServer, service); + } } std::unique_ptr MDNSService::registerService( - const std::string& serviceName, - const std::string& serviceType, - const std::string& serviceProto, - const std::string& serviceHost, - int servicePort, - const std::map txtData -) { + const std::string& serviceName, const std::string& serviceType, + const std::string& serviceProto, const std::string& serviceHost, + int servicePort, const std::map txtData) { + std::lock_guard lock(registerMutex); #ifndef BELL_DISABLE_AVAHI - // try avahi first if available - if (!implMDNSService::avahiPoll) { - implMDNSService::avahiPoll = avahi_simple_poll_new(); + // try avahi first if available + if (!implMDNSService::avahiPoll) { + implMDNSService::avahiPoll = avahi_simple_poll_new(); + } + + if (implMDNSService::avahiPoll && !implMDNSService::avahiClient) { + implMDNSService::avahiClient = + avahi_client_new(avahi_simple_poll_get(implMDNSService::avahiPoll), + AvahiClientFlags(0), NULL, NULL, NULL); + } + AvahiEntryGroup* avahiGroup = NULL; + + if (implMDNSService::avahiClient && + (avahiGroup = avahi_entry_group_new(implMDNSService::avahiClient, + groupHandler, NULL)) == NULL) { + BELL_LOG(error, "MDNS", "cannot create service %s", serviceName.c_str()); + } + + if (avahiGroup != NULL) { + AvahiStringList* avahiTxt = NULL; + + for (auto& [key, value] : txtData) { + avahiTxt = + avahi_string_list_add_pair(avahiTxt, key.c_str(), value.c_str()); } - if (implMDNSService::avahiPoll && !implMDNSService::avahiClient) { - implMDNSService::avahiClient = avahi_client_new(avahi_simple_poll_get(implMDNSService::avahiPoll), - AvahiClientFlags(0), NULL, NULL, NULL); + std::string type(serviceType + "." + serviceProto); + int ret = avahi_entry_group_add_service_strlst( + avahiGroup, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, (AvahiPublishFlags)0, + serviceName.c_str(), type.c_str(), NULL, NULL, servicePort, avahiTxt); + avahi_string_list_free(avahiTxt); + + if (ret >= 0) { + ret = avahi_entry_group_commit(avahiGroup); } - - AvahiEntryGroup *avahiGroup; - if (implMDNSService::avahiClient && - (avahiGroup = avahi_entry_group_new(implMDNSService::avahiClient, groupHandler, NULL)) == NULL) { - BELL_LOG(error, "MDNS", "cannot create service %s", serviceName.c_str()); - } - - if (avahiGroup) { - AvahiStringList* avahiTxt = NULL; - - for (auto& [key, value] : txtData) { - avahiTxt = avahi_string_list_add_pair(avahiTxt, key.c_str(), value.c_str()); - } - - std::string type(serviceType + "." + serviceProto); - int ret = avahi_entry_group_add_service_strlst(avahiGroup, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, (AvahiPublishFlags) 0, - serviceName.c_str(), type.c_str(), NULL, NULL, servicePort, avahiTxt); - avahi_string_list_free(avahiTxt); - - if (ret >= 0) { - ret = avahi_entry_group_commit(avahiGroup); - } - - if (ret < 0) { - BELL_LOG(error, "MDNS", "cannot run service %s", serviceName.c_str()); - avahi_entry_group_free(avahiGroup); - } else { - BELL_LOG(info, "MDNS", "using avahi for %s", serviceName.c_str()); - return std::make_unique(avahiGroup); - } + if (ret < 0) { + BELL_LOG(error, "MDNS", "cannot run service %s", serviceName.c_str()); + avahi_entry_group_free(avahiGroup); + } else { + BELL_LOG(info, "MDNS", "using avahi for %s", serviceName.c_str()); + return std::make_unique(avahiGroup); } + } #endif - // avahi failed, use build-in server - struct ifaddrs* ifaddr; + // avahi failed, use build-in server + struct ifaddrs* ifaddr; - // get the host address first - if (serviceHost.size()) { - struct hostent *h = gethostbyname(serviceHost.c_str()); - if (h) { - memcpy(&implMDNSService::host, h->h_addr_list[0], 4); - } + // get the host address first + if (serviceHost.size()) { + struct hostent* h = gethostbyname(serviceHost.c_str()); + if (h) { + memcpy(&implMDNSService::host, h->h_addr_list[0], 4); } + } - // try go guess ifaddr if we have nothing as listening to INADDR_ANY usually does not work - if (implMDNSService::host == INADDR_ANY && getifaddrs(&ifaddr) != -1) { - for (struct ifaddrs* ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { - if (ifa->ifa_addr == NULL || ifa->ifa_addr->sa_family != AF_INET || - !(ifa->ifa_flags & IFF_UP) || !(ifa->ifa_flags & IFF_MULTICAST) || - (ifa->ifa_flags & IFF_LOOPBACK)) continue; + // try go guess ifaddr if we have nothing as listening to INADDR_ANY usually does not work + if (implMDNSService::host == INADDR_ANY && getifaddrs(&ifaddr) != -1) { + for (struct ifaddrs* ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL || ifa->ifa_addr->sa_family != AF_INET || + !(ifa->ifa_flags & IFF_UP) || !(ifa->ifa_flags & IFF_MULTICAST) || + (ifa->ifa_flags & IFF_LOOPBACK)) + continue; - implMDNSService::host = ((struct sockaddr_in*)ifa->ifa_addr)->sin_addr.s_addr; - break; - } - freeifaddrs(ifaddr); - } - - if (!implMDNSService::mdnsServer) { - char hostname[256]; - struct in_addr addr; - - // it's the same, but who knows.. - addr.s_addr = implMDNSService::host; - gethostname(hostname, sizeof(hostname)); - - implMDNSService::mdnsServer = mdnsd_start(addr, false); - - if (implMDNSService::mdnsServer) { - mdnsd_set_hostname(implMDNSService::mdnsServer, hostname, addr); - } + implMDNSService::host = + ((struct sockaddr_in*)ifa->ifa_addr)->sin_addr.s_addr; + break; } + freeifaddrs(ifaddr); + } + + if (!implMDNSService::mdnsServer) { + char hostname[256]; + struct in_addr addr; + + // it's the same, but who knows.. + addr.s_addr = implMDNSService::host; + gethostname(hostname, sizeof(hostname)); + + implMDNSService::mdnsServer = mdnsd_start(addr, false); if (implMDNSService::mdnsServer) { - std::vector txt; - std::vector> txtStr; + mdnsd_set_hostname(implMDNSService::mdnsServer, hostname, addr); + } + } - for (auto& [key, value] : txtData) { - auto str = make_unique(key + "=" + value); - txtStr.push_back(std::move(str)); - txt.push_back(txtStr.back()->c_str()); - } + if (implMDNSService::mdnsServer) { + std::vector txt; + std::vector> txtStr; - txt.push_back(NULL); - std::string type(serviceType + "." + serviceProto + ".local"); - - BELL_LOG(info, "MDNS", "using built-in mDNS for %s", serviceName.c_str()); - struct mdns_service* mdnsService = mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(), - type.c_str(), servicePort, NULL, txt.data()); - if (mdnsService) { - auto service = mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(), - type.c_str(), servicePort, NULL, txt.data()); - - return std::make_unique(service); - } + for (auto& [key, value] : txtData) { + auto str = make_unique(key + "=" + value); + txtStr.push_back(std::move(str)); + txt.push_back(txtStr.back()->c_str()); } - BELL_LOG(error, "MDNS", "cannot start any mDNS listener for %s", serviceName.c_str()); - return NULL; + txt.push_back(NULL); + std::string type(serviceType + "." + serviceProto + ".local"); + + BELL_LOG(info, "MDNS", "using built-in mDNS for %s", serviceName.c_str()); + struct mdns_service* mdnsService = + mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(), + type.c_str(), servicePort, NULL, txt.data()); + if (mdnsService) { + auto service = + mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(), + type.c_str(), servicePort, NULL, txt.data()); + + return std::make_unique(service); + } + } + + BELL_LOG(error, "MDNS", "cannot start any mDNS listener for %s", + serviceName.c_str()); + return NULL; } diff --git a/components/spotify/cspot/bell/main/platform/linux/WrappedSemaphore.cpp b/components/spotify/cspot/bell/main/platform/linux/WrappedSemaphore.cpp index 131d0b9e..ca44436d 100644 --- a/components/spotify/cspot/bell/main/platform/linux/WrappedSemaphore.cpp +++ b/components/spotify/cspot/bell/main/platform/linux/WrappedSemaphore.cpp @@ -1,33 +1,33 @@ #include "WrappedSemaphore.h" +#include using namespace bell; -WrappedSemaphore::WrappedSemaphore(int count) -{ - sem_init(&this->semaphoreHandle, 0, 0); // eek pointer +WrappedSemaphore::WrappedSemaphore(int count) { + sem_init(&this->semaphoreHandle, 0, 0); // eek pointer } -WrappedSemaphore::~WrappedSemaphore() -{ - sem_destroy(&this->semaphoreHandle); +WrappedSemaphore::~WrappedSemaphore() { + sem_destroy(&this->semaphoreHandle); } -int WrappedSemaphore::wait() -{ - sem_wait(&this->semaphoreHandle); - return 0; +int WrappedSemaphore::wait() { + sem_wait(&this->semaphoreHandle); + return 0; } -int WrappedSemaphore::twait(long milliseconds) -{ - // wait on semaphore with timeout - struct timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); - ts.tv_nsec += (milliseconds % 1000) * 1000000; - return sem_timedwait(&this->semaphoreHandle, &ts); +int WrappedSemaphore::twait(long milliseconds) { + // wait on semaphore with timeout + struct timespec ts; + struct timeval tv; + + gettimeofday(&tv, 0); + + ts.tv_sec = tv.tv_sec + milliseconds / 1000; + ts.tv_nsec = tv.tv_usec * 1000 + (milliseconds % 1000) * 1000000; + return sem_timedwait(&this->semaphoreHandle, &ts); } -void WrappedSemaphore::give() -{ - sem_post(&this->semaphoreHandle); +void WrappedSemaphore::give() { + sem_post(&this->semaphoreHandle); } diff --git a/components/spotify/cspot/bell/main/platform/win32/MDNSService.cpp b/components/spotify/cspot/bell/main/platform/win32/MDNSService.cpp index fb91061a..7ee64a17 100644 --- a/components/spotify/cspot/bell/main/platform/win32/MDNSService.cpp +++ b/components/spotify/cspot/bell/main/platform/win32/MDNSService.cpp @@ -1,8 +1,8 @@ -#include #include +#include -#include "MDNSService.h" #include "BellLogger.h" +#include "MDNSService.h" #ifdef _WIN32 #include @@ -12,19 +12,20 @@ #else #include #include "mdns.h" -#include #endif using namespace bell; class implMDNSService : public MDNSService { -private: - struct mdns_service* service; - void unregisterService(void) { mdns_service_remove(implMDNSService::mdnsServer, service); }; - -public: - static struct mdnsd* mdnsServer; - implMDNSService(struct mdns_service* service) : service(service) { }; + private: + struct mdns_service* service; + void unregisterService(void) { + mdns_service_remove(implMDNSService::mdnsServer, service); + }; + + public: + static struct mdnsd* mdnsServer; + implMDNSService(struct mdns_service* service) : service(service){}; }; /** @@ -32,58 +33,66 @@ public: **/ struct mdnsd* implMDNSService::mdnsServer = NULL; +static std::mutex registerMutex; std::unique_ptr MDNSService::registerService( - const std::string& serviceName, - const std::string& serviceType, - const std::string& serviceProto, - const std::string& serviceHost, - int servicePort, - const std::map txtData -) { - if (!implMDNSService::mdnsServer) { - char hostname[128]; - gethostname(hostname, sizeof(hostname)); + const std::string& serviceName, const std::string& serviceType, + const std::string& serviceProto, const std::string& serviceHost, + int servicePort, const std::map txtData) { + std::lock_guard lock(registerMutex); + if (!implMDNSService::mdnsServer) { + char hostname[128]; + gethostname(hostname, sizeof(hostname)); - struct sockaddr_in* host = NULL; - ULONG size = sizeof(IP_ADAPTER_ADDRESSES) * 64; - IP_ADAPTER_ADDRESSES* adapters = (IP_ADAPTER_ADDRESSES*)malloc(size); - int ret = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_GATEWAYS | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_ANYCAST, 0, adapters, &size); + struct sockaddr_in* host = NULL; + ULONG size = sizeof(IP_ADAPTER_ADDRESSES) * 64; + IP_ADAPTER_ADDRESSES* adapters = (IP_ADAPTER_ADDRESSES*)malloc(size); + int ret = GetAdaptersAddresses(AF_UNSPEC, + GAA_FLAG_INCLUDE_GATEWAYS | + GAA_FLAG_SKIP_MULTICAST | + GAA_FLAG_SKIP_ANYCAST, + 0, adapters, &size); - for (PIP_ADAPTER_ADDRESSES adapter = adapters; adapter && !host; adapter = adapter->Next) { - if (adapter->TunnelType == TUNNEL_TYPE_TEREDO) continue; - if (adapter->OperStatus != IfOperStatusUp) continue; + for (PIP_ADAPTER_ADDRESSES adapter = adapters; adapter && !host; + adapter = adapter->Next) { + if (adapter->TunnelType == TUNNEL_TYPE_TEREDO) + continue; + if (adapter->OperStatus != IfOperStatusUp) + continue; - for (IP_ADAPTER_UNICAST_ADDRESS* unicast = adapter->FirstUnicastAddress; unicast; - unicast = unicast->Next) { - if (adapter->FirstGatewayAddress && unicast->Address.lpSockaddr->sa_family == AF_INET) { - host = (struct sockaddr_in*)unicast->Address.lpSockaddr; - BELL_LOG(info, "mdns", "mDNS on interface %s", inet_ntoa(host->sin_addr)); - implMDNSService::mdnsServer = mdnsd_start(host->sin_addr, false); - break; - } - } + for (IP_ADAPTER_UNICAST_ADDRESS* unicast = adapter->FirstUnicastAddress; + unicast; unicast = unicast->Next) { + if (adapter->FirstGatewayAddress && + unicast->Address.lpSockaddr->sa_family == AF_INET) { + host = (struct sockaddr_in*)unicast->Address.lpSockaddr; + BELL_LOG(info, "mdns", "mDNS on interface %s", + inet_ntoa(host->sin_addr)); + implMDNSService::mdnsServer = mdnsd_start(host->sin_addr, false); + break; } - - assert(implMDNSService::mdnsServer); - mdnsd_set_hostname(implMDNSService::mdnsServer, hostname, host->sin_addr); - free(adapters); + } } - std::vector txt; - std::vector> txtStr; + assert(implMDNSService::mdnsServer); + mdnsd_set_hostname(implMDNSService::mdnsServer, hostname, host->sin_addr); + free(adapters); + } - for (auto& [key, value] : txtData) { - auto str = make_unique(key + "=" + value); - txtStr.push_back(std::move(str)); - txt.push_back(txtStr.back()->c_str()); - } - txt.push_back(NULL); + std::vector txt; + std::vector> txtStr; - std::string type(serviceType + "." + serviceProto + ".local"); + for (auto& [key, value] : txtData) { + auto str = make_unique(key + "=" + value); + txtStr.push_back(std::move(str)); + txt.push_back(txtStr.back()->c_str()); + } + txt.push_back(NULL); - auto service = mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(), - type.c_str(), servicePort, NULL, txt.data()); + std::string type(serviceType + "." + serviceProto + ".local"); - return std::make_unique(service); + auto service = + mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(), + type.c_str(), servicePort, NULL, txt.data()); + + return std::make_unique(service); } diff --git a/components/spotify/cspot/bell/main/platform/win32/WrappedSemaphore.cpp b/components/spotify/cspot/bell/main/platform/win32/WrappedSemaphore.cpp index 4ed8c491..20c33825 100644 --- a/components/spotify/cspot/bell/main/platform/win32/WrappedSemaphore.cpp +++ b/components/spotify/cspot/bell/main/platform/win32/WrappedSemaphore.cpp @@ -2,28 +2,24 @@ using namespace bell; -WrappedSemaphore::WrappedSemaphore(int count) -{ - this->semaphoreHandle = CreateSemaphore(NULL, 0, count, NULL); +WrappedSemaphore::WrappedSemaphore(int count) { + this->semaphoreHandle = CreateSemaphore(NULL, 0, count, NULL); } -WrappedSemaphore::~WrappedSemaphore() -{ - CloseHandle(this->semaphoreHandle); +WrappedSemaphore::~WrappedSemaphore() { + CloseHandle(this->semaphoreHandle); } -int WrappedSemaphore::wait() -{ - WaitForSingleObject(this->semaphoreHandle, INFINITE); - return 0; +int WrappedSemaphore::wait() { + WaitForSingleObject(this->semaphoreHandle, INFINITE); + return 0; } -int WrappedSemaphore::twait(long milliseconds) -{ - return WaitForSingleObject(this->semaphoreHandle, milliseconds) != WAIT_OBJECT_0; +int WrappedSemaphore::twait(long milliseconds) { + return WaitForSingleObject(this->semaphoreHandle, milliseconds) != + WAIT_OBJECT_0; } -void WrappedSemaphore::give() -{ - ReleaseSemaphore(this->semaphoreHandle, 1, NULL); +void WrappedSemaphore::give() { + ReleaseSemaphore(this->semaphoreHandle, 1, NULL); } diff --git a/components/spotify/cspot/bell/main/platform/win32/win32shim.h b/components/spotify/cspot/bell/main/platform/win32/win32shim.h index 57fbedfa..f06bebf3 100644 --- a/components/spotify/cspot/bell/main/platform/win32/win32shim.h +++ b/components/spotify/cspot/bell/main/platform/win32/win32shim.h @@ -7,8 +7,12 @@ #define strcasecmp stricmp #define strncasecmp _strnicmp -#define bzero(p,n) memset(p,0,n) -#define usleep(x) Sleep((x)/1000) +#define bzero(p, n) memset(p, 0, n) +#define usleep(x) Sleep((x) / 1000) -inline size_t read(int sock, char* buf, size_t n) { return recv(sock, buf, n, 0); } -inline int write(int sock, const char* buf, size_t n) { return send(sock, buf, n, 0); } +inline size_t read(int sock, char* buf, size_t n) { + return recv(sock, buf, n, 0); +} +inline int write(int sock, const char* buf, size_t n) { + return send(sock, buf, n, 0); +} diff --git a/components/spotify/cspot/bell/main/utilities/BellLogger.cpp b/components/spotify/cspot/bell/main/utilities/BellLogger.cpp index 975b2975..46ec6d92 100644 --- a/components/spotify/cspot/bell/main/utilities/BellLogger.cpp +++ b/components/spotify/cspot/bell/main/utilities/BellLogger.cpp @@ -3,9 +3,13 @@ bell::AbstractLogger* bell::bellGlobalLogger; void bell::setDefaultLogger() { - bell::bellGlobalLogger = new bell::BellLogger(); + bell::bellGlobalLogger = new bell::BellLogger(); } void bell::enableSubmoduleLogging() { - bell::bellGlobalLogger->enableSubmodule = true; -} \ No newline at end of file + bell::bellGlobalLogger->enableSubmodule = true; +} + +void bell::enableTimestampLogging() { + bell::bellGlobalLogger->enableTimestamp = true; +} diff --git a/components/spotify/cspot/bell/main/utilities/BellUtils.cpp b/components/spotify/cspot/bell/main/utilities/BellUtils.cpp index f77a1368..902252f3 100644 --- a/components/spotify/cspot/bell/main/utilities/BellUtils.cpp +++ b/components/spotify/cspot/bell/main/utilities/BellUtils.cpp @@ -1,5 +1,11 @@ #include "BellUtils.h" +#include // for free +#include // for mt19937, uniform_int_distribution, random_device +#ifdef ESP_PLATFORM +#include "esp_system.h" +#endif + std::string bell::generateRandomUUID() { static std::random_device dev; static std::mt19937 rng(dev()); diff --git a/components/spotify/cspot/bell/main/utilities/Crypto.cpp b/components/spotify/cspot/bell/main/utilities/Crypto.cpp index ab4883b0..28c2d31d 100644 --- a/components/spotify/cspot/bell/main/utilities/Crypto.cpp +++ b/components/spotify/cspot/bell/main/utilities/Crypto.cpp @@ -1,5 +1,17 @@ #include "Crypto.h" +#include // for mbedtls_base64_encode, mbedtls_base64_... +#include // for mbedtls_mpi_free, mbedtls_mpi_init +#include // for mbedtls_ctr_drbg_free, mbedtls_ctr_drb... +#include // for mbedtls_entropy_free, mbedtls_entropy_... +#include // for mbedtls_pkcs5_pbkdf2_hmac +#include // for uint8_t +#include // for runtime_error + +extern "C" { +#include "aes.h" // for AES_ECB_decrypt, AES_init_ctx, AES_ctx +} + CryptoMbedTLS::CryptoMbedTLS() {} CryptoMbedTLS::~CryptoMbedTLS() { diff --git a/components/spotify/cspot/bell/main/utilities/NanoPBExtensions.cpp b/components/spotify/cspot/bell/main/utilities/NanoPBExtensions.cpp new file mode 100644 index 00000000..222a8699 --- /dev/null +++ b/components/spotify/cspot/bell/main/utilities/NanoPBExtensions.cpp @@ -0,0 +1,59 @@ +#include "NanoPBExtensions.h" + +#include // for optional +#include // for string +#include // for vector + +#include +#include + +bool bell::nanopb::encodeString(pb_ostream_t* stream, const pb_field_t* field, + void* const* arg) { + auto& str = *static_cast(*arg); + + if (str.size() > 0) { + if (!pb_encode_tag_for_field(stream, field)) { + return false; + } + + if (!pb_encode_string(stream, (uint8_t*)str.c_str(), str.size())) { + return false; + } + } + + return true; +} + +bool bell::nanopb::encodeBoolean(pb_ostream_t* stream, const pb_field_t* field, + void* const* arg) { + auto& boolean = *static_cast*>(*arg); + + if (boolean.has_value()) { + if (!pb_encode_tag_for_field(stream, field)) { + return false; + } + + if (!pb_encode_varint(stream, boolean.value())) { + return false; + } + } + + return true; +} + +bool bell::nanopb::encodeVector(pb_ostream_t* stream, const pb_field_t* field, + void* const* arg) { + auto& vector = *static_cast*>(*arg); + + if (vector.size() > 0) { + if (!pb_encode_tag_for_field(stream, field)) { + return false; + } + + if (!pb_encode_string(stream, vector.data(), vector.size())) { + return false; + } + } + + return true; +} \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/utilities/NanoPBHelper.cpp b/components/spotify/cspot/bell/main/utilities/NanoPBHelper.cpp index 9a4170c7..4bd626d5 100644 --- a/components/spotify/cspot/bell/main/utilities/NanoPBHelper.cpp +++ b/components/spotify/cspot/bell/main/utilities/NanoPBHelper.cpp @@ -1,78 +1,82 @@ #include "NanoPBHelper.h" -static bool vectorWrite(pb_ostream_t *stream, const pb_byte_t *buf, size_t count) -{ - size_t i; - auto *dest = reinterpret_cast *>(stream->state); +#include // for malloc +#include // for strcpy, memcpy, strlen +#include // for copy +#include // for uint8_t - dest->insert(dest->end(), buf, buf + count); +#include "pb_encode.h" // for pb_ostream_s, pb_encode, pb_get_encoded_size - return true; +static bool vectorWrite(pb_ostream_t* stream, const pb_byte_t* buf, + size_t count) { + size_t i; + auto* dest = reinterpret_cast*>(stream->state); + + dest->insert(dest->end(), buf, buf + count); + + return true; } -pb_ostream_t pb_ostream_from_vector(std::vector &vec) -{ - pb_ostream_t stream; +pb_ostream_t pb_ostream_from_vector(std::vector& vec) { + pb_ostream_t stream; - stream.callback = &vectorWrite; - stream.state = &vec; - stream.max_size = 100000; - stream.bytes_written = 0; + stream.callback = &vectorWrite; + stream.state = &vec; + stream.max_size = 100000; + stream.bytes_written = 0; - return stream; + return stream; } -std::vector pbEncode(const pb_msgdesc_t *fields, const void *src_struct) -{ - std::vector vecData(0); - pb_ostream_t stream = pb_ostream_from_vector(vecData); - pb_encode(&stream, fields, src_struct); +std::vector pbEncode(const pb_msgdesc_t* fields, + const void* src_struct) { + std::vector vecData(0); + pb_ostream_t stream = pb_ostream_from_vector(vecData); + pb_encode(&stream, fields, src_struct); - return vecData; + return vecData; } -void packString(char *&dst, std::string stringToPack) -{ - dst = (char *)malloc((strlen(stringToPack.c_str()) + 1) * sizeof(char)); - strcpy(dst, stringToPack.c_str()); +void packString(char*& dst, std::string stringToPack) { + dst = (char*)malloc((strlen(stringToPack.c_str()) + 1) * sizeof(char)); + strcpy(dst, stringToPack.c_str()); } -pb_bytes_array_t* vectorToPbArray(const std::vector& vectorToPack) -{ - auto size = static_cast(vectorToPack.size()); - auto result = static_cast( - malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(size))); - result->size = size; - memcpy(result->bytes, vectorToPack.data(), size); - return result; +pb_bytes_array_t* vectorToPbArray(const std::vector& vectorToPack) { + auto size = static_cast(vectorToPack.size()); + auto result = + static_cast(malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(size))); + result->size = size; + memcpy(result->bytes, vectorToPack.data(), size); + return result; } -void pbPutString(const std::string &stringToPack, char* dst) { - stringToPack.copy(dst, stringToPack.size()); - dst[stringToPack.size()] = '\0'; +void pbPutString(const std::string& stringToPack, char* dst) { + stringToPack.copy(dst, stringToPack.size()); + dst[stringToPack.size()] = '\0'; } -void pbPutCharArray(const char * stringToPack, char* dst) { - // copy stringToPack into dst - strcpy(dst, stringToPack); - //dst[sizeof(stringToPack)-1] = '\0'; +void pbPutCharArray(const char* stringToPack, char* dst) { + // copy stringToPack into dst + strcpy(dst, stringToPack); + //dst[sizeof(stringToPack)-1] = '\0'; } -void pbPutBytes(const std::vector &data, pb_bytes_array_t &dst) { - dst.size = data.size(); - std::copy(data.begin(), data.end(), dst.bytes); +void pbPutBytes(const std::vector& data, pb_bytes_array_t& dst) { + dst.size = data.size(); + std::copy(data.begin(), data.end(), dst.bytes); } std::vector pbArrayToVector(pb_bytes_array_t* pbArray) { - return std::vector(pbArray->bytes, pbArray->bytes + pbArray->size); + return std::vector(pbArray->bytes, pbArray->bytes + pbArray->size); } -const char *pb_encode_to_string(const pb_msgdesc_t *fields, const void *data) { - size_t len; - pb_get_encoded_size(&len, fields, data); - auto *buf = static_cast(malloc(len + 1)); - auto ostream = pb_ostream_from_buffer(buf, len); - pb_encode(&ostream, fields, data); - buf[len] = '\0'; - return reinterpret_cast(buf); +const char* pb_encode_to_string(const pb_msgdesc_t* fields, const void* data) { + size_t len; + pb_get_encoded_size(&len, fields, data); + auto* buf = static_cast(malloc(len + 1)); + auto ostream = pb_ostream_from_buffer(buf, len); + pb_encode(&ostream, fields, data); + buf[len] = '\0'; + return reinterpret_cast(buf); } \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/utilities/aes.c b/components/spotify/cspot/bell/main/utilities/aes.c index 2bd115e2..2ed66c69 100644 --- a/components/spotify/cspot/bell/main/utilities/aes.c +++ b/components/spotify/cspot/bell/main/utilities/aes.c @@ -31,12 +31,13 @@ NOTE: String length must be evenly divisible by 16byte (str_len % 16 == 0) */ - +#include // for uint8_t /*****************************************************************************/ /* Includes: */ /*****************************************************************************/ -#include // CBC mode, for memset -#include "aes.h" +#include // for memcpy, size_t + +#include "aes.h" // for AES_ctx, AES_BLOCKLEN, CBC, ECB, CTR, AES192 /*****************************************************************************/ /* Defines: */ @@ -45,80 +46,87 @@ NOTE: String length must be evenly divisible by 16byte (str_len % 16 == 0) #define Nb 4 #if defined(AES256) && (AES256 == 1) - #define Nk 8 - #define Nr 14 +#define Nk 8 +#define Nr 14 #elif defined(AES192) && (AES192 == 1) - #define Nk 6 - #define Nr 12 +#define Nk 6 +#define Nr 12 #else - #define Nk 4 // The number of 32 bit words in a key. - #define Nr 10 // The number of rounds in AES Cipher. +#define Nk 4 // The number of 32 bit words in a key. +#define Nr 10 // The number of rounds in AES Cipher. #endif -// jcallan@github points out that declaring Multiply as a function +// jcallan@github points out that declaring Multiply as a function // reduces code size considerably with the Keil ARM compiler. // See this link for more information: https://github.com/kokke/tiny-AES-C/pull/3 #ifndef MULTIPLY_AS_A_FUNCTION - #define MULTIPLY_AS_A_FUNCTION 0 +#define MULTIPLY_AS_A_FUNCTION 0 #endif - - - /*****************************************************************************/ /* Private variables: */ /*****************************************************************************/ // state - array holding the intermediate results during decryption. typedef uint8_t state_t[4][4]; - - // The lookup-tables are marked const so they can be placed in read-only storage instead of RAM -// The numbers below can be computed dynamically trading ROM for RAM - +// The numbers below can be computed dynamically trading ROM for RAM - // This can be useful in (embedded) bootloader applications, where ROM is often limited. static const uint8_t sbox[256] = { - //0 1 2 3 4 5 6 7 8 9 A B C D E F - 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, - 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, - 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, - 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, - 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, - 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, - 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, - 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, - 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, - 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, - 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, - 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, - 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, - 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, - 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, - 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 }; + //0 1 2 3 4 5 6 7 8 9 A B C D E F + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, + 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, + 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, + 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, + 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, + 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, + 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, + 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, + 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, + 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, + 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, + 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, + 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, + 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, + 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, + 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, + 0xb0, 0x54, 0xbb, 0x16}; #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) static const uint8_t rsbox[256] = { - 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, - 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, - 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, - 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, - 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, - 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, - 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, - 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, - 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, - 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, - 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, - 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, - 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, - 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, - 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, - 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d }; + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, + 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, + 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, + 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, + 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, + 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, + 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, + 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, + 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, + 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, + 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, + 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, + 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, + 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, + 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, + 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, + 0x55, 0x21, 0x0c, 0x7d}; #endif -// The round constant word array, Rcon[i], contains the values given by +// The round constant word array, Rcon[i], contains the values given by // x to the power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8) -static const uint8_t Rcon[11] = { - 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 }; +static const uint8_t Rcon[11] = {0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, + 0x20, 0x40, 0x80, 0x1b, 0x36}; /* * Jordan Goulder points out in PR #12 (https://github.com/kokke/tiny-AES-C/pull/12), @@ -130,7 +138,6 @@ static const uint8_t Rcon[11] = { * up to rcon[8] for AES-192, up to rcon[7] for AES-256. rcon[0] is not used in AES algorithm." */ - /*****************************************************************************/ /* Private functions: */ /*****************************************************************************/ @@ -142,15 +149,13 @@ static uint8_t getSBoxValue(uint8_t num) */ #define getSBoxValue(num) (sbox[(num)]) -// This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states. -static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key) -{ +// This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states. +static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key) { unsigned i, j, k; - uint8_t tempa[4]; // Used for the column/row operations - + uint8_t tempa[4]; // Used for the column/row operations + // The first round key is the key itself. - for (i = 0; i < Nk; ++i) - { + for (i = 0; i < Nk; ++i) { RoundKey[(i * 4) + 0] = Key[(i * 4) + 0]; RoundKey[(i * 4) + 1] = Key[(i * 4) + 1]; RoundKey[(i * 4) + 2] = Key[(i * 4) + 2]; @@ -158,19 +163,16 @@ static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key) } // All other round keys are found from the previous round keys. - for (i = Nk; i < Nb * (Nr + 1); ++i) - { + for (i = Nk; i < Nb * (Nr + 1); ++i) { { k = (i - 1) * 4; - tempa[0]=RoundKey[k + 0]; - tempa[1]=RoundKey[k + 1]; - tempa[2]=RoundKey[k + 2]; - tempa[3]=RoundKey[k + 3]; - + tempa[0] = RoundKey[k + 0]; + tempa[1] = RoundKey[k + 1]; + tempa[2] = RoundKey[k + 2]; + tempa[3] = RoundKey[k + 3]; } - if (i % Nk == 0) - { + if (i % Nk == 0) { // This function shifts the 4 bytes in a word to the left once. // [a0,a1,a2,a3] becomes [a1,a2,a3,a0] @@ -183,7 +185,7 @@ static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key) tempa[3] = u8tmp; } - // SubWord() is a function that takes a four-byte input word and + // SubWord() is a function that takes a four-byte input word and // applies the S-box to each of the four bytes to produce an output word. // Function Subword() @@ -194,11 +196,10 @@ static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key) tempa[3] = getSBoxValue(tempa[3]); } - tempa[0] = tempa[0] ^ Rcon[i/Nk]; + tempa[0] = tempa[0] ^ Rcon[i / Nk]; } #if defined(AES256) && (AES256 == 1) - if (i % Nk == 4) - { + if (i % Nk == 4) { // Function Subword() { tempa[0] = getSBoxValue(tempa[0]); @@ -208,7 +209,8 @@ static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key) } } #endif - j = i * 4; k=(i - Nk) * 4; + j = i * 4; + k = (i - Nk) * 4; RoundKey[j + 0] = RoundKey[k + 0] ^ tempa[0]; RoundKey[j + 1] = RoundKey[k + 1] ^ tempa[1]; RoundKey[j + 2] = RoundKey[k + 2] ^ tempa[2]; @@ -216,31 +218,27 @@ static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key) } } -void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key) -{ +void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key) { KeyExpansion(ctx->RoundKey, key); } #if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1)) -void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv) -{ +void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, + const uint8_t* iv) { KeyExpansion(ctx->RoundKey, key); - memcpy (ctx->Iv, iv, AES_BLOCKLEN); + memcpy(ctx->Iv, iv, AES_BLOCKLEN); } -void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv) -{ - memcpy (ctx->Iv, iv, AES_BLOCKLEN); +void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv) { + memcpy(ctx->Iv, iv, AES_BLOCKLEN); } #endif // This function adds the round key to state. // The round key is added to the state by an XOR function. -static void AddRoundKey(uint8_t round, state_t* state, const uint8_t* RoundKey) -{ - uint8_t i,j; - for (i = 0; i < 4; ++i) - { - for (j = 0; j < 4; ++j) - { +static void AddRoundKey(uint8_t round, state_t* state, + const uint8_t* RoundKey) { + uint8_t i, j; + for (i = 0; i < 4; ++i) { + for (j = 0; j < 4; ++j) { (*state)[i][j] ^= RoundKey[(round * Nb * 4) + (i * Nb) + j]; } } @@ -248,13 +246,10 @@ static void AddRoundKey(uint8_t round, state_t* state, const uint8_t* RoundKey) // The SubBytes Function Substitutes the values in the // state matrix with values in an S-box. -static void SubBytes(state_t* state) -{ +static void SubBytes(state_t* state) { uint8_t i, j; - for (i = 0; i < 4; ++i) - { - for (j = 0; j < 4; ++j) - { + for (i = 0; i < 4; ++i) { + for (j = 0; j < 4; ++j) { (*state)[j][i] = getSBoxValue((*state)[j][i]); } } @@ -263,52 +258,56 @@ static void SubBytes(state_t* state) // The ShiftRows() function shifts the rows in the state to the left. // Each row is shifted with different offset. // Offset = Row number. So the first row is not shifted. -static void ShiftRows(state_t* state) -{ +static void ShiftRows(state_t* state) { uint8_t temp; - // Rotate first row 1 columns to left - temp = (*state)[0][1]; + // Rotate first row 1 columns to left + temp = (*state)[0][1]; (*state)[0][1] = (*state)[1][1]; (*state)[1][1] = (*state)[2][1]; (*state)[2][1] = (*state)[3][1]; (*state)[3][1] = temp; - // Rotate second row 2 columns to left - temp = (*state)[0][2]; + // Rotate second row 2 columns to left + temp = (*state)[0][2]; (*state)[0][2] = (*state)[2][2]; (*state)[2][2] = temp; - temp = (*state)[1][2]; + temp = (*state)[1][2]; (*state)[1][2] = (*state)[3][2]; (*state)[3][2] = temp; // Rotate third row 3 columns to left - temp = (*state)[0][3]; + temp = (*state)[0][3]; (*state)[0][3] = (*state)[3][3]; (*state)[3][3] = (*state)[2][3]; (*state)[2][3] = (*state)[1][3]; (*state)[1][3] = temp; } -static uint8_t xtime(uint8_t x) -{ - return ((x<<1) ^ (((x>>7) & 1) * 0x1b)); +static uint8_t xtime(uint8_t x) { + return ((x << 1) ^ (((x >> 7) & 1) * 0x1b)); } // MixColumns function mixes the columns of the state matrix -static void MixColumns(state_t* state) -{ +static void MixColumns(state_t* state) { uint8_t i; uint8_t Tmp, Tm, t; - for (i = 0; i < 4; ++i) - { - t = (*state)[i][0]; - Tmp = (*state)[i][0] ^ (*state)[i][1] ^ (*state)[i][2] ^ (*state)[i][3] ; - Tm = (*state)[i][0] ^ (*state)[i][1] ; Tm = xtime(Tm); (*state)[i][0] ^= Tm ^ Tmp ; - Tm = (*state)[i][1] ^ (*state)[i][2] ; Tm = xtime(Tm); (*state)[i][1] ^= Tm ^ Tmp ; - Tm = (*state)[i][2] ^ (*state)[i][3] ; Tm = xtime(Tm); (*state)[i][2] ^= Tm ^ Tmp ; - Tm = (*state)[i][3] ^ t ; Tm = xtime(Tm); (*state)[i][3] ^= Tm ^ Tmp ; + for (i = 0; i < 4; ++i) { + t = (*state)[i][0]; + Tmp = (*state)[i][0] ^ (*state)[i][1] ^ (*state)[i][2] ^ (*state)[i][3]; + Tm = (*state)[i][0] ^ (*state)[i][1]; + Tm = xtime(Tm); + (*state)[i][0] ^= Tm ^ Tmp; + Tm = (*state)[i][1] ^ (*state)[i][2]; + Tm = xtime(Tm); + (*state)[i][1] ^= Tm ^ Tmp; + Tm = (*state)[i][2] ^ (*state)[i][3]; + Tm = xtime(Tm); + (*state)[i][2] ^= Tm ^ Tmp; + Tm = (*state)[i][3] ^ t; + Tm = xtime(Tm); + (*state)[i][3] ^= Tm ^ Tmp; } } @@ -317,21 +316,20 @@ static void MixColumns(state_t* state) // The compiler seems to be able to vectorize the operation better this way. // See https://github.com/kokke/tiny-AES-c/pull/34 #if MULTIPLY_AS_A_FUNCTION -static uint8_t Multiply(uint8_t x, uint8_t y) -{ - return (((y & 1) * x) ^ - ((y>>1 & 1) * xtime(x)) ^ - ((y>>2 & 1) * xtime(xtime(x))) ^ - ((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ - ((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))); /* this last call to xtime() can be omitted */ - } +static uint8_t Multiply(uint8_t x, uint8_t y) { + return (((y & 1) * x) ^ ((y >> 1 & 1) * xtime(x)) ^ + ((y >> 2 & 1) * xtime(xtime(x))) ^ + ((y >> 3 & 1) * xtime(xtime(xtime(x)))) ^ + ((y >> 4 & 1) * + xtime(xtime(xtime( + xtime(x)))))); /* this last call to xtime() can be omitted */ +} #else -#define Multiply(x, y) \ - ( ((y & 1) * x) ^ \ - ((y>>1 & 1) * xtime(x)) ^ \ - ((y>>2 & 1) * xtime(xtime(x))) ^ \ - ((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ \ - ((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))) \ +#define Multiply(x, y) \ + (((y & 1) * x) ^ ((y >> 1 & 1) * xtime(x)) ^ \ + ((y >> 2 & 1) * xtime(xtime(x))) ^ \ + ((y >> 3 & 1) * xtime(xtime(xtime(x)))) ^ \ + ((y >> 4 & 1) * xtime(xtime(xtime(xtime(x)))))) #endif @@ -347,51 +345,48 @@ static uint8_t getSBoxInvert(uint8_t num) // MixColumns function mixes the columns of the state matrix. // The method used to multiply may be difficult to understand for the inexperienced. // Please use the references to gain more information. -static void InvMixColumns(state_t* state) -{ +static void InvMixColumns(state_t* state) { int i; uint8_t a, b, c, d; - for (i = 0; i < 4; ++i) - { + for (i = 0; i < 4; ++i) { a = (*state)[i][0]; b = (*state)[i][1]; c = (*state)[i][2]; d = (*state)[i][3]; - (*state)[i][0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09); - (*state)[i][1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d); - (*state)[i][2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b); - (*state)[i][3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e); + (*state)[i][0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ + Multiply(d, 0x09); + (*state)[i][1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ + Multiply(d, 0x0d); + (*state)[i][2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ + Multiply(d, 0x0b); + (*state)[i][3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ + Multiply(d, 0x0e); } } - // The SubBytes Function Substitutes the values in the // state matrix with values in an S-box. -static void InvSubBytes(state_t* state) -{ +static void InvSubBytes(state_t* state) { uint8_t i, j; - for (i = 0; i < 4; ++i) - { - for (j = 0; j < 4; ++j) - { + for (i = 0; i < 4; ++i) { + for (j = 0; j < 4; ++j) { (*state)[j][i] = getSBoxInvert((*state)[j][i]); } } } -static void InvShiftRows(state_t* state) -{ +static void InvShiftRows(state_t* state) { uint8_t temp; - // Rotate first row 1 columns to right + // Rotate first row 1 columns to right temp = (*state)[3][1]; (*state)[3][1] = (*state)[2][1]; (*state)[2][1] = (*state)[1][1]; (*state)[1][1] = (*state)[0][1]; (*state)[0][1] = temp; - // Rotate second row 2 columns to right + // Rotate second row 2 columns to right temp = (*state)[0][2]; (*state)[0][2] = (*state)[2][2]; (*state)[2][2] = temp; @@ -407,11 +402,10 @@ static void InvShiftRows(state_t* state) (*state)[2][3] = (*state)[3][3]; (*state)[3][3] = temp; } -#endif // #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) +#endif // #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) // Cipher is the main function that encrypts the PlainText. -static void Cipher(state_t* state, const uint8_t* RoundKey) -{ +static void Cipher(state_t* state, const uint8_t* RoundKey) { uint8_t round = 0; // Add the First round key to the state before starting the rounds. @@ -421,8 +415,7 @@ static void Cipher(state_t* state, const uint8_t* RoundKey) // The first Nr-1 rounds are identical. // These Nr rounds are executed in the loop below. // Last one without MixColumns() - for (round = 1; ; ++round) - { + for (round = 1;; ++round) { SubBytes(state); ShiftRows(state); if (round == Nr) { @@ -436,8 +429,7 @@ static void Cipher(state_t* state, const uint8_t* RoundKey) } #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) -static void InvCipher(state_t* state, const uint8_t* RoundKey) -{ +static void InvCipher(state_t* state, const uint8_t* RoundKey) { uint8_t round = 0; // Add the First round key to the state before starting the rounds. @@ -447,8 +439,7 @@ static void InvCipher(state_t* state, const uint8_t* RoundKey) // The first Nr-1 rounds are identical. // These Nr rounds are executed in the loop below. // Last one without InvMixColumn() - for (round = (Nr - 1); ; --round) - { + for (round = (Nr - 1);; --round) { InvShiftRows(state); InvSubBytes(state); AddRoundKey(round, state, RoundKey); @@ -457,53 +448,41 @@ static void InvCipher(state_t* state, const uint8_t* RoundKey) } InvMixColumns(state); } - } -#endif // #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) +#endif // #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) /*****************************************************************************/ /* Public functions: */ /*****************************************************************************/ #if defined(ECB) && (ECB == 1) - -void AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf) -{ +void AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf) { // The next function call encrypts the PlainText with the Key using AES algorithm. Cipher((state_t*)buf, ctx->RoundKey); } -void AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf) -{ +void AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf) { // The next function call decrypts the PlainText with the Key using AES algorithm. InvCipher((state_t*)buf, ctx->RoundKey); } - -#endif // #if defined(ECB) && (ECB == 1) - - - - +#endif // #if defined(ECB) && (ECB == 1) #if defined(CBC) && (CBC == 1) - -static void XorWithIv(uint8_t* buf, const uint8_t* Iv) -{ +static void XorWithIv(uint8_t* buf, const uint8_t* Iv) { uint8_t i; - for (i = 0; i < AES_BLOCKLEN; ++i) // The block in AES is always 128bit no matter the key size + for (i = 0; i < AES_BLOCKLEN; + ++i) // The block in AES is always 128bit no matter the key size { buf[i] ^= Iv[i]; } } -void AES_CBC_encrypt_buffer(struct AES_ctx *ctx, uint8_t* buf, size_t length) -{ +void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) { size_t i; - uint8_t *Iv = ctx->Iv; - for (i = 0; i < length; i += AES_BLOCKLEN) - { + uint8_t* Iv = ctx->Iv; + for (i = 0; i < length; i += AES_BLOCKLEN) { XorWithIv(buf, Iv); Cipher((state_t*)buf, ctx->RoundKey); Iv = buf; @@ -513,53 +492,44 @@ void AES_CBC_encrypt_buffer(struct AES_ctx *ctx, uint8_t* buf, size_t length) memcpy(ctx->Iv, Iv, AES_BLOCKLEN); } -void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) -{ +void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) { size_t i; uint8_t storeNextIv[AES_BLOCKLEN]; - for (i = 0; i < length; i += AES_BLOCKLEN) - { + for (i = 0; i < length; i += AES_BLOCKLEN) { memcpy(storeNextIv, buf, AES_BLOCKLEN); InvCipher((state_t*)buf, ctx->RoundKey); XorWithIv(buf, ctx->Iv); memcpy(ctx->Iv, storeNextIv, AES_BLOCKLEN); buf += AES_BLOCKLEN; } - } -#endif // #if defined(CBC) && (CBC == 1) - - +#endif // #if defined(CBC) && (CBC == 1) #if defined(CTR) && (CTR == 1) /* Symmetrical operation: same function for encrypting as for decrypting. Note any IV/nonce should never be reused with the same key */ -void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) -{ +void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) { uint8_t buffer[AES_BLOCKLEN]; - + size_t i; int bi; - for (i = 0, bi = AES_BLOCKLEN; i < length; ++i, ++bi) - { + for (i = 0, bi = AES_BLOCKLEN; i < length; ++i, ++bi) { if (bi == AES_BLOCKLEN) /* we need to regen xor compliment in buffer */ { - + memcpy(buffer, ctx->Iv, AES_BLOCKLEN); - Cipher((state_t*)buffer,ctx->RoundKey); + Cipher((state_t*)buffer, ctx->RoundKey); /* Increment Iv and handle overflow */ - for (bi = (AES_BLOCKLEN - 1); bi >= 0; --bi) - { - /* inc will overflow */ - if (ctx->Iv[bi] == 255) - { + for (bi = (AES_BLOCKLEN - 1); bi >= 0; --bi) { + /* inc will overflow */ + if (ctx->Iv[bi] == 255) { ctx->Iv[bi] = 0; continue; - } + } ctx->Iv[bi] += 1; - break; + break; } bi = 0; } @@ -568,4 +538,4 @@ void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) } } -#endif // #if defined(CTR) && (CTR == 1) +#endif // #if defined(CTR) && (CTR == 1) diff --git a/components/spotify/cspot/bell/main/utilities/include/BellLogger.h b/components/spotify/cspot/bell/main/utilities/include/BellLogger.h index a71784b3..ba6694c3 100644 --- a/components/spotify/cspot/bell/main/utilities/include/BellLogger.h +++ b/components/spotify/cspot/bell/main/utilities/include/BellLogger.h @@ -1,120 +1,140 @@ #ifndef BELL_LOGGER_H #define BELL_LOGGER_H -#include -#include -#include -#include -#include +#include // for va_end, va_list, va_start +#include // for printf, vprintf +#include +#include +#include +#include // for string, basic_string -namespace bell -{ +namespace bell { - class AbstractLogger - { - public: - bool enableSubmodule = false; - virtual void debug(std::string filename, int line, std::string submodule, const char *format, ...) = 0; - virtual void error(std::string filename, int line, std::string submodule, const char *format, ...) = 0; - virtual void info(std::string filename, int line, std::string submodule, const char *format, ...) = 0; - }; +class AbstractLogger { + public: + bool enableSubmodule = false; + bool enableTimestamp = false; - extern bell::AbstractLogger* bellGlobalLogger; - class BellLogger : public bell::AbstractLogger - { - public: - // static bool enableColors = true; - void debug(std::string filename, int line, std::string submodule, const char *format, ...) - { + virtual void debug(std::string filename, int line, std::string submodule, + const char* format, ...) = 0; + virtual void error(std::string filename, int line, std::string submodule, + const char* format, ...) = 0; + virtual void info(std::string filename, int line, std::string submodule, + const char* format, ...) = 0; +}; - printf(colorRed); - printf("D "); - if (enableSubmodule) { - printf(colorReset); - printf("[%s] ", submodule.c_str()); - } - printFilename(filename); - printf(":%d: ", line); - va_list args; - va_start(args, format); - vprintf(format, args); - va_end(args); - printf("\n"); - }; +extern bell::AbstractLogger* bellGlobalLogger; +class BellLogger : public bell::AbstractLogger { + public: + // static bool enableColors = true; + void debug(std::string filename, int line, std::string submodule, + const char* format, ...) { + printTimestamp(); - void error(std::string filename, int line, std::string submodule, const char *format, ...) - { + printf(colorRed); + printf("D "); + if (enableSubmodule) { + printf(colorReset); + printf("[%s] ", submodule.c_str()); + } + printFilename(filename); + printf(":%d: ", line); + va_list args; + va_start(args, format); + vprintf(format, args); + va_end(args); + printf("\n"); + }; - printf(colorRed); - printf("E "); - if (enableSubmodule) { - printf(colorReset); - printf("[%s] ", submodule.c_str()); - } - printFilename(filename); - printf(":%d: ", line); - printf(colorRed); - va_list args; - va_start(args, format); - vprintf(format, args); - va_end(args); - printf("\n"); - }; + void error(std::string filename, int line, std::string submodule, + const char* format, ...) { + printTimestamp(); - void info(std::string filename, int line, std::string submodule, const char *format, ...) - { + printf(colorRed); + printf("E "); + if (enableSubmodule) { + printf(colorReset); + printf("[%s] ", submodule.c_str()); + } + printFilename(filename); + printf(":%d: ", line); + printf(colorRed); + va_list args; + va_start(args, format); + vprintf(format, args); + va_end(args); + printf("\n"); + }; - printf(colorBlue); - printf("I "); - if (enableSubmodule) { - printf(colorReset); - printf("[%s] ", submodule.c_str()); - } - printFilename(filename); - printf(":%d: ", line); - printf(colorReset); - va_list args; - va_start(args, format); - vprintf(format, args); - va_end(args); - printf("\n"); - }; + void info(std::string filename, int line, std::string submodule, + const char* format, ...) { + printTimestamp(); - void printFilename(std::string filename) - { + printf(colorBlue); + printf("I "); + if (enableSubmodule) { + printf(colorReset); + printf("[%s] ", submodule.c_str()); + } + printFilename(filename); + printf(":%d: ", line); + printf(colorReset); + va_list args; + va_start(args, format); + vprintf(format, args); + va_end(args); + printf("\n"); + }; + + void printTimestamp() { + if (enableTimestamp) { + auto now = std::chrono::system_clock::now(); + time_t now_time = std::chrono::system_clock::to_time_t(now); + const auto nowMs = std::chrono::duration_cast( + now.time_since_epoch()) % + 1000; + + auto gmt_time = gmtime(&now_time); + printf(colorReset); + std::cout << std::put_time(gmt_time, "[%Y-%m-%d %H:%M:%S") << '.' + << std::setfill('0') << std::setw(3) << nowMs.count() << "] "; + } + } + + void printFilename(std::string filename) { #ifdef _WIN32 - std::string basenameStr(filename.substr(filename.rfind("\\") + 1)); + std::string basenameStr(filename.substr(filename.rfind("\\") + 1)); #else - std::string basenameStr(filename.substr(filename.rfind("/") + 1)); + std::string basenameStr(filename.substr(filename.rfind("/") + 1)); #endif - unsigned long hash = 5381; - for (char const &c : basenameStr) - { - hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ - } + unsigned long hash = 5381; + for (char const& c : basenameStr) { + hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ + } - printf("\033[0;%dm", allColors[hash % NColors]); + printf("\033[0;%dm", allColors[hash % NColors]); - printf("%s", basenameStr.c_str()); - printf(colorReset); - } + printf("%s", basenameStr.c_str()); + printf(colorReset); + } - private: - static constexpr const char *colorReset = "\033[0m"; - static constexpr const char *colorRed = "\033[0;31m"; - static constexpr const char *colorBlue = "\033[0;34m"; - static constexpr const int NColors = 15; - static constexpr int allColors[NColors] = {31, 32, 33, 34, 35, 36, 37, 90, 91, 92, 93, 94, 95, 96, 97}; - }; + private: + static constexpr const char* colorReset = "\033[0m"; + static constexpr const char* colorRed = "\033[0;31m"; + static constexpr const char* colorBlue = "\033[0;34m"; + static constexpr const int NColors = 15; + static constexpr int allColors[NColors] = {31, 32, 33, 34, 35, 36, 37, 90, + 91, 92, 93, 94, 95, 96, 97}; +}; - void setDefaultLogger(); - void enableSubmoduleLogging(); -} +void setDefaultLogger(); +void enableSubmoduleLogging(); +void enableTimestampLogging(); +} // namespace bell -#define BELL_LOG(type, ...) \ - do \ - { \ - bell::bellGlobalLogger->type(__FILE__, __LINE__, __VA_ARGS__); \ - } while (0) +#define BELL_LOG(type, ...) \ + do { \ + bell::bellGlobalLogger->type(__FILE__, __LINE__, __VA_ARGS__); \ + } while (0) -#endif \ No newline at end of file +#endif diff --git a/components/spotify/cspot/bell/main/utilities/include/BellTask.h b/components/spotify/cspot/bell/main/utilities/include/BellTask.h index b549fa4a..7f0fc3be 100644 --- a/components/spotify/cspot/bell/main/utilities/include/BellTask.h +++ b/components/spotify/cspot/bell/main/utilities/include/BellTask.h @@ -15,8 +15,8 @@ #include #endif -#include #include +#include namespace bell { class Task { diff --git a/components/spotify/cspot/bell/main/utilities/include/BellUtils.h b/components/spotify/cspot/bell/main/utilities/include/BellUtils.h index ac6921cd..c196368c 100644 --- a/components/spotify/cspot/bell/main/utilities/include/BellUtils.h +++ b/components/spotify/cspot/bell/main/utilities/include/BellUtils.h @@ -1,14 +1,16 @@ #ifndef EUPHONIUM_BELL_UTILS #define EUPHONIUM_BELL_UTILS -#include +#include // for int32_t, int64_t +#include // for NULL #ifdef _WIN32 #include #else -#include +#include // for timeval, gettimeofday +#include // for usleep #endif -#include -#include +#include // for floor +#include // for string #ifdef ESP_PLATFORM #include "esp_system.h" @@ -28,9 +30,9 @@ struct tv { #if _WIN32 static const uint64_t EPOCH = ((uint64_t)116444736000000000ULL); - SYSTEMTIME system_time; - FILETIME file_time; - uint64_t time; + SYSTEMTIME system_time; + FILETIME file_time; + uint64_t time; GetSystemTime(&system_time); SystemTimeToFileTime(&system_time, &file_time); @@ -50,7 +52,9 @@ struct tv { int32_t sec; int32_t usec; - int64_t ms() { return (sec * (int64_t)1000) + (usec / 1000); } + int64_t ms() { + return (sec * (int64_t)1000) + (usec / 1000); + } tv operator+(const tv& other) const { tv result(*this); @@ -95,7 +99,6 @@ struct tv { #define BELL_SLEEP_MS(ms) Sleep(ms) #define BELL_YIELD() ; #else -#include #define BELL_SLEEP_MS(ms) usleep(ms * 1000) #define BELL_YIELD() ; diff --git a/components/spotify/cspot/bell/main/utilities/include/Crypto.h b/components/spotify/cspot/bell/main/utilities/include/Crypto.h index bafb5f07..503e0cca 100644 --- a/components/spotify/cspot/bell/main/utilities/include/Crypto.h +++ b/components/spotify/cspot/bell/main/utilities/include/Crypto.h @@ -1,83 +1,76 @@ #ifndef BELL_CRYPTO_H #define BELL_CRYPTO_H -#define Crypto CryptoMbedTLS - -#include -#include -#include -#include - -extern "C" { -#include "aes.h" -} -#include -#include -#include -#include -#include -#include -#include +#include // for string +#include // for vector +#include // for mbedtls_aes_context +#include // for mbedtls_md_context_t +#include // for size_t +#include // for uint8_t #define DH_KEY_SIZE 96 const static unsigned char DHPrime[] = { /* Well-known Group 1, 768-bit prime */ - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, - 0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6, - 0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e, - 0x08, 0x8a, 0x67, 0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6, - 0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79, 0x8e, - 0x34, 0x04, 0xdd, 0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a, - 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d, 0xf2, 0x5f, 0x14, - 0x37, 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45, - 0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6, 0xf4, - 0x4c, 0x42, 0xe9, 0xa6, 0x3a, 0x36, 0x20, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff -}; + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, 0xda, 0xa2, + 0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6, 0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1, + 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67, 0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6, + 0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79, 0x8e, 0x34, 0x04, 0xdd, + 0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d, + 0xf2, 0x5f, 0x14, 0x37, 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45, + 0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6, 0xf4, 0x4c, 0x42, 0xe9, + 0xa6, 0x3a, 0x36, 0x20, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; static unsigned char DHGenerator[1] = {2}; class CryptoMbedTLS { -private: - mbedtls_md_context_t sha1Context; - mbedtls_aes_context aesCtx; - bool aesCtxInitialized = false; -public: - CryptoMbedTLS(); - ~CryptoMbedTLS(); - // Base64 - std::vector base64Decode(const std::string& data); - std::string base64Encode(const std::vector& data); + private: + mbedtls_md_context_t sha1Context; + mbedtls_aes_context aesCtx; + bool aesCtxInitialized = false; - // Sha1 - void sha1Init(); - void sha1Update(const std::string& s); - void sha1Update(const std::vector& vec); - std::string sha1Final(); - std::vector sha1FinalBytes(); + public: + CryptoMbedTLS(); + ~CryptoMbedTLS(); + // Base64 + std::vector base64Decode(const std::string& data); + std::string base64Encode(const std::vector& data); - // HMAC SHA1 - std::vector sha1HMAC(const std::vector& inputKey, const std::vector& message); + // Sha1 + void sha1Init(); + void sha1Update(const std::string& s); + void sha1Update(const std::vector& vec); + std::string sha1Final(); + std::vector sha1FinalBytes(); - // AES CTR - void aesCTRXcrypt(const std::vector& key, std::vector& iv, uint8_t* data, size_t nbytes); - - // AES ECB - void aesECBdecrypt(const std::vector& key, std::vector& data); + // HMAC SHA1 + std::vector sha1HMAC(const std::vector& inputKey, + const std::vector& message); - // Diffie Hellman - std::vector publicKey; - std::vector privateKey; - void dhInit(); - std::vector dhCalculateShared(const std::vector& remoteKey); + // AES CTR + void aesCTRXcrypt(const std::vector& key, std::vector& iv, + uint8_t* data, size_t nbytes); - // PBKDF2 - std::vector pbkdf2HmacSha1(const std::vector& password, const std::vector& salt, int iterations, int digestSize); + // AES ECB + void aesECBdecrypt(const std::vector& key, + std::vector& data); - // Random stuff - std::vector generateVectorWithRandomData(size_t length); + // Diffie Hellman + std::vector publicKey; + std::vector privateKey; + void dhInit(); + std::vector dhCalculateShared(const std::vector& remoteKey); + + // PBKDF2 + std::vector pbkdf2HmacSha1(const std::vector& password, + const std::vector& salt, + int iterations, int digestSize); + + // Random stuff + std::vector generateVectorWithRandomData(size_t length); }; +#define Crypto CryptoMbedTLS + #endif \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/utilities/include/NanoPBExtensions.h b/components/spotify/cspot/bell/main/utilities/include/NanoPBExtensions.h new file mode 100644 index 00000000..0672eb7f --- /dev/null +++ b/components/spotify/cspot/bell/main/utilities/include/NanoPBExtensions.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +/// Set of helper methods that simplify nanopb usage in C++. +namespace bell::nanopb { +bool encodeString(pb_ostream_t* stream, const pb_field_t* field, + void* const* arg); + +bool encodeVector(pb_ostream_t* stream, const pb_field_t* field, + void* const* arg); + +bool encodeBoolean(pb_ostream_t* stream, const pb_field_t* field, + void* const* arg); +} // namespace bell::nanopb \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/utilities/include/NanoPBHelper.h b/components/spotify/cspot/bell/main/utilities/include/NanoPBHelper.h index f0c68eb5..f488a7c5 100644 --- a/components/spotify/cspot/bell/main/utilities/include/NanoPBHelper.h +++ b/components/spotify/cspot/bell/main/utilities/include/NanoPBHelper.h @@ -1,47 +1,51 @@ #pragma once -#include -#include "pb_encode.h" -#include "pb_decode.h" -#include +#include // for uint8_t +#include // for printf +#include // for string +#include // for vector -std::vector pbEncode(const pb_msgdesc_t *fields, const void *src_struct); +#include "pb.h" // for pb_msgdesc_t, pb_bytes_array_t, PB_GET_ERROR +#include "pb_decode.h" // for pb_istream_from_buffer, pb_decode, pb_istream_s + +std::vector pbEncode(const pb_msgdesc_t* fields, + const void* src_struct); pb_bytes_array_t* vectorToPbArray(const std::vector& vectorToPack); -void packString(char* &dst, std::string stringToPack); +void packString(char*& dst, std::string stringToPack); std::vector pbArrayToVector(pb_bytes_array_t* pbArray); template -T pbDecode(const pb_msgdesc_t *fields, std::vector &data) -{ +T pbDecode(const pb_msgdesc_t* fields, std::vector& data) { - T result = {}; - // Create stream - pb_istream_t stream = pb_istream_from_buffer(&data[0], data.size()); - - // Decode the message - if (pb_decode(&stream, fields, &result) == false) { - printf("Decode failed: %s\n", PB_GET_ERROR(&stream)); - } - return result; + T result = {}; + // Create stream + pb_istream_t stream = pb_istream_from_buffer(&data[0], data.size()); + + // Decode the message + if (pb_decode(&stream, fields, &result) == false) { + printf("Decode failed: %s\n", PB_GET_ERROR(&stream)); + } + + return result; } template -void pbDecode(T &result, const pb_msgdesc_t *fields, std::vector &data) -{ - // Create stream - pb_istream_t stream = pb_istream_from_buffer(&data[0], data.size()); - - // Decode the message - if (pb_decode(&stream, fields, &result) == false) { - printf("Decode failed: %s\n", PB_GET_ERROR(&stream)); - } +void pbDecode(T& result, const pb_msgdesc_t* fields, + std::vector& data) { + // Create stream + pb_istream_t stream = pb_istream_from_buffer(&data[0], data.size()); + + // Decode the message + if (pb_decode(&stream, fields, &result) == false) { + printf("Decode failed: %s\n", PB_GET_ERROR(&stream)); + } } -void pbPutString(const std::string &stringToPack, char* dst); -void pbPutCharArray(const char * stringToPack, char* dst); -void pbPutBytes(const std::vector &data, pb_bytes_array_t &dst); +void pbPutString(const std::string& stringToPack, char* dst); +void pbPutCharArray(const char* stringToPack, char* dst); +void pbPutBytes(const std::vector& data, pb_bytes_array_t& dst); -const char* pb_encode_to_string(const pb_msgdesc_t *fields, const void *data); +const char* pb_encode_to_string(const pb_msgdesc_t* fields, const void* data); diff --git a/components/spotify/cspot/bell/main/utilities/include/Queue.h b/components/spotify/cspot/bell/main/utilities/include/Queue.h index ae938ea2..80b3c0b8 100644 --- a/components/spotify/cspot/bell/main/utilities/include/Queue.h +++ b/components/spotify/cspot/bell/main/utilities/include/Queue.h @@ -1,117 +1,101 @@ #ifndef BELL_QUEUE_H #define BELL_QUEUE_H -#include #include #include -#include +#include -namespace bell -{ - template - class Queue - { - private: - /// Queue - std::queue m_queue; - /// Mutex to controll multiple access - mutable std::mutex m_mutex; - /// Conditional variable used to fire event - std::condition_variable m_cv; - /// Atomic variable used to terminate immediately wpop and wtpop functions - std::atomic m_forceExit = false; +namespace bell { +template +class Queue { + private: + /// Queue + std::queue m_queue; + /// Mutex to controll multiple access + mutable std::mutex m_mutex; + /// Conditional variable used to fire event + std::condition_variable m_cv; + /// Atomic variable used to terminate immediately wpop and wtpop functions + std::atomic m_forceExit = false; - public: - /// Add a new element in the queue. - /// New element. - void push(dataType const &data) - { - m_forceExit.store(false); - std::unique_lock lk(m_mutex); - m_queue.push(data); - lk.unlock(); - m_cv.notify_one(); - } - /// Check queue empty. - /// True if the queue is empty. - bool isEmpty() const - { - std::unique_lock lk(m_mutex); - return m_queue.empty(); - } - /// Pop element from queue. - /// [in,out] Element. - /// false if the queue is empty. - bool pop(dataType &popped_value) - { - std::unique_lock lk(m_mutex); - if (m_queue.empty()) - { - return false; - } - else - { - popped_value = m_queue.front(); - m_queue.pop(); - return true; - } - } - /// Wait and pop an element in the queue. - /// [in,out] Element. - /// False for forced exit. - bool wpop(dataType &popped_value) - { - std::unique_lock lk(m_mutex); - m_cv.wait(lk, [&]() -> bool - { return !m_queue.empty() || m_forceExit.load(); }); - if (m_forceExit.load()) - return false; - popped_value = m_queue.front(); - m_queue.pop(); - return true; - } - /// Timed wait and pop an element in the queue. - /// [in,out] Element. - /// [in] Wait time. - /// False for timeout or forced exit. - bool wtpop(dataType &popped_value, long milliseconds = 1000) - { - std::unique_lock lk(m_mutex); - m_cv.wait_for(lk, std::chrono::milliseconds(milliseconds), [&]() -> bool - { return !m_queue.empty() || m_forceExit.load(); }); - if (m_forceExit.load()) - return false; - if (m_queue.empty()) - return false; - popped_value = m_queue.front(); - m_queue.pop(); - return true; - } - /// Queue size. - int size() - { - std::unique_lock lk(m_mutex); - return static_cast(m_queue.size()); - } - /// Free the queue and force stop. - void clear() - { - m_forceExit.store(true); - std::unique_lock lk(m_mutex); - while (!m_queue.empty()) - { - //delete m_queue.front(); - m_queue.pop(); - } - lk.unlock(); - m_cv.notify_one(); - } - /// Check queue in forced exit state. - bool isExit() const - { - return m_forceExit.load(); - } - }; -} + public: + /// Add a new element in the queue. + /// New element. + void push(dataType const& data) { + m_forceExit.store(false); + std::unique_lock lk(m_mutex); + m_queue.push(data); + lk.unlock(); + m_cv.notify_one(); + } + /// Check queue empty. + /// True if the queue is empty. + bool isEmpty() const { + std::unique_lock lk(m_mutex); + return m_queue.empty(); + } + /// Pop element from queue. + /// [in,out] Element. + /// false if the queue is empty. + bool pop(dataType& popped_value) { + std::unique_lock lk(m_mutex); + if (m_queue.empty()) { + return false; + } else { + popped_value = m_queue.front(); + m_queue.pop(); + return true; + } + } + /// Wait and pop an element in the queue. + /// [in,out] Element. + /// False for forced exit. + bool wpop(dataType& popped_value) { + std::unique_lock lk(m_mutex); + m_cv.wait(lk, + [&]() -> bool { return !m_queue.empty() || m_forceExit.load(); }); + if (m_forceExit.load()) + return false; + popped_value = m_queue.front(); + m_queue.pop(); + return true; + } + /// Timed wait and pop an element in the queue. + /// [in,out] Element. + /// [in] Wait time. + /// False for timeout or forced exit. + bool wtpop(dataType& popped_value, long milliseconds = 1000) { + std::unique_lock lk(m_mutex); + m_cv.wait_for(lk, std::chrono::milliseconds(milliseconds), [&]() -> bool { + return !m_queue.empty() || m_forceExit.load(); + }); + if (m_forceExit.load()) + return false; + if (m_queue.empty()) + return false; + popped_value = m_queue.front(); + m_queue.pop(); + return true; + } + /// Queue size. + int size() { + std::unique_lock lk(m_mutex); + return static_cast(m_queue.size()); + } + /// Free the queue and force stop. + void clear() { + m_forceExit.store(true); + std::unique_lock lk(m_mutex); + while (!m_queue.empty()) { + //delete m_queue.front(); + m_queue.pop(); + } + lk.unlock(); + m_cv.notify_one(); + } + /// Check queue in forced exit state. + bool isExit() const { return m_forceExit.load(); } +}; +} // namespace bell #endif \ No newline at end of file diff --git a/components/spotify/cspot/bell/main/utilities/include/TimeDefs.h b/components/spotify/cspot/bell/main/utilities/include/TimeDefs.h index 21d602ab..8299b679 100644 --- a/components/spotify/cspot/bell/main/utilities/include/TimeDefs.h +++ b/components/spotify/cspot/bell/main/utilities/include/TimeDefs.h @@ -5,4 +5,4 @@ #ifndef EUPHONIUMCLI_TIMEDEFS_H #define EUPHONIUMCLI_TIMEDEFS_H -#endif // EUPHONIUMCLI_TIMEDEFS_H +#endif // EUPHONIUMCLI_TIMEDEFS_H diff --git a/components/spotify/cspot/bell/main/utilities/include/aes.h b/components/spotify/cspot/bell/main/utilities/include/aes.h index 3a24bd28..3eba21de 100644 --- a/components/spotify/cspot/bell/main/utilities/include/aes.h +++ b/components/spotify/cspot/bell/main/utilities/include/aes.h @@ -1,8 +1,8 @@ #ifndef _AES_H_ #define _AES_H_ -#include #include +#include // #define the macros below to 1/0 to enable/disable the mode of operation. // @@ -12,37 +12,35 @@ // The #ifndef-guard allows it to be configured before #include'ing or at compile time. #ifndef CBC - #define CBC 1 +#define CBC 1 #endif #ifndef ECB - #define ECB 1 +#define ECB 1 #endif #ifndef CTR - #define CTR 1 +#define CTR 1 #endif - // #define AES128 1 #define AES192 1 //#define AES256 1 -#define AES_BLOCKLEN 16 // Block length in bytes - AES is 128b block only +#define AES_BLOCKLEN 16 // Block length in bytes - AES is 128b block only #if defined(AES256) && (AES256 == 1) - #define AES_KEYLEN 32 - #define AES_keyExpSize 240 +#define AES_KEYLEN 32 +#define AES_keyExpSize 240 #elif defined(AES192) && (AES192 == 1) - #define AES_KEYLEN 24 - #define AES_keyExpSize 208 +#define AES_KEYLEN 24 +#define AES_keyExpSize 208 #else - #define AES_KEYLEN 16 // Key length in bytes - #define AES_keyExpSize 176 +#define AES_KEYLEN 16 // Key length in bytes +#define AES_keyExpSize 176 #endif -struct AES_ctx -{ +struct AES_ctx { uint8_t RoundKey[AES_keyExpSize]; #if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1)) uint8_t Iv[AES_BLOCKLEN]; @@ -51,41 +49,39 @@ struct AES_ctx void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key); #if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1)) -void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv); +void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, + const uint8_t* iv); void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv); #endif #if defined(ECB) && (ECB == 1) -// buffer size is exactly AES_BLOCKLEN bytes; -// you need only AES_init_ctx as IV is not used in ECB +// buffer size is exactly AES_BLOCKLEN bytes; +// you need only AES_init_ctx as IV is not used in ECB // NB: ECB is considered insecure for most uses void AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf); void AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf); -#endif // #if defined(ECB) && (ECB == !) - +#endif // #if defined(ECB) && (ECB == !) #if defined(CBC) && (CBC == 1) // buffer size MUST be mutile of AES_BLOCKLEN; // Suggest https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme // NOTES: you need to set IV in ctx via AES_init_ctx_iv() or AES_ctx_set_iv() -// no IV should ever be reused with the same key +// no IV should ever be reused with the same key void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); -#endif // #if defined(CBC) && (CBC == 1) - +#endif // #if defined(CBC) && (CBC == 1) #if defined(CTR) && (CTR == 1) -// Same function for encrypting as for decrypting. +// Same function for encrypting as for decrypting. // IV is incremented for every block, and used after encryption as XOR-compliment for output // Suggesting https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme // NOTES: you need to set IV in ctx with AES_init_ctx_iv() or AES_ctx_set_iv() -// no IV should ever be reused with the same key +// no IV should ever be reused with the same key void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); -#endif // #if defined(CTR) && (CTR == 1) +#endif // #if defined(CTR) && (CTR == 1) - -#endif // _AES_H_ \ No newline at end of file +#endif // _AES_H_ \ No newline at end of file diff --git a/components/spotify/cspot/include/AccessKeyFetcher.h b/components/spotify/cspot/include/AccessKeyFetcher.h index 11e5a73c..949c1461 100644 --- a/components/spotify/cspot/include/AccessKeyFetcher.h +++ b/components/spotify/cspot/include/AccessKeyFetcher.h @@ -3,29 +3,41 @@ #include // for function #include // for shared_ptr #include // for string +#include +namespace bell { +class WrappedSemaphore; +}; namespace cspot { struct Context; class AccessKeyFetcher { public: AccessKeyFetcher(std::shared_ptr ctx); - ~AccessKeyFetcher(); - typedef std::function Callback; + /** + * @brief Checks if key is expired + * @returns true when currently held access key is not valid + */ + bool isExpired(); - void getAccessKey(Callback callback); + /** + * @brief Fetches a new access key + * @remark In case the key is expired, this function blocks until a refresh is done. + * @returns access key + */ + std::string getAccessKey(); + + /** + * @brief Forces a refresh of the access key + */ + void updateAccessKey(); private: - const std::string CLIENT_ID = - "65b708073fc0480ea92a077233ca87bd"; // Spotify web client's client id - const std::string SCOPES = - "streaming,user-library-read,user-library-modify,user-top-read,user-read-" - "recently-played"; // Required access scopes - std::shared_ptr ctx; + std::shared_ptr updateSemaphore; - bool isExpired(); + std::atomic keyPending = false; std::string accessKey; long long int expiresAt; }; diff --git a/components/spotify/cspot/include/ApResolve.h b/components/spotify/cspot/include/ApResolve.h index 13b282a9..fafb4485 100644 --- a/components/spotify/cspot/include/ApResolve.h +++ b/components/spotify/cspot/include/ApResolve.h @@ -8,17 +8,16 @@ namespace cspot { class ApResolve { - private: - std::string apOverride; - public: ApResolve(std::string apOverride); /** - * @brief Connects to spotify's servers and returns first valid ap address - * - * @return std::string Address in form of url:port - */ + * @brief Connects to spotify's servers and returns first valid ap address + * @returns std::string Address in form of url:port + */ std::string fetchFirstApAddress(); + + private: + std::string apOverride; }; } // namespace cspot diff --git a/components/spotify/cspot/include/AuthChallenges.h b/components/spotify/cspot/include/AuthChallenges.h index 38d5a161..379f2e7c 100644 --- a/components/spotify/cspot/include/AuthChallenges.h +++ b/components/spotify/cspot/include/AuthChallenges.h @@ -1,10 +1,9 @@ #pragma once - -#include // for uint8_t -#include // for unique_ptr -#include // for string -#include // for vector +#include // for uint8_t +#include // for unique_ptr +#include // for string +#include // for vector #include "Crypto.h" // for Crypto #include "protobuf/authentication.pb.h" // for ClientResponseEncrypted @@ -16,20 +15,45 @@ class AuthChallenges { AuthChallenges(); ~AuthChallenges(); - std::vector shanSendKey = {}; - std::vector shanRecvKey = {}; - + /** + * @brief Prepares a spotify authentication packet + * @param authBlob authentication blob bytes + * @param authType value representing spotify's authentication type + * @param deviceId device id to use during auth. + * @param username spotify's username + * + * @returns vector containing bytes of the authentication packet + */ std::vector prepareAuthPacket(std::vector& authBlob, int authType, const std::string& deviceId, const std::string& username); + + /** + * @brief Solves the ApHello packet, and returns a packet with response + * + * @param helloPacket hello packet bytes received from the server + * @param data authentication data received from the server + * + * @returns vector containing response packet + */ std::vector solveApHello(std::vector& helloPacket, std::vector& data); + /** + * @brief Prepares an client hello packet, used for initial auth with spotify + * + * @returns vector containing the packet's data + */ std::vector prepareClientHello(); + std::vector shanSendKey = {}; + std::vector shanRecvKey = {}; + private: const long long SPOTIFY_VERSION = 0x10800000000; + + // Protobuf structures ClientResponseEncrypted authRequest; ClientResponsePlaintext clientResPlaintext; ClientHello clientHello; @@ -37,4 +61,4 @@ class AuthChallenges { std::unique_ptr crypto; }; -} // namespace cspot \ No newline at end of file +} // namespace cspot diff --git a/components/spotify/cspot/include/CDNTrackStream.h b/components/spotify/cspot/include/CDNAudioFile.h similarity index 58% rename from components/spotify/cspot/include/CDNTrackStream.h rename to components/spotify/cspot/include/CDNAudioFile.h index be7c2ca6..90cf8286 100644 --- a/components/spotify/cspot/include/CDNTrackStream.h +++ b/components/spotify/cspot/include/CDNAudioFile.h @@ -1,10 +1,10 @@ #pragma once -#include // for size_t -#include // for uint8_t -#include // for shared_ptr, unique_ptr -#include // for string -#include // for vector +#include // for size_t +#include // for uint8_t +#include // for shared_ptr, unique_ptr +#include // for string +#include // for vector #include "Crypto.h" // for Crypto #include "HTTPClient.h" // for HTTPClient @@ -16,46 +16,45 @@ class WrappedSemaphore; namespace cspot { class AccessKeyFetcher; -class CDNTrackStream { +class CDNAudioFile { public: - CDNTrackStream(std::shared_ptr); - ~CDNTrackStream(); - - enum class Status { INITIALIZING, HAS_DATA, HAS_URL, FAILED }; - - struct TrackInfo { - std::string trackId; - std::string name; - std::string album; - std::string artist; - std::string imageUrl; - int duration; - }; - - TrackInfo trackInfo; - - Status status; - std::unique_ptr trackReady; - - void fetchFile(const std::vector& trackId, - const std::vector& audioKey); - - void fail(); + CDNAudioFile(const std::string& cdnUrl, const std::vector& audioKey); + /** + * @brief Opens connection to the provided cdn url, and fetches track metadata. + */ void openStream(); + /** + * @brief Read and decrypt part of the cdn stream + * + * @param dst buffer where to read received data to + * @param amount of bytes to read + * + * @returns amount of bytes read + */ size_t readBytes(uint8_t* dst, size_t bytes); + /** + * @brief Returns current position in CDN stream + */ size_t getPosition(); + /** + * @brief returns total size of the audio file in bytes + */ size_t getSize(); + /** + * @brief Seeks the track to provided position + * @param position position where to seek the track + */ void seek(size_t position); private: const int OPUS_HEADER_SIZE = 8 * 1024; - const int OPUS_FOOTER_PREFFERED = 1024 * 12; // 12K should be safe + const int OPUS_FOOTER_PREFFERED = 1024 * 12; // 12K should be safe const int SEEK_MARGIN_SIZE = 1024 * 4; const int HTTP_BUFFER_SIZE = 1024 * 14; @@ -74,12 +73,9 @@ class CDNTrackStream { 0x3f, 0x63, 0x0d, 0x93}; std::unique_ptr crypto; - std::shared_ptr accessKeyFetcher; - std::unique_ptr httpConnection; - bool isConnected = false; - size_t position = 0; // Spotify header size + size_t position = 0; size_t totalFileSize = 0; size_t lastRequestPosition = 0; size_t lastRequestCapacity = 0; @@ -87,7 +83,6 @@ class CDNTrackStream { bool enableRequestMargin = false; std::string cdnUrl; - std::vector trackId; std::vector audioKey; void decrypt(uint8_t* dst, size_t nbytes, size_t pos); diff --git a/components/spotify/cspot/include/CSpotContext.h b/components/spotify/cspot/include/CSpotContext.h index 0237da85..ae1c77d2 100644 --- a/components/spotify/cspot/include/CSpotContext.h +++ b/components/spotify/cspot/include/CSpotContext.h @@ -2,10 +2,10 @@ #include +#include "LoginBlob.h" #include "MercurySession.h" #include "TimeProvider.h" #include "protobuf/metadata.pb.h" -#include "LoginBlob.h" namespace cspot { struct Context { @@ -25,7 +25,8 @@ struct Context { std::shared_ptr timeProvider; std::shared_ptr session; - static std::shared_ptr createFromBlob(std::shared_ptr blob) { + static std::shared_ptr createFromBlob( + std::shared_ptr blob) { auto ctx = std::make_shared(); ctx->timeProvider = std::make_shared(); @@ -37,6 +38,5 @@ struct Context { return ctx; } - }; -} // namespace cspot \ No newline at end of file +} // namespace cspot diff --git a/components/spotify/cspot/include/ConstantParameters.h b/components/spotify/cspot/include/ConstantParameters.h index 7cdd5495..12ebac30 100644 --- a/components/spotify/cspot/include/ConstantParameters.h +++ b/components/spotify/cspot/include/ConstantParameters.h @@ -7,11 +7,11 @@ extern char deviceId[]; namespace cspot { // Hardcoded information sent to spotify servers -const char * const informationString = "cspot-player"; -const char * const brandName = "cspot"; -const char * const versionString = "cspot-1.1"; -const char * const protocolVersion = "2.7.1"; -const char * const defaultDeviceName = "CSpot"; -const char * const swVersion = "1.0.0"; +const char* const informationString = "cspot-player"; +const char* const brandName = "cspot"; +const char* const versionString = "cspot-1.1"; +const char* const protocolVersion = "2.7.1"; +const char* const defaultDeviceName = "CSpot"; +const char* const swVersion = "1.0.0"; -} \ No newline at end of file +} // namespace cspot \ No newline at end of file diff --git a/components/spotify/cspot/include/CspotAssert.h b/components/spotify/cspot/include/CspotAssert.h index de535984..ca0dd8fd 100644 --- a/components/spotify/cspot/include/CspotAssert.h +++ b/components/spotify/cspot/include/CspotAssert.h @@ -3,14 +3,13 @@ #include #include -#define CSPOT_ASSERT(CONDITION, MESSAGE) \ - do \ - { \ - if (!(CONDITION)) \ - { \ - printf("At %s in %s:%d\n Assertion %s failed: %s", __func__, __FILE__, __LINE__, #CONDITION, MESSAGE); \ - abort(); \ - } \ - } while (0) +#define CSPOT_ASSERT(CONDITION, MESSAGE) \ + do { \ + if (!(CONDITION)) { \ + printf("At %s in %s:%d\n Assertion %s failed: %s", __func__, __FILE__, \ + __LINE__, #CONDITION, MESSAGE); \ + abort(); \ + } \ + } while (0) #endif diff --git a/components/spotify/cspot/include/Logger.h b/components/spotify/cspot/include/Logger.h index 40c3c333..60864ff8 100644 --- a/components/spotify/cspot/include/Logger.h +++ b/components/spotify/cspot/include/Logger.h @@ -2,8 +2,7 @@ #include -#define CSPOT_LOG(type, ...) \ - do \ - { \ - bell::bellGlobalLogger->type(__FILE__, __LINE__, "cspot", __VA_ARGS__); \ - } while (0) +#define CSPOT_LOG(type, ...) \ + do { \ + bell::bellGlobalLogger->type(__FILE__, __LINE__, "cspot", __VA_ARGS__); \ + } while (0) diff --git a/components/spotify/cspot/include/LoginBlob.h b/components/spotify/cspot/include/LoginBlob.h index 2a1f3611..0a8bb00b 100644 --- a/components/spotify/cspot/include/LoginBlob.h +++ b/components/spotify/cspot/include/LoginBlob.h @@ -1,10 +1,10 @@ #pragma once -#include // for uint8_t, uint32_t -#include // for map -#include // for unique_ptr -#include // for string -#include // for vector +#include // for uint8_t, uint32_t +#include // for map +#include // for unique_ptr +#include // for string +#include // for vector #include "Crypto.h" // for CryptoMbedTLS, Crypto diff --git a/components/spotify/cspot/include/MercurySession.h b/components/spotify/cspot/include/MercurySession.h index 4f4126e7..07496cc2 100644 --- a/components/spotify/cspot/include/MercurySession.h +++ b/components/spotify/cspot/include/MercurySession.h @@ -1,13 +1,13 @@ #pragma once -#include // for atomic -#include // for uint8_t, uint64_t, uint32_t -#include // for function -#include // for shared_ptr -#include // for mutex -#include // for string -#include // for unordered_map -#include // for vector +#include // for atomic +#include // for uint8_t, uint64_t, uint32_t +#include // for function +#include // for shared_ptr +#include // for mutex +#include // for string +#include // for unordered_map +#include // for vector #include "BellTask.h" // for Task #include "Packet.h" // for Packet @@ -15,7 +15,7 @@ #include "Session.h" // for Session #include "protobuf/mercury.pb.h" // for Header -namespace cspot { +namespace cspot { class TimeProvider; class MercurySession : public bell::Task, public cspot::Session { @@ -33,7 +33,8 @@ class MercurySession : public bell::Task, public cspot::Session { }; typedef std::function ResponseCallback; - typedef std::function&)> AudioKeyCallback; + typedef std::function&)> + AudioKeyCallback; typedef std::function ConnectionEstabilishedCallback; enum class RequestType : uint8_t { @@ -82,7 +83,11 @@ class MercurySession : public bell::Task, public cspot::Session { return this->executeSubscription(type, uri, callback, nullptr, parts); } - void requestAudioKey(const std::vector& trackId, + void unregister(uint64_t sequenceId); + + void unregisterAudioKey(uint32_t sequenceId); + + uint32_t requestAudioKey(const std::vector& trackId, const std::vector& fileId, AudioKeyCallback audioCallback); @@ -108,7 +113,7 @@ class MercurySession : public bell::Task, public cspot::Session { std::unordered_map callbacks; std::unordered_map subscriptions; - AudioKeyCallback audioKeyCallback; + std::unordered_map audioKeyCallbacks; uint64_t sequenceId = 1; uint32_t audioKeySequence = 1; diff --git a/components/spotify/cspot/include/PlainConnection.h b/components/spotify/cspot/include/PlainConnection.h index 6bb37b47..0bd735cc 100644 --- a/components/spotify/cspot/include/PlainConnection.h +++ b/components/spotify/cspot/include/PlainConnection.h @@ -6,7 +6,7 @@ #include "win32shim.h" #else -#include // for size_t +#include // for size_t #endif #include // for uint8_t #include // for function @@ -37,8 +37,8 @@ class PlainConnection { void readBlock(const uint8_t* dst, size_t size); size_t writeBlock(const std::vector& data); - private: - int apSock; + private: + int apSock; }; } // namespace cspot diff --git a/components/spotify/cspot/include/PlaybackState.h b/components/spotify/cspot/include/PlaybackState.h index b13fd71e..a79eab01 100644 --- a/components/spotify/cspot/include/PlaybackState.h +++ b/components/spotify/cspot/include/PlaybackState.h @@ -1,10 +1,11 @@ #pragma once -#include // for uint8_t, uint32_t -#include // for shared_ptr -#include // for string -#include // for vector +#include // for uint8_t, uint32_t +#include // for shared_ptr +#include // for string +#include // for vector +#include "TrackReference.h" #include "protobuf/spirc.pb.h" // for Frame, TrackRef, CapabilityType, Mess... namespace cspot { @@ -13,8 +14,10 @@ struct Context; class PlaybackState { private: std::shared_ptr ctx; + uint32_t seqNum = 0; uint8_t capabilityIndex = 0; + std::vector frameData; void addCapability( @@ -24,6 +27,9 @@ class PlaybackState { public: Frame innerFrame; Frame remoteFrame; + + std::vector remoteTracks; + enum class State { Playing, Stopped, Loading, Paused }; /** @@ -74,56 +80,9 @@ class PlaybackState { void setVolume(uint32_t volume); /** - * @brief Enables queue shuffling. - * - * Sets shuffle parameter on local frame, and in case shuffling is enabled, - * it will randomize the entire local queue. - * - * @param shuffle whenever should shuffle + * @brief Updates local track queue from remote data. */ - void setShuffle(bool shuffle); - - /** - * @brief Enables repeat - * - * @param repeat should repeat param - */ - void setRepeat(bool repeat); - - /** - * @brief Updates local track queue from remote data. - */ - void updateTracks(); - - /** - * @brief Changes playback to next queued track. - * - * Will go back to first track if current track is last track in queue. - * In that case, it will pause if repeat is disabled. - */ - bool nextTrack(); - - /** - * @brief Changes playback to previous queued track. - * - * Will stop if current track is the first track in queue and repeat is disabled. - * If repeat is enabled, it will loop back to the last track in queue. - */ - void prevTrack(); - - /** - * @brief Gets the current track reference. - * - * @return std::shared_ptr pointer to track reference - */ - TrackRef* getCurrentTrackRef(); - - /** - * @brief Gets reference to next track in queue, or nullptr if there is no next track. - * - * @return std::shared_ptr pointer to track reference - */ - TrackRef* getNextTrackRef(); + void syncWithRemote(); /** * @brief Encodes current frame into binary data via protobuf. @@ -132,5 +91,7 @@ class PlaybackState { * @return std::vector binary frame data */ std::vector encodeCurrentFrame(MessageType typ); + + bool decodeRemoteFrame(std::vector& data); }; -} // namespace cspot \ No newline at end of file +} // namespace cspot diff --git a/components/spotify/cspot/include/Shannon.h b/components/spotify/cspot/include/Shannon.h index aa19bdb8..23e9f172 100644 --- a/components/spotify/cspot/include/Shannon.h +++ b/components/spotify/cspot/include/Shannon.h @@ -4,41 +4,40 @@ #include // for uint32_t, uint8_t #include // for vector -class Shannon -{ -public: - static constexpr unsigned int N = 16; +class Shannon { + public: + static constexpr unsigned int N = 16; - void key(const std::vector &key); /* set key */ - void nonce(const std::vector &nonce); /* set Init Vector */ - void stream(std::vector &buf); /* stream cipher */ - void maconly(std::vector &buf); /* accumulate MAC */ - void encrypt(std::vector &buf); /* encrypt + MAC */ - void decrypt(std::vector &buf); /* finalize + MAC */ - void finish(std::vector &buf); /* finalise MAC */ + void key(const std::vector& key); /* set key */ + void nonce(const std::vector& nonce); /* set Init Vector */ + void stream(std::vector& buf); /* stream cipher */ + void maconly(std::vector& buf); /* accumulate MAC */ + void encrypt(std::vector& buf); /* encrypt + MAC */ + void decrypt(std::vector& buf); /* finalize + MAC */ + void finish(std::vector& buf); /* finalise MAC */ -private: - static constexpr unsigned int FOLD = Shannon::N; - static constexpr unsigned int INITKONST = 0x6996c53a; - static constexpr unsigned int KEYP = 13; - uint32_t R[Shannon::N]; - uint32_t CRC[Shannon::N]; - uint32_t initR[Shannon::N]; - uint32_t konst; - uint32_t sbuf; - uint32_t mbuf; - int nbuf; - static uint32_t sbox1(uint32_t w); - static uint32_t sbox2(uint32_t w); - void cycle(); - void crcfunc(uint32_t i); - void macfunc(uint32_t i); - void initState(); - void saveState(); - void reloadState(); - void genkonst(); - void diffuse(); - void loadKey(const std::vector &key); + private: + static constexpr unsigned int FOLD = Shannon::N; + static constexpr unsigned int INITKONST = 0x6996c53a; + static constexpr unsigned int KEYP = 13; + uint32_t R[Shannon::N]; + uint32_t CRC[Shannon::N]; + uint32_t initR[Shannon::N]; + uint32_t konst; + uint32_t sbuf; + uint32_t mbuf; + int nbuf; + static uint32_t sbox1(uint32_t w); + static uint32_t sbox2(uint32_t w); + void cycle(); + void crcfunc(uint32_t i); + void macfunc(uint32_t i); + void initState(); + void saveState(); + void reloadState(); + void genkonst(); + void diffuse(); + void loadKey(const std::vector& key); }; #endif \ No newline at end of file diff --git a/components/spotify/cspot/include/ShannonConnection.h b/components/spotify/cspot/include/ShannonConnection.h index b1a1fd01..30a8cbac 100644 --- a/components/spotify/cspot/include/ShannonConnection.h +++ b/components/spotify/cspot/include/ShannonConnection.h @@ -1,10 +1,10 @@ #ifndef SHANNONCONNECTION_H #define SHANNONCONNECTION_H -#include // for uint8_t, uint32_t -#include // for shared_ptr, unique_ptr -#include // for mutex -#include // for vector +#include // for uint8_t, uint32_t +#include // for shared_ptr, unique_ptr +#include // for mutex +#include // for vector #include "Packet.h" // for Packet diff --git a/components/spotify/cspot/include/SpircHandler.h b/components/spotify/cspot/include/SpircHandler.h index 5401088b..ecb21082 100644 --- a/components/spotify/cspot/include/SpircHandler.h +++ b/components/spotify/cspot/include/SpircHandler.h @@ -1,14 +1,14 @@ #pragma once -#include // for uint32_t, uint8_t -#include // for function -#include // for shared_ptr, unique_ptr -#include // for string -#include // for variant -#include // for vector +#include // for uint32_t, uint8_t +#include // for function +#include // for shared_ptr, unique_ptr +#include // for string +#include // for variant +#include // for vector -#include "CDNTrackStream.h" // for CDNTrackStream, CDNTrackStream::Track... -#include "PlaybackState.h" // for PlaybackState +#include "CDNAudioFile.h" // for CDNTrackStream, CDNTrackStream::Track... +#include "TrackQueue.h" #include "protobuf/spirc.pb.h" // for MessageType namespace cspot { @@ -31,7 +31,8 @@ class SpircHandler { FLUSH, PLAYBACK_START }; - typedef std::variant EventData; + + typedef std::variant EventData; struct Event { EventType eventType; @@ -47,36 +48,34 @@ class SpircHandler { void setPause(bool pause); - void nextSong(); void previousSong(); + void nextSong(); + void notifyAudioReachedPlayback(); void updatePositionMs(uint32_t position); void setRemoteVolume(int volume); void loadTrackFromURI(const std::string& uri); + std::shared_ptr getTrackQueue() { return trackQueue; } void disconnect(); private: std::shared_ptr ctx; std::shared_ptr trackPlayer; + std::shared_ptr trackQueue; EventHandler eventHandler = nullptr; - cspot::PlaybackState playbackState; - CDNTrackStream::TrackInfo currentTrackInfo; - - bool isTrackFresh = true; - bool isRequestedFromLoad = false; - bool isNextTrackPreloaded = false; - uint32_t nextTrackPosition = 0; + std::shared_ptr playbackState; void sendCmd(MessageType typ); void sendEvent(EventType type); void sendEvent(EventType type, EventData data); + void skipSong(TrackQueue::SkipDirection dir); void handleFrame(std::vector& data); void notify(); }; -} // namespace cspot \ No newline at end of file +} // namespace cspot diff --git a/components/spotify/cspot/include/TrackPlayer.h b/components/spotify/cspot/include/TrackPlayer.h index 5eaf9f50..74735ab4 100644 --- a/components/spotify/cspot/include/TrackPlayer.h +++ b/components/spotify/cspot/include/TrackPlayer.h @@ -1,47 +1,54 @@ #pragma once -#include // for atomic -#include // for uint8_t, int64_t -#include // for size_t, time -#include // for function -#include // for shared_ptr, unique_ptr -#include // for mutex -#include // for string_view -#include // for vector +#include // for atomic +#include // for uint8_t, int64_t +#include // for size_t, time +#include // for function +#include // for shared_ptr, unique_ptr +#include // for mutex +#include // for string_view +#include // for vector -#include "BellTask.h" // for Task -#include "CDNTrackStream.h" // for CDNTrackStream, CDNTrackStream::TrackInfo +#include "BellTask.h" // for Task +#include "CDNAudioFile.h" +#include "TrackQueue.h" namespace bell { class WrappedSemaphore; } // namespace bell + #ifdef BELL_VORBIS_FLOAT #include "vorbis/vorbisfile.h" #else -#include "ivorbisfile.h" // for OggVorbis_File, ov_callbacks +#include "ivorbisfile.h" // for OggVorbis_File, ov_callbacks #endif namespace cspot { class TrackProvider; +class TrackQueue; struct Context; struct TrackReference; class TrackPlayer : bell::Task { public: - typedef std::function TrackLoadedCallback; - typedef std::function DataCallback; + // Callback types + typedef std::function)> TrackLoadedCallback; + typedef std::function DataCallback; typedef std::function EOFCallback; typedef std::function isAiringCallback; - TrackPlayer(std::shared_ptr ctx, isAiringCallback, EOFCallback, TrackLoadedCallback); + TrackPlayer(std::shared_ptr ctx, + std::shared_ptr trackQueue, + EOFCallback eofCallback, TrackLoadedCallback loadedCallback); ~TrackPlayer(); - - void loadTrackFromRef(TrackReference& ref, size_t playbackMs, bool startAutomatically); + + void loadTrackFromRef(TrackReference& ref, size_t playbackMs, + bool startAutomatically); void setDataCallback(DataCallback callback); - - CDNTrackStream::TrackInfo getCurrentTrackInfo(); + + // CDNTrackStream::TrackInfo getCurrentTrackInfo(); void seekMs(size_t ms); - void stopTrack(); + void resetState(); // Vorbis codec callbacks size_t _vorbisRead(void* ptr, size_t size, size_t nmemb); @@ -49,35 +56,39 @@ class TrackPlayer : bell::Task { int _vorbisSeek(int64_t offset, int whence); long _vorbisTell(); - void destroy(); + void stop(); + void start(); private: std::shared_ptr ctx; - std::shared_ptr trackProvider; - std::shared_ptr currentTrackStream; - size_t sequence = std::time(nullptr); + std::shared_ptr trackQueue; + std::shared_ptr currentTrackStream; std::unique_ptr playbackSemaphore; TrackLoadedCallback trackLoaded; DataCallback dataCallback = nullptr; EOFCallback eofCallback; - isAiringCallback isAiring; // Playback control std::atomic currentSongPlaying; std::mutex playbackMutex; - std::mutex seekMutex; - + std::mutex dataOutMutex; + // Vorbis related OggVorbis_File vorbisFile; ov_callbacks vorbisCallbacks; int currentSection; + std::vector pcmBuffer = std::vector(1024); - size_t playbackPosition = 0; bool autoStart = false; - std::atomic isRunning = true; + + std::atomic isRunning = false; + std::atomic pendingReset = false; + std::atomic inFuture = false; + std::atomic pendingSeekPositionMs = 0; + std::mutex runningMutex; void runTask() override; diff --git a/components/spotify/cspot/include/TrackProvider.h b/components/spotify/cspot/include/TrackProvider.h deleted file mode 100644 index de2900fe..00000000 --- a/components/spotify/cspot/include/TrackProvider.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include // for uint8_t -#include // for shared_ptr, unique_ptr, weak_ptr -#include // for vector - -#include "MercurySession.h" // for MercurySession -#include "TrackReference.h" // for TrackReference -#include "protobuf/metadata.pb.h" // for Episode, Restriction, Track - -namespace cspot { -class AccessKeyFetcher; -class CDNTrackStream; -struct Context; - -class TrackProvider { - public: - TrackProvider(std::shared_ptr ctx); - ~TrackProvider(); - - std::shared_ptr loadFromTrackRef(TrackReference& trackRef); - - private: - std::shared_ptr accessKeyFetcher; - std::shared_ptr ctx; - std::unique_ptr cdnStream; - - Track trackInfo; - Episode episodeInfo; - std::weak_ptr currentTrackReference; - TrackReference trackIdInfo; - - void queryMetadata(); - void onMetadataResponse(MercurySession::Response& res); - bool doRestrictionsApply(Restriction* restrictions, int count); - void fetchFile(const std::vector& fileId, - const std::vector& trackId); - bool canPlayTrack(int index); -}; -} // namespace cspot \ No newline at end of file diff --git a/components/spotify/cspot/include/TrackQueue.h b/components/spotify/cspot/include/TrackQueue.h new file mode 100644 index 00000000..7ff097e3 --- /dev/null +++ b/components/spotify/cspot/include/TrackQueue.h @@ -0,0 +1,134 @@ +#pragma once + +#include // for size_t +#include +#include +#include +#include + +#include "BellTask.h" +#include "PlaybackState.h" +#include "TrackReference.h" + +#include "protobuf/metadata.pb.h" // for Track, _Track, AudioFile, Episode + +namespace bell { +class WrappedSemaphore; +}; + +namespace cspot { +struct Context; +class AccessKeyFetcher; +class CDNAudioFile; + +// Used in got track info event +struct TrackInfo { + std::string name, album, artist, imageUrl, trackId; + uint32_t duration; + + void loadPbTrack(Track* pbTrack, const std::vector& gid); + void loadPbEpisode(Episode* pbEpisode, const std::vector& gid); +}; + +class QueuedTrack { + public: + QueuedTrack(TrackReference& ref, std::shared_ptr ctx, + uint32_t requestedPosition = 0); + ~QueuedTrack(); + + enum class State { + QUEUED, + PENDING_META, + KEY_REQUIRED, + PENDING_KEY, + CDN_REQUIRED, + READY, + FAILED + }; + + std::shared_ptr loadedSemaphore; + + State state = State::QUEUED; // Current state of the track + TrackReference ref; // Holds GID, URI and Context + TrackInfo trackInfo; // Full track information fetched from spotify, name etc + + uint32_t requestedPosition; + std::string identifier; + + // Will return nullptr if the track is not ready + std::shared_ptr getAudioFile(); + + // --- Steps --- + void stepLoadMetadata( + Track* pbTrack, Episode* pbEpisode, std::mutex& trackListMutex, + std::shared_ptr updateSemaphore); + + void stepParseMetadata(Track* pbTrack, Episode* pbEpisode); + + void stepLoadAudioFile( + std::mutex& trackListMutex, + std::shared_ptr updateSemaphore); + + void stepLoadCDNUrl(const std::string& accessKey); + + void expire(); + + private: + std::shared_ptr ctx; + + uint64_t pendingMercuryRequest = 0; + uint32_t pendingAudioKeyRequest = 0; + + std::vector trackId, fileId, audioKey; + std::string cdnUrl; +}; + +class TrackQueue : public bell::Task { + public: + TrackQueue(std::shared_ptr ctx, + std::shared_ptr playbackState); + ~TrackQueue(); + + enum class SkipDirection { NEXT, PREV }; + + std::shared_ptr playableSemaphore; + std::atomic notifyPending = false; + + + void runTask() override; + void stopTask(); + + bool hasTracks(); + bool isFinished(); + bool skipTrack(SkipDirection dir, bool expectNotify = true); + void updateTracks(uint32_t requestedPosition = 0, bool initial = false); + TrackInfo getTrackInfo(std::string_view identifier); + std::shared_ptr consumeTrack( + std::shared_ptr prevSong, int& offset); + + private: + static const int MAX_TRACKS_PRELOAD = 3; + + std::shared_ptr accessKeyFetcher; + std::shared_ptr playbackState; + std::shared_ptr ctx; + std::shared_ptr processSemaphore; + + std::deque> preloadedTracks; + std::vector currentTracks; + std::mutex tracksMutex, runningMutex; + + // PB data + Track pbTrack; + Episode pbEpisode; + + std::string accessKey; + + int16_t currentTracksIndex = -1; + + bool isRunning = false; + + void processTrack(std::shared_ptr track); + bool queueNextTrack(int offset = 0, uint32_t positionMs = 0); +}; +} // namespace cspot diff --git a/components/spotify/cspot/include/TrackReference.h b/components/spotify/cspot/include/TrackReference.h index ec87e16c..a1ede7be 100644 --- a/components/spotify/cspot/include/TrackReference.h +++ b/components/spotify/cspot/include/TrackReference.h @@ -1,51 +1,36 @@ #pragma once +#include #include #include +#include #include "NanoPBHelper.h" -#include "Utils.h" +#include "pb_decode.h" #include "protobuf/spirc.pb.h" -namespace cspot { +namespace cspot { struct TrackReference { - static constexpr auto base62Alphabet = - "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + TrackReference(); // Resolved track GID std::vector gid; + std::string uri, context; + std::optional queued; // Type identifier enum class Type { TRACK, EPISODE }; + Type type; - static TrackReference fromTrackRef(TrackRef* ref) { - TrackReference trackRef; - if (ref->gid != nullptr) { - // For tracks, the GID is already in the protobuf - trackRef.gid = pbArrayToVector(ref->gid); - trackRef.type = Type::TRACK; - } else { - // Episode GID is being fetched via base62 encoded URI - auto uri = std::string(ref->uri); - auto idString = uri.substr(uri.find_last_of(":") + 1, uri.size()); - trackRef.gid = {0}; + void decodeURI(); - std::string_view alphabet(base62Alphabet); - for (int x = 0; x < idString.size(); x++) { - size_t d = alphabet.find(idString[x]); - trackRef.gid = bigNumMultiply(trackRef.gid, 62); - trackRef.gid = bigNumAdd(trackRef.gid, d); - } - } + bool operator==(const TrackReference& other) const; - return trackRef; - } + // Encodes list of track references into a pb structure, used by nanopb + static bool pbEncodeTrackList(pb_ostream_t* stream, const pb_field_t* field, + void* const* arg); - static TrackReference fromGID(std::vector gid, bool isEpisode) { - TrackReference trackRef; - trackRef.gid = gid; - trackRef.type = isEpisode ? Type::EPISODE : Type::TRACK; - return trackRef; - } + static bool pbDecodeTrackList(pb_istream_t* stream, const pb_field_t* field, + void** arg); }; -} // namespace cspot \ No newline at end of file +} // namespace cspot diff --git a/components/spotify/cspot/include/Utils.h b/components/spotify/cspot/include/Utils.h index 95dcce11..a52be4ea 100644 --- a/components/spotify/cspot/include/Utils.h +++ b/components/spotify/cspot/include/Utils.h @@ -1,7 +1,7 @@ #ifndef UTILS_H #define UTILS_H -#include // for snprintf, size_t -#include // for vector +#include // for snprintf, size_t +#include // for vector #ifdef _WIN32 #include #include @@ -55,7 +55,6 @@ std::vector bigNumAdd(std::vector num, int n); unsigned char h2int(char c); - std::string urlDecode(std::string str); /** @@ -64,7 +63,7 @@ std::string urlDecode(std::string str); * @param s string containing hex data * @return std::vector vector containing binary data */ -std::vector stringHexToBytes(const std::string &s); +std::vector stringHexToBytes(const std::string& s); /** * @brief Converts provided bytes into a human readable hex string @@ -72,7 +71,7 @@ std::vector stringHexToBytes(const std::string &s); * @param bytes vector containing binary data * @return std::string string containing hex representation of inputted data */ -std::string bytesToHexString(const std::vector &bytes); +std::string bytesToHexString(const std::vector& bytes); /** * @brief Extracts given type from binary data @@ -83,8 +82,7 @@ std::string bytesToHexString(const std::vector &bytes); * @return T extracted type */ template -T extract(const std::vector &v, int pos) -{ +T extract(const std::vector& v, int pos) { T value; memcpy(&value, &v[pos], sizeof(T)); return value; @@ -98,22 +96,25 @@ T extract(const std::vector &v, int pos) * @return std::vector resulting vector containing binary data */ template -std::vector pack(T data) -{ - std::vector rawData( (std::uint8_t*)&data, (std::uint8_t*)&(data) + sizeof(T)); +std::vector pack(T data) { + std::vector rawData((std::uint8_t*)&data, + (std::uint8_t*)&(data) + sizeof(T)); - return rawData; + return rawData; } -template -std::string string_format( const std::string& format, Args ... args ) -{ - int size_s = std::snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0' - if( size_s <= 0 ){ throw std::runtime_error( "Error during formatting." ); } - auto size = static_cast( size_s ); - std::unique_ptr buf( new char[ size ] ); - std::snprintf( buf.get(), size, format.c_str(), args ... ); - return std::string( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside +template +std::string string_format(const std::string& format, Args... args) { + int size_s = std::snprintf(nullptr, 0, format.c_str(), args...) + + 1; // Extra space for '\0' + if (size_s <= 0) { + throw std::runtime_error("Error during formatting."); + } + auto size = static_cast(size_s); + std::unique_ptr buf(new char[size]); + std::snprintf(buf.get(), size, format.c_str(), args...); + return std::string(buf.get(), + buf.get() + size - 1); // We don't want the '\0' inside } #endif \ No newline at end of file diff --git a/components/spotify/cspot/protobuf/authentication.proto b/components/spotify/cspot/protobuf/authentication.proto index d3896147..543331a4 100644 --- a/components/spotify/cspot/protobuf/authentication.proto +++ b/components/spotify/cspot/protobuf/authentication.proto @@ -1,3 +1,5 @@ +syntax = "proto2"; + enum CpuFamily { CPU_UNKNOWN = 0x0; CPU_X86 = 0x1; diff --git a/components/spotify/cspot/protobuf/keyexchange.proto b/components/spotify/cspot/protobuf/keyexchange.proto index a816d1ee..55cd802a 100644 --- a/components/spotify/cspot/protobuf/keyexchange.proto +++ b/components/spotify/cspot/protobuf/keyexchange.proto @@ -1,3 +1,5 @@ +syntax = "proto2"; + message LoginCryptoDiffieHellmanChallenge { required bytes gs = 0xa; } diff --git a/components/spotify/cspot/protobuf/mercury.proto b/components/spotify/cspot/protobuf/mercury.proto index 72138948..60c752aa 100644 --- a/components/spotify/cspot/protobuf/mercury.proto +++ b/components/spotify/cspot/protobuf/mercury.proto @@ -1,3 +1,5 @@ +syntax = "proto2"; + message Header { optional string uri = 0x01; optional string method = 0x03; diff --git a/components/spotify/cspot/protobuf/spirc.options b/components/spotify/cspot/protobuf/spirc.options index 3ed13c47..b7331f53 100644 --- a/components/spotify/cspot/protobuf/spirc.options +++ b/components/spotify/cspot/protobuf/spirc.options @@ -7,7 +7,4 @@ DeviceState.sw_version type:FT_POINTER DeviceState.name type:FT_POINTER DeviceState.capabilities max_count:17, fixed_count:false State.context_uri type:FT_POINTER -State.track type:FT_POINTER -TrackRef.gid type:FT_POINTER -TrackRef.uri type:FT_POINTER -TrackRef.context type:FT_POINTER \ No newline at end of file +TrackRef.queued type:FT_CALLBACK \ No newline at end of file diff --git a/components/spotify/cspot/protobuf/spirc.proto b/components/spotify/cspot/protobuf/spirc.proto index 4339949c..3b5448f9 100644 --- a/components/spotify/cspot/protobuf/spirc.proto +++ b/components/spotify/cspot/protobuf/spirc.proto @@ -1,3 +1,5 @@ +syntax = "proto2"; + enum MessageType { kMessageTypeHello = 0x1; kMessageTypeGoodbye = 0x2; @@ -58,6 +60,10 @@ enum CapabilityType { kVolumeSteps = 0x8; kSupportedTypes = 0x9; kCommandAcks = 0xa; + kSupportsRename = 0xb; + kHidden = 0xc; + kSupportsPlaylistV2 = 0xd; + kSupportsExternalEpisodes = 0xe; } message Capability { diff --git a/components/spotify/cspot/src/AccessKeyFetcher.cpp b/components/spotify/cspot/src/AccessKeyFetcher.cpp index d3d32237..968522c5 100644 --- a/components/spotify/cspot/src/AccessKeyFetcher.cpp +++ b/components/spotify/cspot/src/AccessKeyFetcher.cpp @@ -6,13 +6,14 @@ #include // for remove_extent_t #include // for vector -#include "BellLogger.h" // for AbstractLogger -#include "CSpotContext.h" // for Context -#include "Logger.h" // for CSPOT_LOG -#include "MercurySession.h" // for MercurySession, MercurySession::Res... -#include "Packet.h" // for cspot -#include "TimeProvider.h" // for TimeProvider -#include "Utils.h" // for string_format +#include "BellLogger.h" // for AbstractLogger +#include "CSpotContext.h" // for Context +#include "Logger.h" // for CSPOT_LOG +#include "MercurySession.h" // for MercurySession, MercurySession::Res... +#include "Packet.h" // for cspot +#include "TimeProvider.h" // for TimeProvider +#include "Utils.h" // for string_format +#include "WrappedSemaphore.h" #ifdef BELL_ONLY_CJSON #include "cJSON.h" #else @@ -22,11 +23,17 @@ using namespace cspot; -AccessKeyFetcher::AccessKeyFetcher(std::shared_ptr ctx) { - this->ctx = ctx; -} +static std::string CLIENT_ID = + "65b708073fc0480ea92a077233ca87bd"; // Spotify web client's client id -AccessKeyFetcher::~AccessKeyFetcher() {} +static std::string SCOPES = + "streaming,user-library-read,user-library-modify,user-top-read,user-read-" + "recently-played"; // Required access scopes + +AccessKeyFetcher::AccessKeyFetcher(std::shared_ptr ctx) + : ctx(ctx) { + this->updateSemaphore = std::make_shared(); +} bool AccessKeyFetcher::isExpired() { if (accessKey.empty()) { @@ -40,11 +47,24 @@ bool AccessKeyFetcher::isExpired() { return false; } -void AccessKeyFetcher::getAccessKey(AccessKeyFetcher::Callback callback) { +std::string AccessKeyFetcher::getAccessKey() { if (!isExpired()) { - return callback(accessKey); + return accessKey; } + updateAccessKey(); + + return accessKey; +} + +void AccessKeyFetcher::updateAccessKey() { + if (keyPending) { + // Already pending refresh request + return; + } + + keyPending = true; + CSPOT_LOG(info, "Access token expired, fetching new one..."); std::string url = @@ -54,17 +74,17 @@ void AccessKeyFetcher::getAccessKey(AccessKeyFetcher::Callback callback) { ctx->session->execute( MercurySession::RequestType::GET, url, - [this, timeProvider, callback](MercurySession::Response& res) { + [this, timeProvider](MercurySession::Response& res) { if (res.fail) return; - char* accessKeyJson = (char*)res.parts[0].data(); - auto accessJSON = std::string( - accessKeyJson, strrchr(accessKeyJson, '}') - accessKeyJson + 1); + auto accessJSON = + std::string((char*)res.parts[0].data(), res.parts[0].size()); #ifdef BELL_ONLY_CJSON cJSON* jsonBody = cJSON_Parse(accessJSON.c_str()); this->accessKey = cJSON_GetObjectItem(jsonBody, "accessToken")->valuestring; int expiresIn = cJSON_GetObjectItem(jsonBody, "expiresIn")->valueint; + cJSON_Delete(jsonBody); #else auto jsonBody = nlohmann::json::parse(accessJSON); this->accessKey = jsonBody["accessToken"]; @@ -74,11 +94,11 @@ void AccessKeyFetcher::getAccessKey(AccessKeyFetcher::Callback callback) { this->expiresAt = timeProvider->getSyncedTimestamp() + (expiresIn * 1000); -#ifdef BELL_ONLY_CJSON - callback(cJSON_GetObjectItem(jsonBody, "accessToken")->valuestring); - cJSON_Delete(jsonBody); -#else - callback(jsonBody["accessToken"]); -#endif + updateSemaphore->give(); }); + + updateSemaphore->twait(5000); + + // Mark as not pending for refresh + keyPending = false; } diff --git a/components/spotify/cspot/src/ApResolve.cpp b/components/spotify/cspot/src/ApResolve.cpp index 011f1c23..ba455e9d 100644 --- a/components/spotify/cspot/src/ApResolve.cpp +++ b/components/spotify/cspot/src/ApResolve.cpp @@ -1,10 +1,10 @@ #include "ApResolve.h" -#include // for initializer_list -#include // for operator!=, operator== -#include // for allocator, unique_ptr -#include // for string_view -#include // for vector +#include // for initializer_list +#include // for operator!=, operator== +#include // for allocator, unique_ptr +#include // for string_view +#include // for vector #include "HTTPClient.h" // for HTTPClient, HTTPClient::Response #ifdef BELL_ONLY_CJSON @@ -16,29 +16,27 @@ using namespace cspot; -ApResolve::ApResolve(std::string apOverride) -{ - this->apOverride = apOverride; +ApResolve::ApResolve(std::string apOverride) { + this->apOverride = apOverride; } -std::string ApResolve::fetchFirstApAddress() -{ - if (apOverride != "") - { - return apOverride; - } +std::string ApResolve::fetchFirstApAddress() { + if (apOverride != "") { + return apOverride; + } - auto request = bell::HTTPClient::get("https://apresolve.spotify.com/"); - std::string_view responseStr = request->body(); + auto request = bell::HTTPClient::get("https://apresolve.spotify.com/"); + std::string_view responseStr = request->body(); - // parse json with nlohmann + // parse json with nlohmann #ifdef BELL_ONLY_CJSON - cJSON* json = cJSON_Parse(responseStr.data()); - auto ap_string = std::string(cJSON_GetArrayItem(cJSON_GetObjectItem(json, "ap_list"), 0)->valuestring); - cJSON_Delete(json); - return ap_string; -#else - auto json = nlohmann::json::parse(responseStr); - return json["ap_list"][0]; -#endif + cJSON* json = cJSON_Parse(responseStr.data()); + auto ap_string = std::string( + cJSON_GetArrayItem(cJSON_GetObjectItem(json, "ap_list"), 0)->valuestring); + cJSON_Delete(json); + return ap_string; +#else + auto json = nlohmann::json::parse(responseStr); + return json["ap_list"][0]; +#endif } diff --git a/components/spotify/cspot/src/AuthChallenges.cpp b/components/spotify/cspot/src/AuthChallenges.cpp index b1b48509..633d9fa4 100644 --- a/components/spotify/cspot/src/AuthChallenges.cpp +++ b/components/spotify/cspot/src/AuthChallenges.cpp @@ -1,8 +1,8 @@ #include "AuthChallenges.h" -#include // for copy -#include // for CHAR_BIT -#include // for default_random_engine, independent_bits_en... +#include // for copy +#include // for CHAR_BIT +#include // for default_random_engine, independent_bits_en... #include "NanoPBHelper.h" // for pbPutString, pbEncode, pbDecode #include "pb.h" // for pb_byte_t @@ -94,9 +94,9 @@ std::vector AuthChallenges::solveApHello( // Get send and receive keys this->shanSendKey = std::vector(resultData.begin() + 0x14, - resultData.begin() + 0x34); + resultData.begin() + 0x34); this->shanRecvKey = std::vector(resultData.begin() + 0x34, - resultData.begin() + 0x54); + resultData.begin() + 0x54); return pbEncode(ClientResponsePlaintext_fields, &clientResPlaintext); } @@ -125,5 +125,6 @@ std::vector AuthChallenges::prepareClientHello() { // Generate the random nonce auto nonce = crypto->generateVectorWithRandomData(16); std::copy(nonce.begin(), nonce.end(), clientHello.client_nonce); + return pbEncode(ClientHello_fields, &clientHello); -} \ No newline at end of file +} diff --git a/components/spotify/cspot/src/CDNTrackStream.cpp b/components/spotify/cspot/src/CDNAudioFile.cpp similarity index 68% rename from components/spotify/cspot/src/CDNTrackStream.cpp rename to components/spotify/cspot/src/CDNAudioFile.cpp index e38c6f13..2fbd0742 100644 --- a/components/spotify/cspot/src/CDNTrackStream.cpp +++ b/components/spotify/cspot/src/CDNAudioFile.cpp @@ -1,4 +1,4 @@ -#include "CDNTrackStream.h" +#include "CDNAudioFile.h" #include // for memcpy #include // for __base @@ -7,8 +7,9 @@ #include // for string_view #include // for remove_extent_t -#include "AccessKeyFetcher.h" // for AccessKeyFetcher -#include "BellLogger.h" // for AbstractLogger +#include "AccessKeyFetcher.h" // for AccessKeyFetcher +#include "BellLogger.h" // for AbstractLogger +#include "Crypto.h" #include "Logger.h" // for CSPOT_LOG #include "Packet.h" // for cspot #include "SocketStream.h" // for SocketStream @@ -23,73 +24,22 @@ using namespace cspot; -CDNTrackStream::CDNTrackStream( - std::shared_ptr accessKeyFetcher) { - this->accessKeyFetcher = accessKeyFetcher; - this->status = Status::INITIALIZING; - this->trackReady = std::make_unique(5); +CDNAudioFile::CDNAudioFile(const std::string& cdnUrl, + const std::vector& audioKey) + : cdnUrl(cdnUrl), audioKey(audioKey) { this->crypto = std::make_unique(); } -CDNTrackStream::~CDNTrackStream() {} - -void CDNTrackStream::fail() { - this->status = Status::FAILED; - this->trackReady->give(); -} - -void CDNTrackStream::fetchFile(const std::vector& trackId, - const std::vector& audioKey) { - this->status = Status::HAS_DATA; - this->trackId = trackId; - this->audioKey = std::vector(audioKey.begin() + 4, audioKey.end()); - - accessKeyFetcher->getAccessKey([this, trackId, audioKey](std::string key) { - CSPOT_LOG(info, "Received access key, fetching CDN URL..."); - - std::string requestUrl = string_format( - "https://api.spotify.com/v1/storage-resolve/files/audio/interactive/" - "%s?alt=json&product=9", - bytesToHexString(trackId).c_str()); - - auto req = bell::HTTPClient::get( - requestUrl, - {bell::HTTPClient::ValueHeader({"Authorization", "Bearer " + key})}); - - std::string_view result = req->body(); - -#ifdef BELL_ONLY_CJSON - cJSON* jsonResult = cJSON_Parse(result.data()); - std::string cdnUrl = - cJSON_GetArrayItem(cJSON_GetObjectItem(jsonResult, "cdnurl"), 0) - ->valuestring; - cJSON_Delete(jsonResult); -#else - auto jsonResult = nlohmann::json::parse(result); - std::string cdnUrl = jsonResult["cdnurl"][0]; -#endif - if (this->status != Status::FAILED) { - - this->cdnUrl = cdnUrl; - this->status = Status::HAS_URL; - CSPOT_LOG(info, "Received CDN URL, %s", cdnUrl.c_str()); - - this->openStream(); - this->trackReady->give(); - } - }); -} - -size_t CDNTrackStream::getPosition() { +size_t CDNAudioFile::getPosition() { return this->position; } -void CDNTrackStream::seek(size_t newPos) { +void CDNAudioFile::seek(size_t newPos) { this->enableRequestMargin = true; this->position = newPos; } -void CDNTrackStream::openStream() { +void CDNAudioFile::openStream() { CSPOT_LOG(info, "Opening HTTP stream to %s", this->cdnUrl.c_str()); // Open connection, read first 128 bytes @@ -121,10 +71,9 @@ void CDNTrackStream::openStream() { this->position = 0; this->lastRequestPosition = 0; this->lastRequestCapacity = 0; - this->isConnected = true; } -size_t CDNTrackStream::readBytes(uint8_t* dst, size_t bytes) { +size_t CDNAudioFile::readBytes(uint8_t* dst, size_t bytes) { size_t offsetPosition = position + SPOTIFY_OPUS_HEADER; size_t actualFileSize = this->totalFileSize + SPOTIFY_OPUS_HEADER; @@ -199,11 +148,11 @@ size_t CDNTrackStream::readBytes(uint8_t* dst, size_t bytes) { return bytes; } -size_t CDNTrackStream::getSize() { +size_t CDNAudioFile::getSize() { return this->totalFileSize; } -void CDNTrackStream::decrypt(uint8_t* dst, size_t nbytes, size_t pos) { +void CDNAudioFile::decrypt(uint8_t* dst, size_t nbytes, size_t pos) { auto calculatedIV = bigNumAdd(audioAESIV, pos / 16); this->crypto->aesCTRXcrypt(this->audioKey, calculatedIV, dst, nbytes); diff --git a/components/spotify/cspot/src/LoginBlob.cpp b/components/spotify/cspot/src/LoginBlob.cpp index 4a1d8ca7..feb5e8d2 100644 --- a/components/spotify/cspot/src/LoginBlob.cpp +++ b/components/spotify/cspot/src/LoginBlob.cpp @@ -1,7 +1,7 @@ #include "LoginBlob.h" -#include // for sprintf -#include // for initializer_list +#include // for sprintf +#include // for initializer_list #include "BellLogger.h" // for AbstractLogger #include "ConstantParameters.h" // for brandName, cspot, protoc... @@ -11,8 +11,8 @@ #include "cJSON.h" #else #include "nlohmann/detail/json_pointer.hpp" // for json_pointer<>::string_t -#include "nlohmann/json.hpp" // for basic_json<>::object_t -#include "nlohmann/json_fwd.hpp" // for json +#include "nlohmann/json.hpp" // for basic_json<>::object_t, basic_json +#include "nlohmann/json_fwd.hpp" // for json #endif using namespace cspot; @@ -24,7 +24,7 @@ LoginBlob::LoginBlob(std::string name) { this->deviceId = std::string("142137fd329622137a149016") + std::string(hash); this->crypto = std::make_unique(); this->name = name; - + this->crypto->dhInit(); } @@ -142,7 +142,8 @@ void LoginBlob::loadJson(const std::string& json) { cJSON* root = cJSON_Parse(json.c_str()); this->authType = cJSON_GetObjectItem(root, "authType")->valueint; this->username = cJSON_GetObjectItem(root, "username")->valuestring; - std::string authDataObject = cJSON_GetObjectItem(root, "authData")->valuestring; + std::string authDataObject = + cJSON_GetObjectItem(root, "authData")->valuestring; cJSON_Delete(root); #else auto root = nlohmann::json::parse(json); @@ -151,23 +152,24 @@ void LoginBlob::loadJson(const std::string& json) { std::string authDataObject = root["authData"]; this->authData = crypto->base64Decode(authDataObject); -#endif +#endif } std::string LoginBlob::toJson() { #ifdef BELL_ONLY_CJSON - cJSON* json_obj = cJSON_CreateObject(); - cJSON_AddStringToObject(json_obj, "authData", crypto->base64Encode(authData).c_str()); + cJSON* json_obj = cJSON_CreateObject(); + cJSON_AddStringToObject(json_obj, "authData", + crypto->base64Encode(authData).c_str()); cJSON_AddNumberToObject(json_obj, "authType", this->authType); cJSON_AddStringToObject(json_obj, "username", this->username.c_str()); - - char *str = cJSON_PrintUnformatted(json_obj); - cJSON_Delete(json_obj); + + char* str = cJSON_PrintUnformatted(json_obj); + cJSON_Delete(json_obj); std::string json_objStr(str); free(str); - + return json_objStr; -#else +#else nlohmann::json obj; obj["authData"] = crypto->base64Encode(authData); obj["authType"] = this->authType; @@ -200,7 +202,7 @@ std::string LoginBlob::buildZeroconfInfo() { auto encodedKey = crypto->base64Encode(crypto->publicKey); #ifdef BELL_ONLY_CJSON - cJSON* json_obj = cJSON_CreateObject(); + cJSON* json_obj = cJSON_CreateObject(); cJSON_AddNumberToObject(json_obj, "status", 101); cJSON_AddStringToObject(json_obj, "statusString", "OK"); cJSON_AddStringToObject(json_obj, "version", cspot::protocolVersion); @@ -214,20 +216,21 @@ std::string LoginBlob::buildZeroconfInfo() { cJSON_AddStringToObject(json_obj, "tokenType", "default"); cJSON_AddStringToObject(json_obj, "groupStatus", "NONE"); cJSON_AddStringToObject(json_obj, "resolverVersion", "0"); - cJSON_AddStringToObject(json_obj, "scope", "streaming,client-authorization-universal"); - cJSON_AddStringToObject(json_obj, "activeUser", ""); - cJSON_AddStringToObject(json_obj, "deviceID", deviceId.c_str()); - cJSON_AddStringToObject(json_obj, "remoteName", name.c_str()); - cJSON_AddStringToObject(json_obj, "publicKey", encodedKey.c_str()); - cJSON_AddStringToObject(json_obj, "deviceType", "deviceType"); - - char *str = cJSON_PrintUnformatted(json_obj); - cJSON_Delete(json_obj); + cJSON_AddStringToObject(json_obj, "scope", + "streaming,client-authorization-universal"); + cJSON_AddStringToObject(json_obj, "activeUser", ""); + cJSON_AddStringToObject(json_obj, "deviceID", deviceId.c_str()); + cJSON_AddStringToObject(json_obj, "remoteName", name.c_str()); + cJSON_AddStringToObject(json_obj, "publicKey", encodedKey.c_str()); + cJSON_AddStringToObject(json_obj, "deviceType", "deviceType"); + + char* str = cJSON_PrintUnformatted(json_obj); + cJSON_Delete(json_obj); std::string json_objStr(str); free(str); - + return json_objStr; -#else +#else nlohmann::json obj; obj["status"] = 101; obj["statusString"] = "OK"; diff --git a/components/spotify/cspot/src/MercurySession.cpp b/components/spotify/cspot/src/MercurySession.cpp index 6778cb76..605ac924 100644 --- a/components/spotify/cspot/src/MercurySession.cpp +++ b/components/spotify/cspot/src/MercurySession.cpp @@ -6,11 +6,9 @@ #include // for runtime_error #include // for remove_extent_t, __underlying_type_impl<>:... #include // for pair - #ifndef _WIN32 -#include +#include // for htons, ntohs, htonl, ntohl #endif - #include "BellLogger.h" // for AbstractLogger #include "BellTask.h" // for Task #include "BellUtils.h" // for BELL_SLEEP_MS @@ -110,6 +108,22 @@ bool MercurySession::triggerTimeout() { return false; } +void MercurySession::unregister(uint64_t sequenceId) { + auto callback = this->callbacks.find(sequenceId); + + if (callback != this->callbacks.end()) { + this->callbacks.erase(callback); + } +} + +void MercurySession::unregisterAudioKey(uint32_t sequenceId) { + auto callback = this->audioKeyCallbacks.find(sequenceId); + + if (callback != this->audioKeyCallbacks.end()) { + this->audioKeyCallbacks.erase(callback); + } +} + void MercurySession::disconnect() { CSPOT_LOG(info, "Disconnecting mercury session"); this->isRunning = false; @@ -145,12 +159,13 @@ void MercurySession::handlePacket() { // First four bytes mark the sequence id auto seqId = ntohl(extract(packet.data, 0)); - if (seqId == (this->audioKeySequence - 1) && - audioKeyCallback != nullptr) { + + if (this->audioKeyCallbacks.count(seqId) > 0) { auto success = static_cast(packet.command) == RequestType::AUDIO_KEY_SUCCESS_RESPONSE; - audioKeyCallback(success, packet.data); + this->audioKeyCallbacks[seqId](success, packet.data); } + break; } case RequestType::SEND: @@ -290,23 +305,30 @@ uint64_t MercurySession::executeSubscription(RequestType method, // Bump sequence id this->sequenceId += 1; - this->shanConn->sendPacket( - static_cast::type>(method), - sequenceIdBytes); + try { + this->shanConn->sendPacket( + static_cast::type>(method), + sequenceIdBytes); + } catch (...) { + // @TODO: handle disconnect + } return this->sequenceId - 1; } -void MercurySession::requestAudioKey(const std::vector& trackId, - const std::vector& fileId, - AudioKeyCallback audioCallback) { +uint32_t MercurySession::requestAudioKey(const std::vector& trackId, + const std::vector& fileId, + AudioKeyCallback audioCallback) { auto buffer = fileId; - this->audioKeyCallback = audioCallback; + + // Store callback + this->audioKeyCallbacks.insert({this->audioKeySequence, audioCallback}); // Structure: [FILEID] [TRACKID] [4 BYTES SEQUENCE ID] [0x00, 0x00] buffer.insert(buffer.end(), trackId.begin(), trackId.end()); - auto audioKeySequence = pack(htonl(this->audioKeySequence)); - buffer.insert(buffer.end(), audioKeySequence.begin(), audioKeySequence.end()); + auto audioKeySequenceBuffer = pack(htonl(this->audioKeySequence)); + buffer.insert(buffer.end(), audioKeySequenceBuffer.begin(), + audioKeySequenceBuffer.end()); auto suffix = std::vector({0x00, 0x00}); buffer.insert(buffer.end(), suffix.begin(), suffix.end()); @@ -315,6 +337,11 @@ void MercurySession::requestAudioKey(const std::vector& trackId, // Used for broken connection detection // this->lastRequestTimestamp = timeProvider->getSyncedTimestamp(); - this->shanConn->sendPacket( - static_cast(RequestType::AUDIO_KEY_REQUEST_COMMAND), buffer); + try { + this->shanConn->sendPacket( + static_cast(RequestType::AUDIO_KEY_REQUEST_COMMAND), buffer); + } catch (...) { + // @TODO: Handle disconnect + } + return audioKeySequence - 1; } diff --git a/components/spotify/cspot/src/PlainConnection.cpp b/components/spotify/cspot/src/PlainConnection.cpp index 4ccb4eff..7337f691 100644 --- a/components/spotify/cspot/src/PlainConnection.cpp +++ b/components/spotify/cspot/src/PlainConnection.cpp @@ -1,19 +1,17 @@ #include "PlainConnection.h" #ifndef _WIN32 -#include // for addrinfo, freeaddrinfo, getaddrinfo -#include // for IPPROTO_IP, IPPROTO_TCP -#include // for EAGAIN, EINTR, ETIMEDOUT, errno -#include // for setsockopt, connect, recv, send, shutdown -#include // for timeval -#endif -#include // for memset -#include // for runtime_error -#ifdef _WIN32 -#include -#else +#include // for addrinfo, freeaddrinfo, getaddrinfo +#include // for IPPROTO_IP, IPPROTO_TCP +#include // for EAGAIN, EINTR, ETIMEDOUT, errno +#include // for setsockopt, connect, recv, send, shutdown +#include // for timeval +#include // for memset +#include // for runtime_error #include // for TCP_NODELAY -#include +#include +#else +#include #endif #include "BellLogger.h" // for AbstractLogger #include "Logger.h" // for CSPOT_LOG @@ -67,8 +65,8 @@ void PlainConnection::connect(const std::string& apAddress) { if (this->apSock < 0) continue; - if (::connect(this->apSock, (struct sockaddr*)ai->ai_addr, ai->ai_addrlen) != - -1) { + if (::connect(this->apSock, (struct sockaddr*)ai->ai_addr, + ai->ai_addrlen) != -1) { #ifdef _WIN32 uint32_t tv = 3000; #else @@ -144,10 +142,10 @@ void PlainConnection::readBlock(const uint8_t* dst, size_t size) { switch (getErrno()) { case EAGAIN: case ETIMEDOUT: - // if (timeoutHandler()) { - // CSPOT_LOG(error, "Connection lost, will need to reconnect..."); - // throw std::runtime_error("Reconnection required"); - // } + if (timeoutHandler()) { + CSPOT_LOG(error, "Connection lost, will need to reconnect..."); + throw std::runtime_error("Reconnection required"); + } goto READ; case EINTR: break; @@ -174,9 +172,9 @@ size_t PlainConnection::writeBlock(const std::vector& data) { switch (getErrno()) { case EAGAIN: case ETIMEDOUT: - // if (timeoutHandler()) { - // throw std::runtime_error("Reconnection required"); - // } + if (timeoutHandler()) { + throw std::runtime_error("Reconnection required"); + } goto WRITE; case EINTR: break; diff --git a/components/spotify/cspot/src/PlaybackState.cpp b/components/spotify/cspot/src/PlaybackState.cpp index fd8d7a41..72697c2f 100644 --- a/components/spotify/cspot/src/PlaybackState.cpp +++ b/components/spotify/cspot/src/PlaybackState.cpp @@ -1,11 +1,12 @@ #include "PlaybackState.h" -#include // for strdup, memcpy, strcpy, strlen -#include // for uint8_t -#include // for free, NULL, realloc, rand -#include // for shared_ptr -#include // for remove_extent_t -#include // for swap +#include // for strdup, memcpy, strcpy, strlen +#include // for uint8_t +#include // for free, NULL, realloc, rand +#include +#include // for shared_ptr +#include // for remove_extent_t +#include // for swap #include "BellLogger.h" // for AbstractLogger #include "CSpotContext.h" // for Context::ConfigState, Context (ptr o... @@ -15,6 +16,7 @@ #include "Packet.h" // for cspot #include "pb.h" // for pb_bytes_array_t, PB_BYTES_ARRAY_T_A... #include "pb_decode.h" // for pb_release +#include "protobuf/spirc.pb.h" using namespace cspot; @@ -23,6 +25,13 @@ PlaybackState::PlaybackState(std::shared_ptr ctx) { innerFrame = {}; remoteFrame = {}; + // Prepare callbacks for decoding of remote frame track data + remoteFrame.state.track.funcs.decode = &TrackReference::pbDecodeTrackList; + remoteFrame.state.track.arg = &remoteTracks; + + innerFrame.ident = strdup(ctx->config.deviceId.c_str()); + innerFrame.protocol_version = strdup(protocolVersion); + // Prepare default state innerFrame.state.has_position_ms = true; innerFrame.state.position_ms = 0; @@ -52,13 +61,12 @@ PlaybackState::PlaybackState(std::shared_ptr ctx) { innerFrame.device_state.name = strdup(ctx->config.deviceName.c_str()); - innerFrame.state.track_count = 0; - // Prepare player's capabilities addCapability(CapabilityType_kCanBePlayer, 1); addCapability(CapabilityType_kDeviceType, 4); addCapability(CapabilityType_kGaiaEqConnectId, 1); addCapability(CapabilityType_kSupportsLogout, 0); + addCapability(CapabilityType_kSupportsPlaylistV2, 1); addCapability(CapabilityType_kIsObservable, 1); addCapability(CapabilityType_kVolumeSteps, 64); addCapability(CapabilityType_kSupportedContexts, -1, @@ -66,8 +74,8 @@ PlaybackState::PlaybackState(std::shared_ptr ctx) { "inbox", "toplist", "starred", "publishedstarred", "track"})); addCapability(CapabilityType_kSupportedTypes, -1, - std::vector({"audio/local", "audio/track", - "audio/episode", "local", "track"})); + std::vector( + {"audio/track", "audio/episode", "audio/episode+track"})); innerFrame.device_state.capabilities_count = 8; } @@ -102,34 +110,20 @@ void PlaybackState::setPlaybackState(const PlaybackState::State state) { } } +void PlaybackState::syncWithRemote() { + innerFrame.state.context_uri = (char*)realloc( + innerFrame.state.context_uri, strlen(remoteFrame.state.context_uri) + 1); + + strcpy(innerFrame.state.context_uri, remoteFrame.state.context_uri); + + innerFrame.state.has_playing_track_index = true; + innerFrame.state.playing_track_index = remoteFrame.state.playing_track_index; +} + bool PlaybackState::isActive() { return innerFrame.device_state.is_active; } -bool PlaybackState::nextTrack() { - innerFrame.state.playing_track_index++; - - if (innerFrame.state.playing_track_index >= innerFrame.state.track_count) { - - innerFrame.state.playing_track_index = 0; - - if (!innerFrame.state.repeat) { - setPlaybackState(State::Paused); - return false; - } - } - - return true; -} - -void PlaybackState::prevTrack() { - if (innerFrame.state.playing_track_index > 0) { - innerFrame.state.playing_track_index--; - } else if (innerFrame.state.repeat) { - innerFrame.state.playing_track_index = innerFrame.state.track_count - 1; - } -} - void PlaybackState::setActive(bool isActive) { innerFrame.device_state.is_active = isActive; if (isActive) { @@ -145,131 +139,25 @@ void PlaybackState::updatePositionMs(uint32_t position) { ctx->timeProvider->getSyncedTimestamp(); } -#define FREE(ptr) \ - { \ - free(ptr); \ - ptr = NULL; \ - } -#define STRDUP(dst, src) \ - if (src != NULL) { \ - dst = strdup(src); \ - } else { \ - FREE(dst); \ - } // strdup null pointer safe - -void PlaybackState::updateTracks() { - CSPOT_LOG(info, "---- Track count %d", remoteFrame.state.track_count); - CSPOT_LOG(info, "---- Inner track count %d", innerFrame.state.track_count); - CSPOT_LOG(info, "--- Context URI %s", remoteFrame.state.context_uri); - - // free unused tracks - if (innerFrame.state.track_count > remoteFrame.state.track_count) { - for (uint16_t i = remoteFrame.state.track_count; - i < innerFrame.state.track_count; ++i) { - FREE(innerFrame.state.track[i].gid); - FREE(innerFrame.state.track[i].uri); - FREE(innerFrame.state.track[i].context); - } - } - - // reallocate memory for new tracks - innerFrame.state.track = (TrackRef*)realloc( - innerFrame.state.track, sizeof(TrackRef) * remoteFrame.state.track_count); - - for (uint16_t i = 0; i < remoteFrame.state.track_count; ++i) { - if (i >= innerFrame.state.track_count) { - innerFrame.state.track[i].gid = NULL; - innerFrame.state.track[i].uri = NULL; - innerFrame.state.track[i].context = NULL; - } - - if (remoteFrame.state.track[i].gid != NULL) { - uint16_t gid_size = remoteFrame.state.track[i].gid->size; - innerFrame.state.track[i].gid = (pb_bytes_array_t*)realloc( - innerFrame.state.track[i].gid, PB_BYTES_ARRAY_T_ALLOCSIZE(gid_size)); - - memcpy(innerFrame.state.track[i].gid->bytes, - remoteFrame.state.track[i].gid->bytes, gid_size); - innerFrame.state.track[i].gid->size = gid_size; - } - innerFrame.state.track[i].has_queued = - remoteFrame.state.track[i].has_queued; - innerFrame.state.track[i].queued = remoteFrame.state.track[i].queued; - - STRDUP(innerFrame.state.track[i].uri, remoteFrame.state.track[i].uri); - STRDUP(innerFrame.state.track[i].context, - remoteFrame.state.track[i].context); - } - - innerFrame.state.context_uri = (char*)realloc( - innerFrame.state.context_uri, strlen(remoteFrame.state.context_uri) + 1); - strcpy(innerFrame.state.context_uri, remoteFrame.state.context_uri); - - innerFrame.state.track_count = remoteFrame.state.track_count; - innerFrame.state.has_playing_track_index = true; - innerFrame.state.playing_track_index = remoteFrame.state.playing_track_index; - - if (remoteFrame.state.repeat) { - setRepeat(true); - } - - if (remoteFrame.state.shuffle) { - setShuffle(true); - } -} - void PlaybackState::setVolume(uint32_t volume) { innerFrame.device_state.volume = volume; ctx->config.volume = volume; } -void PlaybackState::setShuffle(bool shuffle) { - innerFrame.state.shuffle = shuffle; - if (shuffle) { - // Put current song at the begining - std::swap(innerFrame.state.track[0], - innerFrame.state.track[innerFrame.state.playing_track_index]); +bool PlaybackState::decodeRemoteFrame(std::vector& data) { + pb_release(Frame_fields, &remoteFrame); - // Shuffle current tracks - for (int x = 1; x < innerFrame.state.track_count - 1; x++) { - auto j = x + (std::rand() % (innerFrame.state.track_count - x)); - std::swap(innerFrame.state.track[j], innerFrame.state.track[x]); - } - innerFrame.state.playing_track_index = 0; - } -} + remoteTracks.clear(); -void PlaybackState::setRepeat(bool repeat) { - innerFrame.state.repeat = repeat; -} + pbDecode(remoteFrame, Frame_fields, data); -TrackRef* PlaybackState::getCurrentTrackRef() { - if (innerFrame.state.playing_track_index >= innerFrame.state.track_count) { - return nullptr; - } - return &innerFrame.state.track[innerFrame.state.playing_track_index]; -} - -TrackRef* PlaybackState::getNextTrackRef() { - if ((innerFrame.state.playing_track_index + 1) >= innerFrame.state.track_count) { - if (innerFrame.state.repeat) { - return &innerFrame.state.track[0]; - } - return nullptr; - } - - return &innerFrame.state.track[innerFrame.state.playing_track_index + 1]; + return true; } std::vector PlaybackState::encodeCurrentFrame(MessageType typ) { - free(innerFrame.ident); - free(innerFrame.protocol_version); - // Prepare current frame info innerFrame.version = 1; - innerFrame.ident = strdup(ctx->config.deviceId.c_str()); innerFrame.seq_nr = this->seqNum; - innerFrame.protocol_version = strdup(protocolVersion); innerFrame.typ = typ; innerFrame.state_update_id = ctx->timeProvider->getSyncedTimestamp(); innerFrame.has_version = true; @@ -281,6 +169,7 @@ std::vector PlaybackState::encodeCurrentFrame(MessageType typ) { innerFrame.has_state_update_id = true; this->seqNum += 1; + return pbEncode(Frame_fields, &innerFrame); } @@ -308,5 +197,6 @@ void PlaybackState::addCapability(CapabilityType typ, int intValue, this->innerFrame.device_state.capabilities[capabilityIndex] .stringValue_count = stringValue.size(); + this->capabilityIndex += 1; } diff --git a/components/spotify/cspot/src/Shannon.cpp b/components/spotify/cspot/src/Shannon.cpp index 37615eee..f6419611 100644 --- a/components/spotify/cspot/src/Shannon.cpp +++ b/components/spotify/cspot/src/Shannon.cpp @@ -5,437 +5,389 @@ using std::size_t; -static inline uint32_t rotl(uint32_t n, unsigned int c) -{ - const unsigned int mask = (CHAR_BIT * sizeof(n) - 1); // assumes width is a power of 2. - // assert ( (c<=mask) &&"rotate by type width or more"); - c &= mask; - return (n << c) | (n >> ((-c) & mask)); +static inline uint32_t rotl(uint32_t n, unsigned int c) { + const unsigned int mask = + (CHAR_BIT * sizeof(n) - 1); // assumes width is a power of 2. + // assert ( (c<=mask) &&"rotate by type width or more"); + c &= mask; + return (n << c) | (n >> ((-c) & mask)); } -static inline uint32_t rotr(uint32_t n, unsigned int c) -{ - const unsigned int mask = (CHAR_BIT * sizeof(n) - 1); // assumes width is a power of 2. - // assert ( (c<=mask) &&"rotate by type width or more"); - c &= mask; - return (n >> c) | (n << ((-c) & mask)); +static inline uint32_t rotr(uint32_t n, unsigned int c) { + const unsigned int mask = + (CHAR_BIT * sizeof(n) - 1); // assumes width is a power of 2. + // assert ( (c<=mask) &&"rotate by type width or more"); + c &= mask; + return (n >> c) | (n << ((-c) & mask)); } -uint32_t Shannon::sbox1(uint32_t w) -{ - w ^= rotl(w, 5) | rotl(w, 7); - w ^= rotl(w, 19) | rotl(w, 22); - return w; +uint32_t Shannon::sbox1(uint32_t w) { + w ^= rotl(w, 5) | rotl(w, 7); + w ^= rotl(w, 19) | rotl(w, 22); + return w; } -uint32_t Shannon::sbox2(uint32_t w) -{ - w ^= rotl(w, 7) | rotl(w, 22); - w ^= rotl(w, 5) | rotl(w, 19); - return w; +uint32_t Shannon::sbox2(uint32_t w) { + w ^= rotl(w, 7) | rotl(w, 22); + w ^= rotl(w, 5) | rotl(w, 19); + return w; } -void Shannon::cycle() -{ - uint32_t t; - int i; +void Shannon::cycle() { + uint32_t t; + int i; - /* nonlinear feedback function */ - t = this->R[12] ^ this->R[13] ^ this->konst; - t = Shannon::sbox1(t) ^ rotl(this->R[0], 1); - /* shift register */ - for (i = 1; i < N; ++i) - this->R[i - 1] = this->R[i]; - this->R[N - 1] = t; - t = Shannon::sbox2(this->R[2] ^ this->R[15]); - this->R[0] ^= t; - this->sbuf = t ^ this->R[8] ^ this->R[12]; + /* nonlinear feedback function */ + t = this->R[12] ^ this->R[13] ^ this->konst; + t = Shannon::sbox1(t) ^ rotl(this->R[0], 1); + /* shift register */ + for (i = 1; i < N; ++i) + this->R[i - 1] = this->R[i]; + this->R[N - 1] = t; + t = Shannon::sbox2(this->R[2] ^ this->R[15]); + this->R[0] ^= t; + this->sbuf = t ^ this->R[8] ^ this->R[12]; } -void Shannon::crcfunc(uint32_t i) -{ - uint32_t t; - int j; +void Shannon::crcfunc(uint32_t i) { + uint32_t t; + int j; - /* Accumulate CRC of input */ - t = this->CRC[0] ^ this->CRC[2] ^ this->CRC[15] ^ i; - for (j = 1; j < N; ++j) - this->CRC[j - 1] = this->CRC[j]; - this->CRC[N - 1] = t; + /* Accumulate CRC of input */ + t = this->CRC[0] ^ this->CRC[2] ^ this->CRC[15] ^ i; + for (j = 1; j < N; ++j) + this->CRC[j - 1] = this->CRC[j]; + this->CRC[N - 1] = t; } -void Shannon::macfunc(uint32_t i) -{ - this->crcfunc(i); - this->R[KEYP] ^= i; +void Shannon::macfunc(uint32_t i) { + this->crcfunc(i); + this->R[KEYP] ^= i; } -void Shannon::initState() -{ - int i; +void Shannon::initState() { + int i; - /* Register initialised to Fibonacci numbers; Counter zeroed. */ - this->R[0] = 1; - this->R[1] = 1; - for (i = 2; i < N; ++i) - this->R[i] = this->R[i - 1] + this->R[i - 2]; - this->konst = Shannon::INITKONST; + /* Register initialised to Fibonacci numbers; Counter zeroed. */ + this->R[0] = 1; + this->R[1] = 1; + for (i = 2; i < N; ++i) + this->R[i] = this->R[i - 1] + this->R[i - 2]; + this->konst = Shannon::INITKONST; } -void Shannon::saveState() -{ - int i; - for (i = 0; i < Shannon::N; ++i) - this->initR[i] = this->R[i]; +void Shannon::saveState() { + int i; + for (i = 0; i < Shannon::N; ++i) + this->initR[i] = this->R[i]; } -void Shannon::reloadState() -{ - int i; +void Shannon::reloadState() { + int i; - for (i = 0; i < Shannon::N; ++i) - this->R[i] = this->initR[i]; + for (i = 0; i < Shannon::N; ++i) + this->R[i] = this->initR[i]; } -void Shannon::genkonst() -{ - this->konst = this->R[0]; +void Shannon::genkonst() { + this->konst = this->R[0]; } -void Shannon::diffuse() -{ - int i; +void Shannon::diffuse() { + int i; - for (i = 0; i < Shannon::FOLD; ++i) - this->cycle(); + for (i = 0; i < Shannon::FOLD; ++i) + this->cycle(); } #define Byte(x, i) ((uint32_t)(((x) >> (8 * (i))) & 0xFF)) -#define BYTE2WORD(b) ( \ - (((uint32_t)(b)[3] & 0xFF) << 24) | \ - (((uint32_t)(b)[2] & 0xFF) << 16) | \ - (((uint32_t)(b)[1] & 0xFF) << 8) | \ - (((uint32_t)(b)[0] & 0xFF))) -#define WORD2BYTE(w, b) \ - { \ - (b)[3] = Byte(w, 3); \ - (b)[2] = Byte(w, 2); \ - (b)[1] = Byte(w, 1); \ - (b)[0] = Byte(w, 0); \ - } -#define XORWORD(w, b) \ - { \ - (b)[3] ^= Byte(w, 3); \ - (b)[2] ^= Byte(w, 2); \ - (b)[1] ^= Byte(w, 1); \ - (b)[0] ^= Byte(w, 0); \ - } +#define BYTE2WORD(b) \ + ((((uint32_t)(b)[3] & 0xFF) << 24) | (((uint32_t)(b)[2] & 0xFF) << 16) | \ + (((uint32_t)(b)[1] & 0xFF) << 8) | (((uint32_t)(b)[0] & 0xFF))) +#define WORD2BYTE(w, b) \ + { \ + (b)[3] = Byte(w, 3); \ + (b)[2] = Byte(w, 2); \ + (b)[1] = Byte(w, 1); \ + (b)[0] = Byte(w, 0); \ + } +#define XORWORD(w, b) \ + { \ + (b)[3] ^= Byte(w, 3); \ + (b)[2] ^= Byte(w, 2); \ + (b)[1] ^= Byte(w, 1); \ + (b)[0] ^= Byte(w, 0); \ + } -#define XORWORD(w, b) \ - { \ - (b)[3] ^= Byte(w, 3); \ - (b)[2] ^= Byte(w, 2); \ - (b)[1] ^= Byte(w, 1); \ - (b)[0] ^= Byte(w, 0); \ - } +#define XORWORD(w, b) \ + { \ + (b)[3] ^= Byte(w, 3); \ + (b)[2] ^= Byte(w, 2); \ + (b)[1] ^= Byte(w, 1); \ + (b)[0] ^= Byte(w, 0); \ + } /* Load key material into the register */ -#define ADDKEY(k) \ - this->R[KEYP] ^= (k); +#define ADDKEY(k) this->R[KEYP] ^= (k); -void Shannon::loadKey(const std::vector& key) -{ - int i, j; - uint32_t k; - uint8_t xtra[4]; - size_t keylen = key.size(); - /* start folding in key */ - for (i = 0; i < (keylen & ~0x3); i += 4) - { - k = BYTE2WORD(&key[i]); - ADDKEY(k); - this->cycle(); - } - - /* if there were any extra key bytes, zero pad to a word */ - if (i < keylen) - { - for (j = 0 /* i unchanged */; i < keylen; ++i) - xtra[j++] = key[i]; - for (/* j unchanged */; j < 4; ++j) - xtra[j] = 0; - k = BYTE2WORD(xtra); - ADDKEY(k); - this->cycle(); - } - - /* also fold in the length of the key */ - ADDKEY(keylen); +void Shannon::loadKey(const std::vector& key) { + int i, j; + uint32_t k; + uint8_t xtra[4]; + size_t keylen = key.size(); + /* start folding in key */ + for (i = 0; i < (keylen & ~0x3); i += 4) { + k = BYTE2WORD(&key[i]); + ADDKEY(k); this->cycle(); + } - /* save a copy of the register */ - for (i = 0; i < N; ++i) - this->CRC[i] = this->R[i]; + /* if there were any extra key bytes, zero pad to a word */ + if (i < keylen) { + for (j = 0 /* i unchanged */; i < keylen; ++i) + xtra[j++] = key[i]; + for (/* j unchanged */; j < 4; ++j) + xtra[j] = 0; + k = BYTE2WORD(xtra); + ADDKEY(k); + this->cycle(); + } - /* now diffuse */ - this->diffuse(); + /* also fold in the length of the key */ + ADDKEY(keylen); + this->cycle(); - /* now xor the copy back -- makes key loading irreversible */ - for (i = 0; i < N; ++i) - this->R[i] ^= this->CRC[i]; + /* save a copy of the register */ + for (i = 0; i < N; ++i) + this->CRC[i] = this->R[i]; + + /* now diffuse */ + this->diffuse(); + + /* now xor the copy back -- makes key loading irreversible */ + for (i = 0; i < N; ++i) + this->R[i] ^= this->CRC[i]; } -void Shannon::key(const std::vector& key) -{ - this->initState(); - this->loadKey(key); - this->genkonst(); /* in case we proceed to stream generation */ - this->saveState(); - this->nbuf = 0; +void Shannon::key(const std::vector& key) { + this->initState(); + this->loadKey(key); + this->genkonst(); /* in case we proceed to stream generation */ + this->saveState(); + this->nbuf = 0; } -void Shannon::nonce(const std::vector& nonce) -{ - this->reloadState(); - this->konst = Shannon::INITKONST; - this->loadKey(nonce); - this->genkonst(); - this->nbuf = 0; +void Shannon::nonce(const std::vector& nonce) { + this->reloadState(); + this->konst = Shannon::INITKONST; + this->loadKey(nonce); + this->genkonst(); + this->nbuf = 0; } -void Shannon::stream(std::vector& bufVec) -{ - uint8_t* endbuf; - size_t nbytes = bufVec.size(); - uint8_t* buf = bufVec.data(); - /* handle any previously buffered bytes */ - while (this->nbuf != 0 && nbytes != 0) - { - *buf++ ^= this->sbuf & 0xFF; - this->sbuf >>= 8; - this->nbuf -= 8; - --nbytes; - } +void Shannon::stream(std::vector& bufVec) { + uint8_t* endbuf; + size_t nbytes = bufVec.size(); + uint8_t* buf = bufVec.data(); + /* handle any previously buffered bytes */ + while (this->nbuf != 0 && nbytes != 0) { + *buf++ ^= this->sbuf & 0xFF; + this->sbuf >>= 8; + this->nbuf -= 8; + --nbytes; + } - /* handle whole words */ - endbuf = &buf[nbytes & ~((uint32_t)0x03)]; - while (buf < endbuf) - { - this->cycle(); - XORWORD(this->sbuf, buf); - buf += 4; - } + /* handle whole words */ + endbuf = &buf[nbytes & ~((uint32_t)0x03)]; + while (buf < endbuf) { + this->cycle(); + XORWORD(this->sbuf, buf); + buf += 4; + } - /* handle any trailing bytes */ - nbytes &= 0x03; - if (nbytes != 0) - { - this->cycle(); - this->nbuf = 32; - while (this->nbuf != 0 && nbytes != 0) - { - *buf++ ^= this->sbuf & 0xFF; - this->sbuf >>= 8; - this->nbuf -= 8; - --nbytes; - } + /* handle any trailing bytes */ + nbytes &= 0x03; + if (nbytes != 0) { + this->cycle(); + this->nbuf = 32; + while (this->nbuf != 0 && nbytes != 0) { + *buf++ ^= this->sbuf & 0xFF; + this->sbuf >>= 8; + this->nbuf -= 8; + --nbytes; } + } } -void Shannon::maconly(std::vector& bufVec) -{ - size_t nbytes = bufVec.size(); - uint8_t* buf = bufVec.data(); +void Shannon::maconly(std::vector& bufVec) { + size_t nbytes = bufVec.size(); + uint8_t* buf = bufVec.data(); - uint8_t* endbuf; + uint8_t* endbuf; - /* handle any previously buffered bytes */ - if (this->nbuf != 0) - { - while (this->nbuf != 0 && nbytes != 0) - { - this->mbuf ^= (*buf++) << (32 - this->nbuf); - this->nbuf -= 8; - --nbytes; - } - if (this->nbuf != 0) /* not a whole word yet */ - return; - /* LFSR already cycled */ - this->macfunc(this->mbuf); + /* handle any previously buffered bytes */ + if (this->nbuf != 0) { + while (this->nbuf != 0 && nbytes != 0) { + this->mbuf ^= (*buf++) << (32 - this->nbuf); + this->nbuf -= 8; + --nbytes; } + if (this->nbuf != 0) /* not a whole word yet */ + return; + /* LFSR already cycled */ + this->macfunc(this->mbuf); + } - /* handle whole words */ - endbuf = &buf[nbytes & ~((uint32_t)0x03)]; - while (buf < endbuf) - { - this->cycle(); - this->macfunc(BYTE2WORD(buf)); - buf += 4; - } + /* handle whole words */ + endbuf = &buf[nbytes & ~((uint32_t)0x03)]; + while (buf < endbuf) { + this->cycle(); + this->macfunc(BYTE2WORD(buf)); + buf += 4; + } - /* handle any trailing bytes */ - nbytes &= 0x03; - if (nbytes != 0) - { - this->cycle(); - this->mbuf = 0; - this->nbuf = 32; - while (this->nbuf != 0 && nbytes != 0) - { - this->mbuf ^= (*buf++) << (32 - this->nbuf); - this->nbuf -= 8; - --nbytes; - } + /* handle any trailing bytes */ + nbytes &= 0x03; + if (nbytes != 0) { + this->cycle(); + this->mbuf = 0; + this->nbuf = 32; + while (this->nbuf != 0 && nbytes != 0) { + this->mbuf ^= (*buf++) << (32 - this->nbuf); + this->nbuf -= 8; + --nbytes; } + } } -void Shannon::encrypt(std::vector& bufVec) -{ - size_t nbytes = bufVec.size(); - uint8_t* buf = bufVec.data(); - uint8_t* endbuf; - uint32_t t = 0; +void Shannon::encrypt(std::vector& bufVec) { + size_t nbytes = bufVec.size(); + uint8_t* buf = bufVec.data(); + uint8_t* endbuf; + uint32_t t = 0; - /* handle any previously buffered bytes */ - if (this->nbuf != 0) - { - while (this->nbuf != 0 && nbytes != 0) - { - this->mbuf ^= *buf << (32 - this->nbuf); - *buf ^= (this->sbuf >> (32 - this->nbuf)) & 0xFF; - ++buf; - this->nbuf -= 8; - --nbytes; - } - if (this->nbuf != 0) /* not a whole word yet */ - return; - /* LFSR already cycled */ - this->macfunc(this->mbuf); + /* handle any previously buffered bytes */ + if (this->nbuf != 0) { + while (this->nbuf != 0 && nbytes != 0) { + this->mbuf ^= *buf << (32 - this->nbuf); + *buf ^= (this->sbuf >> (32 - this->nbuf)) & 0xFF; + ++buf; + this->nbuf -= 8; + --nbytes; } + if (this->nbuf != 0) /* not a whole word yet */ + return; + /* LFSR already cycled */ + this->macfunc(this->mbuf); + } - /* handle whole words */ - endbuf = &buf[nbytes & ~((uint32_t)0x03)]; - while (buf < endbuf) - { - this->cycle(); - t = BYTE2WORD(buf); - this->macfunc(t); - t ^= this->sbuf; - WORD2BYTE(t, buf); - buf += 4; - } + /* handle whole words */ + endbuf = &buf[nbytes & ~((uint32_t)0x03)]; + while (buf < endbuf) { + this->cycle(); + t = BYTE2WORD(buf); + this->macfunc(t); + t ^= this->sbuf; + WORD2BYTE(t, buf); + buf += 4; + } - /* handle any trailing bytes */ - nbytes &= 0x03; - if (nbytes != 0) - { - this->cycle(); - this->mbuf = 0; - this->nbuf = 32; - while (this->nbuf != 0 && nbytes != 0) - { - this->mbuf ^= *buf << (32 - this->nbuf); - *buf ^= (this->sbuf >> (32 - this->nbuf)) & 0xFF; - ++buf; - this->nbuf -= 8; - --nbytes; - } + /* handle any trailing bytes */ + nbytes &= 0x03; + if (nbytes != 0) { + this->cycle(); + this->mbuf = 0; + this->nbuf = 32; + while (this->nbuf != 0 && nbytes != 0) { + this->mbuf ^= *buf << (32 - this->nbuf); + *buf ^= (this->sbuf >> (32 - this->nbuf)) & 0xFF; + ++buf; + this->nbuf -= 8; + --nbytes; } + } } +void Shannon::decrypt(std::vector& bufVec) { + size_t nbytes = bufVec.size(); + uint8_t* buf = bufVec.data(); + uint8_t* endbuf; + uint32_t t = 0; -void Shannon::decrypt(std::vector& bufVec) -{ - size_t nbytes = bufVec.size(); - uint8_t* buf = bufVec.data(); - uint8_t* endbuf; - uint32_t t = 0; - - /* handle any previously buffered bytes */ - if (this->nbuf != 0) - { - while (this->nbuf != 0 && nbytes != 0) - { - *buf ^= (this->sbuf >> (32 - this->nbuf)) & 0xFF; - this->mbuf ^= *buf << (32 - this->nbuf); - ++buf; - this->nbuf -= 8; - --nbytes; - } - if (this->nbuf != 0) /* not a whole word yet */ - return; - /* LFSR already cycled */ - this->macfunc(this->mbuf); + /* handle any previously buffered bytes */ + if (this->nbuf != 0) { + while (this->nbuf != 0 && nbytes != 0) { + *buf ^= (this->sbuf >> (32 - this->nbuf)) & 0xFF; + this->mbuf ^= *buf << (32 - this->nbuf); + ++buf; + this->nbuf -= 8; + --nbytes; } + if (this->nbuf != 0) /* not a whole word yet */ + return; + /* LFSR already cycled */ + this->macfunc(this->mbuf); + } - /* handle whole words */ - endbuf = &buf[nbytes & ~((uint32_t)0x03)]; - while (buf < endbuf) - { - this->cycle(); - t = BYTE2WORD(buf) ^ this->sbuf; - this->macfunc(t); - WORD2BYTE(t, buf); - buf += 4; - } + /* handle whole words */ + endbuf = &buf[nbytes & ~((uint32_t)0x03)]; + while (buf < endbuf) { + this->cycle(); + t = BYTE2WORD(buf) ^ this->sbuf; + this->macfunc(t); + WORD2BYTE(t, buf); + buf += 4; + } - /* handle any trailing bytes */ - nbytes &= 0x03; - if (nbytes != 0) - { - this->cycle(); - this->mbuf = 0; - this->nbuf = 32; - while (this->nbuf != 0 && nbytes != 0) - { - *buf ^= (this->sbuf >> (32 - this->nbuf)) & 0xFF; - this->mbuf ^= *buf << (32 - this->nbuf); - ++buf; - this->nbuf -= 8; - --nbytes; - } + /* handle any trailing bytes */ + nbytes &= 0x03; + if (nbytes != 0) { + this->cycle(); + this->mbuf = 0; + this->nbuf = 32; + while (this->nbuf != 0 && nbytes != 0) { + *buf ^= (this->sbuf >> (32 - this->nbuf)) & 0xFF; + this->mbuf ^= *buf << (32 - this->nbuf); + ++buf; + this->nbuf -= 8; + --nbytes; } + } } -void Shannon::finish(std::vector& bufVec) -{ - size_t nbytes = bufVec.size(); - uint8_t* buf = bufVec.data(); - int i; +void Shannon::finish(std::vector& bufVec) { + size_t nbytes = bufVec.size(); + uint8_t* buf = bufVec.data(); + int i; - /* handle any previously buffered bytes */ - if (this->nbuf != 0) - { - /* LFSR already cycled */ - this->macfunc(this->mbuf); - } + /* handle any previously buffered bytes */ + if (this->nbuf != 0) { + /* LFSR already cycled */ + this->macfunc(this->mbuf); + } - /* perturb the MAC to mark end of input. + /* perturb the MAC to mark end of input. * Note that only the stream register is updated, not the CRC. This is an * action that can't be duplicated by passing in plaintext, hence * defeating any kind of extension attack. */ + this->cycle(); + ADDKEY(INITKONST ^ (this->nbuf << 3)); + this->nbuf = 0; + + /* now add the CRC to the stream register and diffuse it */ + for (i = 0; i < N; ++i) + this->R[i] ^= this->CRC[i]; + this->diffuse(); + + /* produce output from the stream buffer */ + while (nbytes > 0) { this->cycle(); - ADDKEY(INITKONST ^ (this->nbuf << 3)); - this->nbuf = 0; - - /* now add the CRC to the stream register and diffuse it */ - for (i = 0; i < N; ++i) - this->R[i] ^= this->CRC[i]; - this->diffuse(); - - /* produce output from the stream buffer */ - while (nbytes > 0) - { - this->cycle(); - if (nbytes >= 4) - { - WORD2BYTE(this->sbuf, buf); - nbytes -= 4; - buf += 4; - } - else - { - for (i = 0; i < nbytes; ++i) - buf[i] = Byte(this->sbuf, i); - break; - } + if (nbytes >= 4) { + WORD2BYTE(this->sbuf, buf); + nbytes -= 4; + buf += 4; + } else { + for (i = 0; i < nbytes; ++i) + buf[i] = Byte(this->sbuf, i); + break; } + } } diff --git a/components/spotify/cspot/src/ShannonConnection.cpp b/components/spotify/cspot/src/ShannonConnection.cpp index 173ebfbb..ebfe0917 100644 --- a/components/spotify/cspot/src/ShannonConnection.cpp +++ b/components/spotify/cspot/src/ShannonConnection.cpp @@ -2,16 +2,15 @@ #include // for remove_extent_t -#ifndef _WIN32 -#include -#endif - #include "BellLogger.h" // for AbstractLogger #include "Logger.h" // for CSPOT_LOG #include "Packet.h" // for Packet, cspot #include "PlainConnection.h" // for PlainConnection #include "Shannon.h" // for Shannon #include "Utils.h" // for pack, extract +#ifndef _WIN32 +#include +#endif using namespace cspot; diff --git a/components/spotify/cspot/src/SpircHandler.cpp b/components/spotify/cspot/src/SpircHandler.cpp index 814a990b..0346b25f 100644 --- a/components/spotify/cspot/src/SpircHandler.cpp +++ b/components/spotify/cspot/src/SpircHandler.cpp @@ -1,18 +1,19 @@ #include "SpircHandler.h" -#include // for uint8_t -#include // for shared_ptr, make_unique, unique_ptr -#include // for remove_extent_t -#include // for move +#include // for uint8_t +#include // for shared_ptr, make_unique, unique_ptr +#include // for remove_extent_t +#include // for move -#include "BellLogger.h" // for AbstractLogger -#include "CSpotContext.h" // for Context::ConfigState, Context (ptr only) -#include "Logger.h" // for CSPOT_LOG -#include "MercurySession.h" // for MercurySession, MercurySession::Response -#include "NanoPBHelper.h" // for pbDecode -#include "Packet.h" // for cspot -#include "PlaybackState.h" // for PlaybackState, PlaybackState::State -#include "TrackPlayer.h" // for TrackPlayer +#include "BellLogger.h" // for AbstractLogger +#include "CSpotContext.h" // for Context::ConfigState, Context (ptr only) +#include "Logger.h" // for CSPOT_LOG +#include "MercurySession.h" // for MercurySession, MercurySession::Response +#include "NanoPBHelper.h" // for pbDecode +#include "Packet.h" // for cspot +#include "PlaybackState.h" // for PlaybackState, PlaybackState::State +#include "TrackPlayer.h" // for TrackPlayer +#include "TrackQueue.h" #include "TrackReference.h" // for TrackReference #include "Utils.h" // for stringHexToBytes #include "pb_decode.h" // for pb_release @@ -20,38 +21,30 @@ using namespace cspot; -SpircHandler::SpircHandler(std::shared_ptr ctx) - : playbackState(ctx) { - - auto isAiringCallback = [this]() { - return !(isNextTrackPreloaded || isRequestedFromLoad); - }; +SpircHandler::SpircHandler(std::shared_ptr ctx) { + this->playbackState = std::make_shared(ctx); + this->trackQueue = std::make_shared(ctx, playbackState); auto EOFCallback = [this]() { - auto ref = this->playbackState.getNextTrackRef(); - - if (!isNextTrackPreloaded && !isRequestedFromLoad && ref != nullptr) { - isNextTrackPreloaded = true; - auto trackRef = TrackReference::fromTrackRef(ref); - this->trackPlayer->loadTrackFromRef(trackRef, 0, true); - } - - if (ref == nullptr) { - sendEvent(EventType::DEPLETED); - } - }; - - auto trackLoadedCallback = [this]() { - this->currentTrackInfo = this->trackPlayer->getCurrentTrackInfo(); - - if (isRequestedFromLoad) { - sendEvent(EventType::PLAYBACK_START, (int)nextTrackPosition); - setPause(false); + if (trackQueue->isFinished()) { + sendEvent(EventType::DEPLETED); } }; + auto trackLoadedCallback = [this](std::shared_ptr track) { + playbackState->setPlaybackState(PlaybackState::State::Playing); + playbackState->updatePositionMs(track->requestedPosition); + + this->notify(); + + // Send playback start event, unpause + sendEvent(EventType::PLAYBACK_START, (int) track->requestedPosition); + sendEvent(EventType::PLAY_PAUSE, false); + }; + this->ctx = ctx; - this->trackPlayer = std::make_shared(ctx, isAiringCallback, EOFCallback, trackLoadedCallback); + this->trackPlayer = std::make_shared( + ctx, trackQueue, EOFCallback, trackLoadedCallback); // Subscribe to mercury on session ready ctx->session->setConnectedHandler([this]() { this->subscribeToMercury(); }); @@ -82,105 +75,80 @@ void SpircHandler::subscribeToMercury() { subscriptionLambda); } -void SpircHandler::loadTrackFromURI(const std::string& uri) { - // {track/episode}:{gid} - bool isEpisode = uri.find("episode:") != std::string::npos; - auto gid = stringHexToBytes(uri.substr(uri.find(":") + 1)); - auto trackRef = TrackReference::fromGID(gid, isEpisode); +void SpircHandler::loadTrackFromURI(const std::string& uri) {} - isRequestedFromLoad = true; - isNextTrackPreloaded = false; +void SpircHandler::notifyAudioReachedPlayback() { + int offset = 0; - playbackState.setActive(true); + // get HEAD track + auto currentTrack = trackQueue->consumeTrack(nullptr, offset); - auto playbackRef = playbackState.getCurrentTrackRef(); + // Do not execute when meta is already updated + if (trackQueue->notifyPending) { + trackQueue->notifyPending = false; - if (playbackRef != nullptr) { - playbackState.updatePositionMs(playbackState.remoteFrame.state.position_ms); + playbackState->updatePositionMs(currentTrack->requestedPosition); - auto ref = TrackReference::fromTrackRef(playbackRef); - this->trackPlayer->loadTrackFromRef( - ref, playbackState.remoteFrame.state.position_ms, true); - playbackState.setPlaybackState(PlaybackState::State::Loading); - this->nextTrackPosition = playbackState.remoteFrame.state.position_ms; - } - - this->notify(); -} - -void SpircHandler::notifyAudioReachedPlayback() { - if (isRequestedFromLoad || isNextTrackPreloaded) { - playbackState.updatePositionMs(nextTrackPosition); - playbackState.setPlaybackState(PlaybackState::State::Playing); + // Reset position in queued track + currentTrack->requestedPosition = 0; } else { - setPause(true); - } + trackQueue->skipTrack(TrackQueue::SkipDirection::NEXT, false); + playbackState->updatePositionMs(0); - isRequestedFromLoad = false; - - if (isNextTrackPreloaded) { - isNextTrackPreloaded = false; - - playbackState.nextTrack(); - nextTrackPosition = 0; + // we moved to next track, re-acquire currentTrack again + currentTrack = trackQueue->consumeTrack(nullptr, offset); } this->notify(); - sendEvent(EventType::TRACK_INFO, this->trackPlayer->getCurrentTrackInfo()); + sendEvent(EventType::TRACK_INFO, currentTrack->trackInfo); } void SpircHandler::updatePositionMs(uint32_t position) { - playbackState.updatePositionMs(position); - notify(); + playbackState->updatePositionMs(position); + notify(); } void SpircHandler::disconnect() { - this->trackPlayer->stopTrack(); + this->trackQueue->stopTask(); + this->trackPlayer->resetState(); this->ctx->session->disconnect(); } void SpircHandler::handleFrame(std::vector& data) { - pb_release(Frame_fields, &playbackState.remoteFrame); - pbDecode(playbackState.remoteFrame, Frame_fields, data); + // Decode received spirc frame + playbackState->decodeRemoteFrame(data); - switch (playbackState.remoteFrame.typ) { + switch (playbackState->remoteFrame.typ) { case MessageType_kMessageTypeNotify: { CSPOT_LOG(debug, "Notify frame"); // Pause the playback if another player took control - if (playbackState.isActive() && - playbackState.remoteFrame.device_state.is_active) { + if (playbackState->isActive() && + playbackState->remoteFrame.device_state.is_active) { CSPOT_LOG(debug, "Another player took control, pausing playback"); - playbackState.setActive(false); - this->trackPlayer->stopTrack(); + playbackState->setActive(false); + + this->trackPlayer->stop(); sendEvent(EventType::DISC); } break; } case MessageType_kMessageTypeSeek: { - /* If next track is already downloading, we can't seek in the current one anymore. Also, - * when last track has been reached, we has to restart as we can't tell the difference */ - if ((!isNextTrackPreloaded && this->playbackState.getNextTrackRef()) || isRequestedFromLoad) { - CSPOT_LOG(debug, "Seek command while streaming current"); - playbackState.updatePositionMs(playbackState.remoteFrame.position); - trackPlayer->seekMs(playbackState.remoteFrame.position); - sendEvent(EventType::SEEK, (int)playbackState.remoteFrame.position); - } else { - CSPOT_LOG(debug, "Seek command while streaming next or before started"); - isRequestedFromLoad = true; - isNextTrackPreloaded = false; - auto ref = TrackReference::fromTrackRef(playbackState.getCurrentTrackRef()); - this->trackPlayer->loadTrackFromRef(ref, playbackState.remoteFrame.position, true); - this->nextTrackPosition = playbackState.remoteFrame.position; - } + this->trackPlayer->seekMs(playbackState->remoteFrame.position); + + playbackState->updatePositionMs(playbackState->remoteFrame.position); + notify(); + + sendEvent(EventType::SEEK, (int)playbackState->remoteFrame.position); + //sendEvent(EventType::FLUSH); break; } case MessageType_kMessageTypeVolume: - playbackState.setVolume(playbackState.remoteFrame.volume); + playbackState->setVolume(playbackState->remoteFrame.volume); this->notify(); - sendEvent(EventType::VOLUME, (int)playbackState.remoteFrame.volume); + sendEvent(EventType::VOLUME, (int)playbackState->remoteFrame.volume); break; case MessageType_kMessageTypePause: setPause(true); @@ -197,44 +165,51 @@ void SpircHandler::handleFrame(std::vector& data) { sendEvent(EventType::PREV); break; case MessageType_kMessageTypeLoad: { - CSPOT_LOG(debug, "Load frame!"); - isRequestedFromLoad = true; - isNextTrackPreloaded = false; + this->trackPlayer->start(); - playbackState.setActive(true); - playbackState.updateTracks(); + CSPOT_LOG(debug, "Load frame %d!", playbackState->remoteTracks.size()); - auto playbackRef = playbackState.getCurrentTrackRef(); - - if (playbackRef != nullptr) { - playbackState.updatePositionMs( - playbackState.remoteFrame.state.position_ms); - - auto ref = TrackReference::fromTrackRef(playbackRef); - this->trackPlayer->loadTrackFromRef( - ref, playbackState.remoteFrame.state.position_ms, true); - playbackState.setPlaybackState(PlaybackState::State::Loading); - this->nextTrackPosition = playbackState.remoteFrame.state.position_ms; + if (playbackState->remoteTracks.size() == 0) { + CSPOT_LOG(info, "No tracks in frame, stopping playback"); + break; } + playbackState->setActive(true); + + playbackState->updatePositionMs(playbackState->remoteFrame.position); + playbackState->setPlaybackState(PlaybackState::State::Playing); + + playbackState->syncWithRemote(); + + // Update track list in case we have a new one + trackQueue->updateTracks(playbackState->remoteFrame.state.position_ms, + true); + this->notify(); + + // Stop the current track, if any + trackPlayer->resetState(); break; } case MessageType_kMessageTypeReplace: { CSPOT_LOG(debug, "Got replace frame"); - playbackState.updateTracks(); + playbackState->syncWithRemote(); + + trackQueue->updateTracks(playbackState->remoteFrame.state.position_ms, + false); this->notify(); + + trackPlayer->resetState(); + sendEvent(EventType::FLUSH); break; } case MessageType_kMessageTypeShuffle: { CSPOT_LOG(debug, "Got shuffle frame"); - playbackState.setShuffle(playbackState.remoteFrame.state.shuffle); this->notify(); break; } case MessageType_kMessageTypeRepeat: { CSPOT_LOG(debug, "Got repeat frame"); - playbackState.setRepeat(playbackState.remoteFrame.state.repeat); this->notify(); break; } @@ -244,7 +219,7 @@ void SpircHandler::handleFrame(std::vector& data) { } void SpircHandler::setRemoteVolume(int volume) { - playbackState.setVolume(volume); + playbackState->setVolume(volume); notify(); } @@ -252,32 +227,34 @@ void SpircHandler::notify() { this->sendCmd(MessageType_kMessageTypeNotify); } -void SpircHandler::nextSong() { - if (playbackState.nextTrack()) { - isRequestedFromLoad = true; - isNextTrackPreloaded = false; - auto ref = TrackReference::fromTrackRef(playbackState.getCurrentTrackRef()); - this->trackPlayer->loadTrackFromRef(ref, 0, true); +void SpircHandler::skipSong(TrackQueue::SkipDirection dir) { + if (trackQueue->skipTrack(dir)) { + playbackState->setPlaybackState(PlaybackState::State::Playing); + notify(); + + // Reset track state + trackPlayer->resetState(); + + sendEvent(EventType::PLAY_PAUSE, false); } else { - sendEvent(EventType::FLUSH); - playbackState.updatePositionMs(0); - trackPlayer->stopTrack(); + playbackState->setPlaybackState(PlaybackState::State::Paused); + playbackState->updatePositionMs(0); + notify(); + + sendEvent(EventType::PLAY_PAUSE, true); } - this->nextTrackPosition = 0; + notify(); + + sendEvent(EventType::FLUSH); +} + +void SpircHandler::nextSong() { + skipSong(TrackQueue::SkipDirection::NEXT); } void SpircHandler::previousSong() { - playbackState.prevTrack(); - isRequestedFromLoad = true; - isNextTrackPreloaded = false; - - sendEvent(EventType::PREV); - auto ref = TrackReference::fromTrackRef(playbackState.getCurrentTrackRef()); - this->trackPlayer->loadTrackFromRef(ref, 0, true); - this->nextTrackPosition = 0; - - notify(); + skipSong(TrackQueue::SkipDirection::PREV); } std::shared_ptr SpircHandler::getTrackPlayer() { @@ -286,7 +263,7 @@ std::shared_ptr SpircHandler::getTrackPlayer() { void SpircHandler::sendCmd(MessageType typ) { // Serialize current player state - auto encodedFrame = playbackState.encodeCurrentFrame(typ); + auto encodedFrame = playbackState->encodeCurrentFrame(typ); auto responseLambda = [=](MercurySession::Response& res) { }; @@ -302,11 +279,11 @@ void SpircHandler::setEventHandler(EventHandler handler) { void SpircHandler::setPause(bool isPaused) { if (isPaused) { CSPOT_LOG(debug, "External pause command"); - playbackState.setPlaybackState(PlaybackState::State::Paused); + playbackState->setPlaybackState(PlaybackState::State::Paused); } else { CSPOT_LOG(debug, "External play command"); - playbackState.setPlaybackState(PlaybackState::State::Playing); + playbackState->setPlaybackState(PlaybackState::State::Playing); } notify(); sendEvent(EventType::PLAY_PAUSE, isPaused); diff --git a/components/spotify/cspot/src/TimeProvider.cpp b/components/spotify/cspot/src/TimeProvider.cpp index 5e952449..8617ef13 100644 --- a/components/spotify/cspot/src/TimeProvider.cpp +++ b/components/spotify/cspot/src/TimeProvider.cpp @@ -1,25 +1,24 @@ #include "TimeProvider.h" -#ifndef _WIN32 -#include -#endif - #include "BellLogger.h" // for AbstractLogger #include "Logger.h" // for CSPOT_LOG #include "Utils.h" // for extract, getCurrentTimestamp +#ifndef _WIN32 +#include +#endif using namespace cspot; -TimeProvider::TimeProvider() { -} +TimeProvider::TimeProvider() {} void TimeProvider::syncWithPingPacket(const std::vector& pongPacket) { - CSPOT_LOG(debug, "Time synced with spotify servers"); - // Spotify's timestamp is in seconds since unix time - convert to millis. - uint64_t remoteTimestamp = ((uint64_t) ntohl(extract(pongPacket, 0))) * 1000; - this->timestampDiff = remoteTimestamp - getCurrentTimestamp(); + CSPOT_LOG(debug, "Time synced with spotify servers"); + // Spotify's timestamp is in seconds since unix time - convert to millis. + uint64_t remoteTimestamp = + ((uint64_t)ntohl(extract(pongPacket, 0))) * 1000; + this->timestampDiff = remoteTimestamp - getCurrentTimestamp(); } unsigned long long TimeProvider::getSyncedTimestamp() { - return getCurrentTimestamp() + this->timestampDiff; + return getCurrentTimestamp() + this->timestampDiff; } \ No newline at end of file diff --git a/components/spotify/cspot/src/TrackPlayer.cpp b/components/spotify/cspot/src/TrackPlayer.cpp index 99de08d7..03155831 100644 --- a/components/spotify/cspot/src/TrackPlayer.cpp +++ b/components/spotify/cspot/src/TrackPlayer.cpp @@ -1,18 +1,26 @@ #include "TrackPlayer.h" -#include // for mutex, scoped_lock -#include // for string -#include // for remove_extent_t -#include // for vector, vector<>::value_type +#include // for mutex, scoped_lock +#include // for string +#include // for remove_extent_t +#include // for vector, vector<>::value_type #include "BellLogger.h" // for AbstractLogger #include "BellUtils.h" // for BELL_SLEEP_MS -#include "CDNTrackStream.h" // for CDNTrackStream, CDNTrackStream::TrackInfo #include "Logger.h" // for CSPOT_LOG #include "Packet.h" // for cspot -#include "TrackProvider.h" // for TrackProvider +#include "TrackQueue.h" // for CDNTrackStream, CDNTrackStream::TrackInfo #include "WrappedSemaphore.h" // for WrappedSemaphore +#ifdef BELL_VORBIS_FLOAT +#define VORBIS_SEEK(file, position) (ov_time_seek(file, (double)position / 1000)) +#define VORBIS_READ(file, buffer, bufferSize, section) (ov_read(file, buffer, bufferSize, 0, 2, 1, section)) +#else +#define VORBIS_SEEK(file, position) (ov_time_seek(file, position)) +#define VORBIS_READ(file, buffer, bufferSize, section) \ + (ov_read(file, buffer, bufferSize, section)) +#endif + namespace cspot { struct Context; struct TrackReference; @@ -38,13 +46,14 @@ static long vorbisTellCb(TrackPlayer* self) { return self->_vorbisTell(); } -TrackPlayer::TrackPlayer(std::shared_ptr ctx, isAiringCallback isAiring, EOFCallback eof, TrackLoadedCallback trackLoaded) - : bell::Task("cspot_player", 48 * 1024, 5, 1) { +TrackPlayer::TrackPlayer(std::shared_ptr ctx, + std::shared_ptr trackQueue, + EOFCallback eof, TrackLoadedCallback trackLoaded) + : bell::Task("cspot_player", 32 * 1024, 5, 1) { this->ctx = ctx; - this->isAiring = isAiring; this->eofCallback = eof; this->trackLoaded = trackLoaded; - this->trackProvider = std::make_shared(ctx); + this->trackQueue = trackQueue; this->playbackSemaphore = std::make_unique(5); // Initialize vorbis callbacks @@ -55,9 +64,6 @@ TrackPlayer::TrackPlayer(std::shared_ptr ctx, isAiringCallback i (decltype(ov_callbacks::close_func))&vorbisCloseCb, (decltype(ov_callbacks::tell_func))&vorbisTellCb, }; - isRunning = true; - - startTask(); } TrackPlayer::~TrackPlayer() { @@ -65,123 +71,192 @@ TrackPlayer::~TrackPlayer() { std::scoped_lock lock(runningMutex); } -void TrackPlayer::loadTrackFromRef(TrackReference& ref, size_t positionMs, - bool startAutomatically) { - this->playbackPosition = positionMs; - this->autoStart = startAutomatically; - - auto nextTrack = trackProvider->loadFromTrackRef(ref); - - stopTrack(); - this->sequence++; - this->currentTrackStream = nextTrack; - this->playbackSemaphore->give(); +void TrackPlayer::start() { + if (!isRunning) { + isRunning = true; + startTask(); + } } -void TrackPlayer::stopTrack() { +void TrackPlayer::stop() { + isRunning = false; + resetState(); + std::scoped_lock lock(runningMutex); +} + +void TrackPlayer::resetState() { + // Mark for reset + this->pendingReset = true; this->currentSongPlaying = false; - std::scoped_lock lock(playbackMutex); + + std::scoped_lock lock(dataOutMutex); + + CSPOT_LOG(info, "Resetting state"); } void TrackPlayer::seekMs(size_t ms) { - std::scoped_lock lock(seekMutex); -#ifdef BELL_VORBIS_FLOAT - ov_time_seek(&vorbisFile, (double)ms / 1000); -#else - ov_time_seek(&vorbisFile, ms); -#endif + if (inFuture) { + // We're in the middle of the next track, so we need to reset the player in order to seek + resetState(); + } + + CSPOT_LOG(info, "Seeking..."); + this->pendingSeekPositionMs = ms; } void TrackPlayer::runTask() { std::scoped_lock lock(runningMutex); + std::shared_ptr track, newTrack = nullptr; + + int trackOffset = 0; + bool eof = false; + bool endOfQueueReached = false; + while (isRunning) { - this->playbackSemaphore->twait(100); - - if (this->currentTrackStream == nullptr) { + // Ensure we even have any tracks to play + if (!this->trackQueue->hasTracks() || + (endOfQueueReached && trackQueue->isFinished())) { + this->trackQueue->playableSemaphore->twait(300); continue; } - CSPOT_LOG(info, "Player received a track, waiting for it to be ready..."); - - // when track changed many times and very quickly, we are stuck on never-given semaphore - while (this->currentTrackStream->trackReady->twait(250)); - CSPOT_LOG(info, "Got track"); + // Last track was interrupted, reset to default + if (pendingReset) { + track = nullptr; + pendingReset = false; + inFuture = false; + } - if (this->currentTrackStream->status == CDNTrackStream::Status::FAILED) { - CSPOT_LOG(error, "Track failed to load, skipping it"); - this->currentTrackStream = nullptr; - this->eofCallback(); + endOfQueueReached = false; + + // Wait 800ms. If next reset is requested in meantime, restart the queue. + // Gets rid of excess actions during rapid queueing + BELL_SLEEP_MS(50); + + if (pendingReset) { continue; } - this->currentSongPlaying = true; + newTrack = trackQueue->consumeTrack(track, trackOffset); - this->trackLoaded(); + if (newTrack == nullptr) { + if (trackOffset == -1) { + // Reset required + track = nullptr; + } - this->playbackMutex.lock(); - - int32_t r = ov_open_callbacks(this, &vorbisFile, NULL, 0, vorbisCallbacks); - - if (playbackPosition > 0) { -#ifdef BELL_VORBIS_FLOAT - ov_time_seek(&vorbisFile, (double)playbackPosition / 1000); -#else - ov_time_seek(&vorbisFile, playbackPosition); -#endif + BELL_SLEEP_MS(100); + continue; } - bool eof = false; + track = newTrack; - while (!eof && currentSongPlaying) { - seekMutex.lock(); -#ifdef BELL_VORBIS_FLOAT - long ret = ov_read(&vorbisFile, (char*)&pcmBuffer[0], pcmBuffer.size(), - 0, 2, 1, ¤tSection); -#else - long ret = ov_read(&vorbisFile, (char*)&pcmBuffer[0], pcmBuffer.size(), - ¤tSection); -#endif - seekMutex.unlock(); - if (ret == 0) { - CSPOT_LOG(info, "EOF"); - // and done :) - eof = true; - } else if (ret < 0) { - CSPOT_LOG(error, "An error has occured in the stream %d", ret); - currentSongPlaying = false; - } else { + inFuture = trackOffset > 0; - if (this->dataCallback != nullptr) { - auto toWrite = ret; + if (track->state != QueuedTrack::State::READY) { + track->loadedSemaphore->twait(5000); - while (!eof && currentSongPlaying && toWrite > 0) { - auto written = - dataCallback(pcmBuffer.data() + (ret - toWrite), toWrite, - this->currentTrackStream->trackInfo.trackId, this->sequence); - if (written == 0) { - BELL_SLEEP_MS(50); + if (track->state != QueuedTrack::State::READY) { + CSPOT_LOG(error, "Track failed to load, skipping it"); + this->eofCallback(); + continue; + } + } + + CSPOT_LOG(info, "Got track ID=%s", track->identifier.c_str()); + + currentSongPlaying = true; + + { + std::scoped_lock lock(playbackMutex); + + currentTrackStream = track->getAudioFile(); + + // Open the stream + currentTrackStream->openStream(); + + if (pendingReset || !currentSongPlaying) { + continue; + } + + if (trackOffset == 0 && pendingSeekPositionMs == 0) { + this->trackLoaded(track); + } + + int32_t r = + ov_open_callbacks(this, &vorbisFile, NULL, 0, vorbisCallbacks); + + if (pendingSeekPositionMs > 0) { + track->requestedPosition = pendingSeekPositionMs; + } + + if (track->requestedPosition > 0) { + VORBIS_SEEK(&vorbisFile, track->requestedPosition); + } + + eof = false; + + CSPOT_LOG(info, "Playing"); + + while (!eof && currentSongPlaying) { + // Execute seek if needed + if (pendingSeekPositionMs > 0) { + uint32_t seekPosition = pendingSeekPositionMs; + + // Reset the pending seek position + pendingSeekPositionMs = 0; + + // Seek to the new position + VORBIS_SEEK(&vorbisFile, seekPosition); + } + + long ret = VORBIS_READ(&vorbisFile, (char*)&pcmBuffer[0], + pcmBuffer.size(), ¤tSection); + + if (ret == 0) { + CSPOT_LOG(info, "EOF"); + // and done :) + eof = true; + } else if (ret < 0) { + CSPOT_LOG(error, "An error has occured in the stream %d", ret); + currentSongPlaying = false; + } else { + if (this->dataCallback != nullptr) { + auto toWrite = ret; + + while (!eof && currentSongPlaying && !pendingReset && toWrite > 0) { + int written = 0; + { + std::scoped_lock dataOutLock(dataOutMutex); + // If reset happened during playback, return + if (!currentSongPlaying || pendingReset) + break; + + written = + dataCallback(pcmBuffer.data() + (ret - toWrite), toWrite, track->identifier); + } + if (written == 0) { + BELL_SLEEP_MS(50); + } + toWrite -= written; } - toWrite -= written; } } } - } - ov_clear(&vorbisFile); + ov_clear(&vorbisFile); - // With very large buffers, track N+1 can be downloaded while N has not aired yet and - // if we continue, the currentTrackStream will be emptied, causing a crash in - // notifyAudioReachedPlayback when it will look for trackInfo. A busy loop is never - // ideal, but this low impact, infrequent and more simple than yet another semaphore - while (currentSongPlaying && !isAiring()) { - BELL_SLEEP_MS(100); - } + CSPOT_LOG(info, "Playing done"); - // always move back to LOADING (ensure proper seeking after last track has been loaded) - this->currentTrackStream.reset(); - this->playbackMutex.unlock(); + // always move back to LOADING (ensure proper seeking after last track has been loaded) + currentTrackStream = nullptr; + } if (eof) { + if (trackQueue->isFinished()) { + endOfQueueReached = true; + } + this->eofCallback(); } } @@ -226,10 +301,6 @@ long TrackPlayer::_vorbisTell() { return this->currentTrackStream->getPosition(); } -CDNTrackStream::TrackInfo TrackPlayer::getCurrentTrackInfo() { - return this->currentTrackStream->trackInfo; -} - void TrackPlayer::setDataCallback(DataCallback callback) { this->dataCallback = callback; } diff --git a/components/spotify/cspot/src/TrackProvider.cpp b/components/spotify/cspot/src/TrackProvider.cpp deleted file mode 100644 index 14b6ffd7..00000000 --- a/components/spotify/cspot/src/TrackProvider.cpp +++ /dev/null @@ -1,245 +0,0 @@ -#include "TrackProvider.h" - -#include // for assert -#include // for strlen -#include // for uint8_t -#include // for __base -#include // for shared_ptr, weak_ptr, make_shared -#include // for string, operator+ -#include // for remove_extent_t - -#include "AccessKeyFetcher.h" // for AccessKeyFetcher -#include "BellLogger.h" // for AbstractLogger -#include "CDNTrackStream.h" // for CDNTrackStream, CDNTrackStream::Tr... -#include "CSpotContext.h" // for Context::ConfigState, Context (ptr... -#include "Logger.h" // for CSPOT_LOG -#include "MercurySession.h" // for MercurySession, MercurySession::Da... -#include "NanoPBHelper.h" // for pbArrayToVector, pbDecode -#include "Packet.h" // for cspot -#include "TrackReference.h" // for TrackReference, TrackReference::Type -#include "Utils.h" // for bytesToHexString, string_format -#include "WrappedSemaphore.h" // for WrappedSemaphore -#include "pb_decode.h" // for pb_release -#include "protobuf/metadata.pb.h" // for Track, _Track, AudioFile, Episode - -using namespace cspot; - -TrackProvider::TrackProvider(std::shared_ptr ctx) { - this->accessKeyFetcher = std::make_shared(ctx); - this->ctx = ctx; - this->cdnStream = - std::make_unique(this->accessKeyFetcher); - - this->trackInfo = {}; -} - -TrackProvider::~TrackProvider() { - pb_release(Track_fields, &trackInfo); - pb_release(Episode_fields, &trackInfo); -} - -std::shared_ptr TrackProvider::loadFromTrackRef( - TrackReference& trackRef) { - auto track = std::make_shared(this->accessKeyFetcher); - this->currentTrackReference = track; - this->trackIdInfo = trackRef; - - queryMetadata(); - return track; -} - -void TrackProvider::queryMetadata() { - std::string requestUrl = string_format( - "hm://metadata/3/%s/%s", - trackIdInfo.type == TrackReference::Type::TRACK ? "track" : "episode", - bytesToHexString(trackIdInfo.gid).c_str()); - CSPOT_LOG(debug, "Requesting track metadata from %s", requestUrl.c_str()); - - auto responseHandler = [this](MercurySession::Response& res) { - this->onMetadataResponse(res); - }; - - // Execute the request - ctx->session->execute(MercurySession::RequestType::GET, requestUrl, - responseHandler); -} - -void TrackProvider::onMetadataResponse(MercurySession::Response& res) { - CSPOT_LOG(debug, "Got track metadata response"); - - int alternativeCount, filesCount = 0; - bool canPlay = false; - AudioFile* selectedFiles; - std::vector trackId, fileId; - - if (trackIdInfo.type == TrackReference::Type::TRACK) { - pb_release(Track_fields, &trackInfo); - assert(res.parts.size() > 0); - pbDecode(trackInfo, Track_fields, res.parts[0]); - CSPOT_LOG(info, "Track name: %s", trackInfo.name); - CSPOT_LOG(info, "Track duration: %d", trackInfo.duration); - - CSPOT_LOG(debug, "trackInfo.restriction.size() = %d", - trackInfo.restriction_count); - - if (doRestrictionsApply(trackInfo.restriction, - trackInfo.restriction_count)) { - // Go through alternatives - for (int x = 0; x < trackInfo.alternative_count; x++) { - if (!doRestrictionsApply(trackInfo.alternative[x].restriction, - trackInfo.alternative[x].restriction_count)) { - selectedFiles = trackInfo.alternative[x].file; - filesCount = trackInfo.alternative[x].file_count; - trackId = pbArrayToVector(trackInfo.alternative[x].gid); - break; - } - } - } else { - selectedFiles = trackInfo.file; - filesCount = trackInfo.file_count; - trackId = pbArrayToVector(trackInfo.gid); - } - - // Set track's metadata - auto trackRef = this->currentTrackReference.lock(); - - auto imageId = - pbArrayToVector(trackInfo.album.cover_group.image[0].file_id); - - trackRef->trackInfo.trackId = bytesToHexString(trackIdInfo.gid); - trackRef->trackInfo.name = std::string(trackInfo.name); - trackRef->trackInfo.album = std::string(trackInfo.album.name); - trackRef->trackInfo.artist = std::string(trackInfo.artist[0].name); - trackRef->trackInfo.imageUrl = - "https://i.scdn.co/image/" + bytesToHexString(imageId); - trackRef->trackInfo.duration = trackInfo.duration; - } else { - pb_release(Episode_fields, &episodeInfo); - assert(res.parts.size() > 0); - pbDecode(episodeInfo, Episode_fields, res.parts[0]); - - CSPOT_LOG(info, "Episode name: %s", episodeInfo.name); - CSPOT_LOG(info, "Episode duration: %d", episodeInfo.duration); - - CSPOT_LOG(debug, "episodeInfo.restriction.size() = %d", - episodeInfo.restriction_count); - if (!doRestrictionsApply(episodeInfo.restriction, - episodeInfo.restriction_count)) { - selectedFiles = episodeInfo.file; - filesCount = episodeInfo.file_count; - trackId = pbArrayToVector(episodeInfo.gid); - } - - auto trackRef = this->currentTrackReference.lock(); - - auto imageId = pbArrayToVector(episodeInfo.covers->image[0].file_id); - - trackRef->trackInfo.trackId = bytesToHexString(trackIdInfo.gid); - trackRef->trackInfo.name = std::string(episodeInfo.name); - trackRef->trackInfo.album = ""; - trackRef->trackInfo.artist = "", - trackRef->trackInfo.imageUrl = - "https://i.scdn.co/image/" + bytesToHexString(imageId); - trackRef->trackInfo.duration = episodeInfo.duration; - } - - for (int x = 0; x < filesCount; x++) { - CSPOT_LOG(debug, "File format: %d", selectedFiles[x].format); - if (selectedFiles[x].format == ctx->config.audioFormat) { - fileId = pbArrayToVector(selectedFiles[x].file_id); - break; // If file found stop searching - } - - // Fallback to OGG Vorbis 96kbps - if (fileId.size() == 0 && - selectedFiles[x].format == AudioFormat_OGG_VORBIS_96) { - fileId = pbArrayToVector(selectedFiles[x].file_id); - } - } - - // No viable files found for playback - if (fileId.size() == 0) { - CSPOT_LOG(info, "File not available for playback"); - // no alternatives for song - if (!this->currentTrackReference.expired()) { - auto trackRef = this->currentTrackReference.lock(); - trackRef->status = CDNTrackStream::Status::FAILED; - trackRef->trackReady->give(); - } - return; - } - - this->fetchFile(fileId, trackId); -} - -void TrackProvider::fetchFile(const std::vector& fileId, - const std::vector& trackId) { - ctx->session->requestAudioKey( - trackId, fileId, - [this, fileId](bool success, const std::vector& audioKey) { - if (success) { - CSPOT_LOG(info, "Got audio key"); - if (!this->currentTrackReference.expired()) { - auto ref = this->currentTrackReference.lock(); - ref->fetchFile(fileId, audioKey); - } - - } else { - CSPOT_LOG(error, "Failed to get audio key"); - if (!this->currentTrackReference.expired()) { - auto ref = this->currentTrackReference.lock(); - ref->fail(); - } - } - }); -} - -bool countryListContains(char* countryList, char* country) { - uint16_t countryList_length = strlen(countryList); - for (int x = 0; x < countryList_length; x += 2) { - if (countryList[x] == country[0] && countryList[x + 1] == country[1]) { - return true; - } - } - return false; -} - -bool TrackProvider::doRestrictionsApply(Restriction* restrictions, int count) { - for (int x = 0; x < count; x++) { - if (restrictions[x].countries_allowed != nullptr) { - return !countryListContains(restrictions[x].countries_allowed, - (char*)ctx->config.countryCode.c_str()); - } - - if (restrictions[x].countries_forbidden != nullptr) { - return countryListContains(restrictions[x].countries_forbidden, - (char*)ctx->config.countryCode.c_str()); - } - } - - return false; -} - -bool TrackProvider::canPlayTrack(int altIndex) { - if (altIndex < 0) { - - } else { - for (int x = 0; x < trackInfo.alternative[altIndex].restriction_count; - x++) { - if (trackInfo.alternative[altIndex].restriction[x].countries_allowed != - nullptr) { - return countryListContains( - trackInfo.alternative[altIndex].restriction[x].countries_allowed, - (char*)ctx->config.countryCode.c_str()); - } - - if (trackInfo.alternative[altIndex].restriction[x].countries_forbidden != - nullptr) { - return !countryListContains( - trackInfo.alternative[altIndex].restriction[x].countries_forbidden, - (char*)ctx->config.countryCode.c_str()); - } - } - } - return true; -} diff --git a/components/spotify/cspot/src/TrackQueue.cpp b/components/spotify/cspot/src/TrackQueue.cpp new file mode 100644 index 00000000..f2e440d6 --- /dev/null +++ b/components/spotify/cspot/src/TrackQueue.cpp @@ -0,0 +1,603 @@ +#include "TrackQueue.h" +#include + +#include +#include +#include +#include + +#include "AccessKeyFetcher.h" +#include "BellTask.h" +#include "CDNAudioFile.h" +#include "CSpotContext.h" +#include "HTTPClient.h" +#include "Logger.h" +#include "Utils.h" +#include "WrappedSemaphore.h" +#ifdef BELL_ONLY_CJSON +#include "cJSON.h" +#else +#include "nlohmann/json.hpp" // for basic_json<>::object_t, basic_json +#include "nlohmann/json_fwd.hpp" // for json +#endif +#include "protobuf/metadata.pb.h" + +using namespace cspot; +namespace TrackDataUtils { +bool countryListContains(char* countryList, const char* country) { + uint16_t countryList_length = strlen(countryList); + for (int x = 0; x < countryList_length; x += 2) { + if (countryList[x] == country[0] && countryList[x + 1] == country[1]) { + return true; + } + } + return false; +} + +bool doRestrictionsApply(Restriction* restrictions, int count, + const char* country) { + for (int x = 0; x < count; x++) { + if (restrictions[x].countries_allowed != nullptr) { + return !countryListContains(restrictions[x].countries_allowed, country); + } + + if (restrictions[x].countries_forbidden != nullptr) { + return countryListContains(restrictions[x].countries_forbidden, country); + } + } + + return false; +} + +bool canPlayTrack(Track& trackInfo, int altIndex, const char* country) { + if (altIndex < 0) { + + } else { + for (int x = 0; x < trackInfo.alternative[altIndex].restriction_count; + x++) { + if (trackInfo.alternative[altIndex].restriction[x].countries_allowed != + nullptr) { + return countryListContains( + trackInfo.alternative[altIndex].restriction[x].countries_allowed, + country); + } + + if (trackInfo.alternative[altIndex].restriction[x].countries_forbidden != + nullptr) { + return !countryListContains( + trackInfo.alternative[altIndex].restriction[x].countries_forbidden, + country); + } + } + } + return true; +} +} // namespace TrackDataUtils + +void TrackInfo::loadPbTrack(Track* pbTrack, const std::vector& gid) { + // Generate ID based on GID + trackId = bytesToHexString(gid); + + name = std::string(pbTrack->name); + + if (pbTrack->artist_count > 0) { + // Handle artist data + artist = std::string(pbTrack->artist[0].name); + } + + if (pbTrack->has_album) { + // Handle album data + album = std::string(pbTrack->album.name); + + if (pbTrack->album.has_cover_group && + pbTrack->album.cover_group.image_count > 0) { + auto imageId = + pbArrayToVector(pbTrack->album.cover_group.image[0].file_id); + imageUrl = "https://i.scdn.co/image/" + bytesToHexString(imageId); + } + } + + duration = pbTrack->duration; +} + +void TrackInfo::loadPbEpisode(Episode* pbEpisode, + const std::vector& gid) { + // Generate ID based on GID + trackId = bytesToHexString(gid); + + name = std::string(pbEpisode->name); + + if (pbEpisode->covers->image_count > 0) { + // Handle episode info + auto imageId = pbArrayToVector(pbEpisode->covers->image[0].file_id); + imageUrl = "https://i.scdn.co/image/" + bytesToHexString(imageId); + } + + duration = pbEpisode->duration; +} + +QueuedTrack::QueuedTrack(TrackReference& ref, + std::shared_ptr ctx, + uint32_t requestedPosition) + : requestedPosition(requestedPosition), ctx(ctx) { + this->ref = ref; + + loadedSemaphore = std::make_shared(); + state = State::QUEUED; +} + +QueuedTrack::~QueuedTrack() { + state = State::FAILED; + loadedSemaphore->give(); + + if (pendingMercuryRequest != 0) { + ctx->session->unregister(pendingMercuryRequest); + } + + if (pendingAudioKeyRequest != 0) { + ctx->session->unregisterAudioKey(pendingAudioKeyRequest); + } +} + +std::shared_ptr QueuedTrack::getAudioFile() { + if (state != State::READY) { + return nullptr; + } + + return std::make_shared(cdnUrl, audioKey); +} + +void QueuedTrack::stepParseMetadata(Track* pbTrack, Episode* pbEpisode) { + int alternativeCount, filesCount = 0; + bool canPlay = false; + AudioFile* selectedFiles = nullptr; + + const char* countryCode = ctx->config.countryCode.c_str(); + + if (ref.type == TrackReference::Type::TRACK) { + CSPOT_LOG(info, "Track name: %s", pbTrack->name); + CSPOT_LOG(info, "Track duration: %d", pbTrack->duration); + + CSPOT_LOG(debug, "trackInfo.restriction.size() = %d", + pbTrack->restriction_count); + + // Check if we can play the track, if not, try alternatives + if (TrackDataUtils::doRestrictionsApply( + pbTrack->restriction, pbTrack->restriction_count, countryCode)) { + // Go through alternatives + for (int x = 0; x < pbTrack->alternative_count; x++) { + if (!TrackDataUtils::doRestrictionsApply( + pbTrack->alternative[x].restriction, + pbTrack->alternative[x].restriction_count, countryCode)) { + selectedFiles = pbTrack->alternative[x].file; + filesCount = pbTrack->alternative[x].file_count; + trackId = pbArrayToVector(pbTrack->alternative[x].gid); + break; + } + } + } else { + // We can play the track + selectedFiles = pbTrack->file; + filesCount = pbTrack->file_count; + trackId = pbArrayToVector(pbTrack->gid); + } + + if (trackId.size() > 0) { + // Load track information + trackInfo.loadPbTrack(pbTrack, trackId); + } + } else { + // Handle episodes + CSPOT_LOG(info, "Episode name: %s", pbEpisode->name); + CSPOT_LOG(info, "Episode duration: %d", pbEpisode->duration); + + CSPOT_LOG(debug, "episodeInfo.restriction.size() = %d", + pbEpisode->restriction_count); + + // Check if we can play the episode + if (!TrackDataUtils::doRestrictionsApply(pbEpisode->restriction, + pbEpisode->restriction_count, + countryCode)) { + selectedFiles = pbEpisode->file; + filesCount = pbEpisode->file_count; + trackId = pbArrayToVector(pbEpisode->gid); + + // Load track information + trackInfo.loadPbEpisode(pbEpisode, trackId); + } + } + + // Find playable file + for (int x = 0; x < filesCount; x++) { + CSPOT_LOG(debug, "File format: %d", selectedFiles[x].format); + if (selectedFiles[x].format == ctx->config.audioFormat) { + fileId = pbArrayToVector(selectedFiles[x].file_id); + break; // If file found stop searching + } + + // Fallback to OGG Vorbis 96kbps + if (fileId.size() == 0 && + selectedFiles[x].format == AudioFormat_OGG_VORBIS_96) { + fileId = pbArrayToVector(selectedFiles[x].file_id); + } + } + + // No viable files found for playback + if (fileId.size() == 0) { + CSPOT_LOG(info, "File not available for playback"); + + // no alternatives for song + state = State::FAILED; + loadedSemaphore->give(); + return; + } + + // Assign track identifier + identifier = bytesToHexString(fileId); + + state = State::KEY_REQUIRED; +} + +void QueuedTrack::stepLoadAudioFile( + std::mutex& trackListMutex, + std::shared_ptr updateSemaphore) { + // Request audio key + this->pendingAudioKeyRequest = ctx->session->requestAudioKey( + trackId, fileId, + [this, &trackListMutex, updateSemaphore]( + bool success, const std::vector& audioKey) { + std::scoped_lock lock(trackListMutex); + + if (success) { + CSPOT_LOG(info, "Got audio key"); + this->audioKey = + std::vector(audioKey.begin() + 4, audioKey.end()); + + state = State::CDN_REQUIRED; + } else { + CSPOT_LOG(error, "Failed to get audio key"); + state = State::FAILED; + loadedSemaphore->give(); + } + updateSemaphore->give(); + }); + + state = State::PENDING_KEY; +} + +void QueuedTrack::stepLoadCDNUrl(const std::string& accessKey) { + if (accessKey.size() == 0) { + // Wait for access key + return; + } + + // Request CDN URL + CSPOT_LOG(info, "Received access key, fetching CDN URL..."); + + try { + + std::string requestUrl = string_format( + "https://api.spotify.com/v1/storage-resolve/files/audio/interactive/" + "%s?alt=json&product=9", + bytesToHexString(fileId).c_str()); + + auto req = bell::HTTPClient::get( + requestUrl, {bell::HTTPClient::ValueHeader( + {"Authorization", "Bearer " + accessKey})}); + + // Wait for response + std::string_view result = req->body(); + +#ifdef BELL_ONLY_CJSON + cJSON* jsonResult = cJSON_Parse(result.data()); + cdnUrl = cJSON_GetArrayItem(cJSON_GetObjectItem(jsonResult, "cdnurl"), 0) + ->valuestring; + cJSON_Delete(jsonResult); +#else + auto jsonResult = nlohmann::json::parse(result); + cdnUrl = jsonResult["cdnurl"][0]; +#endif + + CSPOT_LOG(info, "Received CDN URL, %s", cdnUrl.c_str()); + state = State::READY; + loadedSemaphore->give(); + } catch (...) { + CSPOT_LOG(error, "Cannot fetch CDN URL"); + state = State::FAILED; + loadedSemaphore->give(); + } +} + +void QueuedTrack::expire() { + if (state != State::QUEUED) { + state = State::FAILED; + loadedSemaphore->give(); + } +} + +void QueuedTrack::stepLoadMetadata( + Track* pbTrack, Episode* pbEpisode, std::mutex& trackListMutex, + std::shared_ptr updateSemaphore) { + + // Prepare request ID + std::string requestUrl = string_format( + "hm://metadata/3/%s/%s", + ref.type == TrackReference::Type::TRACK ? "track" : "episode", + bytesToHexString(ref.gid).c_str()); + + auto responseHandler = [this, pbTrack, pbEpisode, &trackListMutex, + updateSemaphore](MercurySession::Response& res) { + std::scoped_lock lock(trackListMutex); + + if (res.parts.size() == 0) { + // Invalid metadata, cannot proceed + state = State::FAILED; + updateSemaphore->give(); + loadedSemaphore->give(); + return; + } + + // Parse the metadata + if (ref.type == TrackReference::Type::TRACK) { + pb_release(Track_fields, pbTrack); + pbDecode(*pbTrack, Track_fields, res.parts[0]); + } else { + pb_release(Episode_fields, pbEpisode); + pbDecode(*pbEpisode, Episode_fields, res.parts[0]); + } + + // Parse received metadata + stepParseMetadata(pbTrack, pbEpisode); + + updateSemaphore->give(); + }; + // Execute the request + pendingMercuryRequest = ctx->session->execute( + MercurySession::RequestType::GET, requestUrl, responseHandler); + + // Set the state to pending + state = State::PENDING_META; +} + +TrackQueue::TrackQueue(std::shared_ptr ctx, + std::shared_ptr state) + : bell::Task("CSpotTrackQueue", 1024 * 32, 2, 1), + playbackState(state), + ctx(ctx) { + accessKeyFetcher = std::make_shared(ctx); + processSemaphore = std::make_shared(); + playableSemaphore = std::make_shared(); + + // Assign encode callback to track list + playbackState->innerFrame.state.track.funcs.encode = + &TrackReference::pbEncodeTrackList; + playbackState->innerFrame.state.track.arg = ¤tTracks; + pbTrack = Track_init_zero; + pbEpisode = Episode_init_zero; + + // Start the task + startTask(); +}; + +TrackQueue::~TrackQueue() { + stopTask(); + + std::scoped_lock lock(tracksMutex); + + pb_release(Track_fields, &pbTrack); + pb_release(Episode_fields, &pbEpisode); +} + +TrackInfo TrackQueue::getTrackInfo(std::string_view identifier) { + for (auto& track : preloadedTracks) { + if (track->identifier == identifier) + return track->trackInfo; + } + return TrackInfo{}; +} + +void TrackQueue::runTask() { + isRunning = true; + + std::scoped_lock lock(runningMutex); + + std::deque> trackQueue; + + while (isRunning) { + processSemaphore->twait(100); + + // Make sure we have the newest access key + accessKey = accessKeyFetcher->getAccessKey(); + + int loadedIndex = currentTracksIndex; + + // No tracks loaded yet + if (loadedIndex < 0) { + continue; + } else { + std::scoped_lock lock(tracksMutex); + + trackQueue = preloadedTracks; + } + + for (auto& track : trackQueue) { + if (track) { + this->processTrack(track); + } + } + } +} + +void TrackQueue::stopTask() { + if (isRunning) { + isRunning = false; + processSemaphore->give(); + std::scoped_lock lock(runningMutex); + } +} + +std::shared_ptr TrackQueue::consumeTrack( + std::shared_ptr prevTrack, int& offset) { + std::scoped_lock lock(tracksMutex); + + if (currentTracksIndex == -1 || currentTracksIndex >= currentTracks.size()) { + return nullptr; + } + + // No previous track, return head + if (prevTrack == nullptr) { + offset = 0; + + return preloadedTracks[0]; + } + + // if (currentTracksIndex + preloadedTracks.size() >= currentTracks.size()) { + // offset = -1; + + // // Last track in queue + // return nullptr; + // } + + auto prevTrackIter = + std::find(preloadedTracks.begin(), preloadedTracks.end(), prevTrack); + + if (prevTrackIter != preloadedTracks.end()) { + // Get offset of next track + offset = prevTrackIter - preloadedTracks.begin() + 1; + } else { + offset = 0; + } + + if (offset >= preloadedTracks.size()) { + // Last track in preloaded queue + return nullptr; + } + + // Return the current track + return preloadedTracks[offset]; +} + +void TrackQueue::processTrack(std::shared_ptr track) { + switch (track->state) { + case QueuedTrack::State::QUEUED: + track->stepLoadMetadata(&pbTrack, &pbEpisode, tracksMutex, + processSemaphore); + break; + case QueuedTrack::State::KEY_REQUIRED: + track->stepLoadAudioFile(tracksMutex, processSemaphore); + break; + case QueuedTrack::State::CDN_REQUIRED: + track->stepLoadCDNUrl(accessKey); + + if (track->state == QueuedTrack::State::READY) { + if (preloadedTracks.size() < MAX_TRACKS_PRELOAD) { + // Queue a new track to preload + queueNextTrack(preloadedTracks.size()); + } + } + break; + default: + // Do not perform any action + break; + } +} + +bool TrackQueue::queueNextTrack(int offset, uint32_t positionMs) { + const int requestedRefIndex = offset + currentTracksIndex; + if (requestedRefIndex < 0 || requestedRefIndex >= currentTracks.size()) { + return false; + } + + if (offset < 0) { + preloadedTracks.push_front(std::make_shared( + currentTracks[requestedRefIndex], ctx, positionMs)); + } else { + preloadedTracks.push_back(std::make_shared( + currentTracks[requestedRefIndex], ctx, positionMs)); + } + + return true; +} + +bool TrackQueue::skipTrack(SkipDirection dir, bool expectNotify) { + bool canSkipNext = currentTracks.size() > currentTracksIndex + 1; + bool canSkipPrev = currentTracksIndex > 0; + + if ((dir == SkipDirection::NEXT && canSkipNext) || + (dir == SkipDirection::PREV && canSkipPrev)) { + std::scoped_lock lock(tracksMutex); + if (dir == SkipDirection::NEXT) { + preloadedTracks.pop_front(); + + if (!queueNextTrack(preloadedTracks.size() + 1)) { + CSPOT_LOG(info, "Failed to queue next track"); + } + + currentTracksIndex++; + } else { + queueNextTrack(-1); + + if (preloadedTracks.size() > MAX_TRACKS_PRELOAD) { + preloadedTracks.pop_back(); + } + + currentTracksIndex--; + } + + // Update frame data + playbackState->innerFrame.state.playing_track_index = currentTracksIndex; + + if (expectNotify) { + // Reset position to zero + notifyPending = true; + } + + return true; + } + + return false; +} + +bool TrackQueue::hasTracks() { + std::scoped_lock lock(tracksMutex); + + return currentTracks.size() > 0; +} + +bool TrackQueue::isFinished() { + std::scoped_lock lock(tracksMutex); + return currentTracksIndex >= currentTracks.size() - 1; +} + +void TrackQueue::updateTracks(uint32_t requestedPosition, bool initial) { + std::scoped_lock lock(tracksMutex); + + if (initial) { + // Clear preloaded tracks + preloadedTracks.clear(); + + // Copy requested track list + currentTracks = playbackState->remoteTracks; + + currentTracksIndex = playbackState->innerFrame.state.playing_track_index; + + if (currentTracksIndex < currentTracks.size()) { + // Push a song on the preloaded queue + queueNextTrack(0, requestedPosition); + } + + // We already updated track meta, mark it + notifyPending = true; + + playableSemaphore->give(); + } else { + // Clear preloaded tracks + preloadedTracks.clear(); + + // Copy requested track list + currentTracks = playbackState->remoteTracks; + + // Push a song on the preloaded queue + queueNextTrack(0, requestedPosition); + } +} diff --git a/components/spotify/cspot/src/TrackReference.cpp b/components/spotify/cspot/src/TrackReference.cpp new file mode 100644 index 00000000..4c399dba --- /dev/null +++ b/components/spotify/cspot/src/TrackReference.cpp @@ -0,0 +1,156 @@ +#include "TrackReference.h" + +#include "NanoPBExtensions.h" +#include "Utils.h" +#include "protobuf/spirc.pb.h" + +using namespace cspot; + +static constexpr auto base62Alphabet = + "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + +TrackReference::TrackReference() : type(Type::TRACK) {} + +void TrackReference::decodeURI() { + if (gid.size() == 0) { + // Episode GID is being fetched via base62 encoded URI + auto idString = uri.substr(uri.find_last_of(":") + 1, uri.size()); + gid = {0}; + + std::string_view alphabet(base62Alphabet); + for (int x = 0; x < idString.size(); x++) { + size_t d = alphabet.find(idString[x]); + gid = bigNumMultiply(gid, 62); + gid = bigNumAdd(gid, d); + } + +#if __cplusplus >= 202002L + if (uri.starts_with("episode")) { +#else + if (uri.find("episode") == 0) { +#endif + type = Type::EPISODE; + } + } +} + +bool TrackReference::operator==(const TrackReference& other) const { + return other.gid == gid && other.uri == uri; +} + +bool TrackReference::pbEncodeTrackList(pb_ostream_t* stream, + const pb_field_t* field, + void* const* arg) { + auto trackQueue = *static_cast*>(*arg); + static TrackRef msg = TrackRef_init_zero; + + // Prepare nanopb callbacks + msg.context.funcs.encode = &bell::nanopb::encodeString; + msg.uri.funcs.encode = &bell::nanopb::encodeString; + msg.gid.funcs.encode = &bell::nanopb::encodeVector; + msg.queued.funcs.encode = &bell::nanopb::encodeBoolean; + + for (auto trackRef : trackQueue) { + if (!pb_encode_tag_for_field(stream, field)) { + return false; + } + + msg.gid.arg = &trackRef.gid; + msg.uri.arg = &trackRef.uri; + msg.context.arg = &trackRef.context; + msg.queued.arg = &trackRef.queued; + + if (!pb_encode_submessage(stream, TrackRef_fields, &msg)) { + return false; + } + } + + return true; +} + +bool TrackReference::pbDecodeTrackList(pb_istream_t* stream, + const pb_field_t* field, void** arg) { + auto trackQueue = static_cast*>(*arg); + + // Push a new reference + trackQueue->push_back(TrackReference()); + + auto& track = trackQueue->back(); + + bool eof = false; + pb_wire_type_t wire_type; + pb_istream_t substream; + uint32_t tag; + + while (!eof) { + if (!pb_decode_tag(stream, &wire_type, &tag, &eof)) { + // Decoding failed and not eof + if (!eof) { + return false; + } + // EOF + } else { + switch (tag) { + case TrackRef_uri_tag: + case TrackRef_context_tag: + case TrackRef_gid_tag: { + // Make substream + if (!pb_make_string_substream(stream, &substream)) { + + return false; + } + + uint8_t* destBuffer = nullptr; + + // Handle GID + if (tag == TrackRef_gid_tag) { + track.gid.resize(substream.bytes_left); + destBuffer = &track.gid[0]; + } else if (tag == TrackRef_context_tag) { + track.context.resize(substream.bytes_left); + + destBuffer = reinterpret_cast(&track.context[0]); + } else if (tag == TrackRef_uri_tag) { + track.uri.resize(substream.bytes_left); + + destBuffer = reinterpret_cast(&track.uri[0]); + } + + if (!pb_read(&substream, destBuffer, substream.bytes_left)) { + return false; + } + + // Close substream + if (!pb_close_string_substream(stream, &substream)) { + return false; + } + + break; + } + case TrackRef_queued_tag: { + uint32_t queuedValue; + + // Decode boolean + if (!pb_decode_varint32(stream, &queuedValue)) { + return false; + } + + // Cast down to bool + track.queued = (bool)queuedValue; + + break; + } + default: + // Field not known, skip + pb_skip_field(stream, wire_type); + + break; + } + } + } + + // Fill in GID when only URI is provided + track.decodeURI(); + + return true; +} diff --git a/components/squeezelite/CMakeLists.txt b/components/squeezelite/CMakeLists.txt index a0c0fb0e..7d1ba61e 100644 --- a/components/squeezelite/CMakeLists.txt +++ b/components/squeezelite/CMakeLists.txt @@ -1,3 +1,8 @@ +# for the forgetful, REQUIRES cannot use CONFIG_XXX due to parsing order +if(IDF_TARGET STREQUAL "esp32") + set(target_requires "driver_bt") +endif() + idf_component_register( SRC_DIRS . external ac101 tas57xx wm8978 INCLUDE_DIRS . ac101 PRIV_REQUIRES @@ -6,7 +11,6 @@ idf_component_register( SRC_DIRS . external ac101 tas57xx wm8978 esp_common esp-dsp platform_config - driver_bt services spotify raop @@ -15,6 +19,7 @@ idf_component_register( SRC_DIRS . external ac101 tas57xx wm8978 audio led_strip _override + ${target_requires} EMBED_FILES vu_s.data arrow.data ) diff --git a/components/squeezelite/ac101/ac101.c b/components/squeezelite/ac101/ac101.c index 51944998..bc64378f 100644 --- a/components/squeezelite/ac101/ac101.c +++ b/components/squeezelite/ac101/ac101.c @@ -48,7 +48,7 @@ static const char TAG[] = "AC101"; return b;\ } -static bool init(char *config, int i2c_port, i2s_config_t *i2s_config); +static bool init(char *config, int i2c_port, i2s_config_t *i2s_config, bool *mck); static void speaker(bool active); static void headset(bool active); static bool volume(unsigned left, unsigned right); @@ -64,7 +64,7 @@ static void ac101_set_spk_volume(uint8_t volume); /**************************************************************************************** * init */ -static bool init(char *config, int i2c_port, i2s_config_t *i2s_config) { +static bool init(char *config, int i2c_port, i2s_config_t *i2s_config, bool *mck) { adac_init(config, i2c_port); if (adac_read_word(AC101_ADDR, CHIP_AUDIO_RS) == 0xffff) { ESP_LOGW(TAG, "No AC101 detected"); diff --git a/components/squeezelite/adac.h b/components/squeezelite/adac.h index 5a3a8cf6..1ab7790e 100644 --- a/components/squeezelite/adac.h +++ b/components/squeezelite/adac.h @@ -17,7 +17,7 @@ typedef enum { ADAC_ON = 0, ADAC_STANDBY, ADAC_OFF } adac_power_e; struct adac_s { char *model; - bool (*init)(char *config, int i2c_port_num, i2s_config_t *i2s_config); + bool (*init)(char *config, int i2c_port_num, i2s_config_t *i2s_config, bool *mck); void (*deinit)(void); void (*power)(adac_power_e mode); void (*speaker)(bool active); diff --git a/components/squeezelite/adac_core.c b/components/squeezelite/adac_core.c index 27db6f58..ea72450c 100644 --- a/components/squeezelite/adac_core.c +++ b/components/squeezelite/adac_core.c @@ -29,16 +29,8 @@ static int i2c_port = -1; * init */ int adac_init(char *config, int i2c_port_num) { - char *p; int i2c_addr = 0; - - // some crappy codecs require MCLK to work - if ((p = strcasestr(config, "mck")) != NULL) { - ESP_LOGI(TAG, "Configuring MCLK on GPIO0"); - PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1); - REG_WRITE(PIN_CTRL, 0xFFFFFFF0); - } - + i2c_port = i2c_port_num; // configure i2c diff --git a/components/squeezelite/decode_external.c b/components/squeezelite/decode_external.c index 71536347..d7437526 100644 --- a/components/squeezelite/decode_external.c +++ b/components/squeezelite/decode_external.c @@ -406,7 +406,7 @@ static bool cspot_cmd_handler(cspot_event_t cmd, va_list args) case CSPOT_VOLUME: { u32_t volume = va_arg(args, u32_t); LOG_INFO("CSpot volume %u", volume); - volume = 65536 * powf(volume / 65536.0f, 2); + volume = 65536 * powf(volume / 65536.0f, 4); set_volume(volume, volume); break; default: diff --git a/components/squeezelite/external/dac_external.c b/components/squeezelite/external/dac_external.c index d16c6d32..fd72c00c 100644 --- a/components/squeezelite/external/dac_external.c +++ b/components/squeezelite/external/dac_external.c @@ -24,7 +24,7 @@ static void speaker(bool active); static void headset(bool active); static bool volume(unsigned left, unsigned right) { return false; } static void power(adac_power_e mode); -static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config); +static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config, bool *mck); static bool i2c_json_execute(char *set); @@ -56,7 +56,7 @@ static struct { /**************************************************************************************** * init */ -static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config) { +static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config, bool *mck) { char *p; i2c_addr = adac_init(config, i2c_port_num); @@ -71,11 +71,7 @@ static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config) { int i; sscanf(p, "%*[^=]=%31[^,]", model); for (i = 0; *model && ((p = codecs[i].controlset) != NULL) && strcasecmp(codecs[i].model, model); i++); - if (p && codecs[i].mclk) { - ESP_LOGI(TAG, "Configuring MCLK on GPIO0"); - PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1); - REG_WRITE(PIN_CTRL, 0xFFFFFFF0); - } + if (p) *mck = codecs[i].mclk; } i2c_json = cJSON_Parse(p); diff --git a/components/squeezelite/opus.c b/components/squeezelite/opus.c index 368efe59..6280332f 100644 --- a/components/squeezelite/opus.c +++ b/components/squeezelite/opus.c @@ -152,9 +152,9 @@ static int get_opus_packet(void) { if (status) OG(&go, stream_pagein, &u->state, &u->page); } - // only return a negative value when end of streaming is reached + // only return a negative value when true end of streaming is reached if (status > 0) packet = status; - else if (stream.state > DISCONNECT) packet = 0; + else if (stream.state > DISCONNECT || _buf_used(streambuf)) packet = 0; UNLOCK_S; return packet; diff --git a/components/squeezelite/output_i2s.c b/components/squeezelite/output_i2s.c index 9f06952d..6c1dd6c8 100644 --- a/components/squeezelite/output_i2s.c +++ b/components/squeezelite/output_i2s.c @@ -190,10 +190,14 @@ static void set_amp_gpio(int gpio, char *value) { * Set pin from config string */ static void set_i2s_pin(char *config, i2s_pin_config_t *pin_config) { - pin_config->bck_io_num = pin_config->ws_io_num = pin_config->data_out_num = pin_config->data_in_num = -1; + pin_config->bck_io_num = pin_config->ws_io_num = pin_config->data_out_num = pin_config->data_in_num = -1; PARSE_PARAM(config, "bck", '=', pin_config->bck_io_num); PARSE_PARAM(config, "ws", '=', pin_config->ws_io_num); PARSE_PARAM(config, "do", '=', pin_config->data_out_num); +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0) + pin_config->mck_io_num = strcasestr(config, "mck") ? 0 : -1; + PARSE_PARAM(config, "mck", '=', pin_config->mck_io_num); +#endif } /**************************************************************************************** @@ -234,14 +238,19 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch ",ws=" STR(CONFIG_SPDIF_WS_IO) ",do=" STR(CONFIG_SPDIF_DO_IO)); char *dac_config = config_alloc_get_str("dac_config", CONFIG_DAC_CONFIG, "model=i2s,bck=" STR(CONFIG_I2S_BCK_IO) - ",ws=" STR(CONFIG_I2S_WS_IO) ",do=" STR(CONFIG_I2S_DO_IO) + ",ws=" STR(CONFIG_I2S_WS_IO) ",do=" STR(CONFIG_I2S_DO_IO) ",mck=" STR(CONFIG_I2S_MCK_IO) ",sda=" STR(CONFIG_I2C_SDA) ",scl=" STR(CONFIG_I2C_SCL) ",mute=" STR(CONFIG_MUTE_GPIO)); - i2s_pin_config_t i2s_dac_pin, i2s_spdif_pin; + i2s_pin_config_t i2s_dac_pin, i2s_spdif_pin; set_i2s_pin(spdif_config, &i2s_spdif_pin); set_i2s_pin(dac_config, &i2s_dac_pin); - + + if (i2s_dac_pin.data_out_num == -1 && i2s_spdif_pin.data_out_num == -1) { + LOG_WARN("DAC and SPDIF not configured, NOT launching i2s thread"); + return; + } + /* BEWARE: i2s.c must be patched otherwise L/R are swapped in 32 bits mode */ // common I2S initialization @@ -250,7 +259,9 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch i2s_config.communication_format = I2S_COMM_FORMAT_STAND_I2S; // in case of overflow, do not replay old buffer i2s_config.tx_desc_auto_clear = true; - i2s_config.use_apll = true; +#ifndef CONFIG_IDF_TARGET_ESP32S3 + i2s_config.use_apll = true; +#endif i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1; //Interrupt level 1 if (strcasestr(device, "spdif")) { @@ -272,8 +283,8 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch i2s_config.dma_buf_count = DMA_BUF_COUNT * 2; /* In DMA, we have room for (LEN * COUNT) frames of 32 bits samples that - we push at sample_rate * 2. Each of these peuso-frames is a single true - audio frame. So the real depth is true frames is (LEN * COUNT / 2) + we push at sample_rate * 2. Each of these pseudo-frames is a single true + audio frame. So the real depth in true frames is (LEN * COUNT / 2) */ dma_buf_frames = DMA_BUF_COUNT * DMA_BUF_LEN / 2; @@ -303,12 +314,36 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch if ((p = strchr(mute, ':')) != NULL) mute_control.active = atoi(p + 1); } + bool mck_required = false; for (int i = 0; adac == &dac_external && dac_set[i]; i++) if (strcasestr(dac_set[i]->model, model)) adac = dac_set[i]; - res = adac->init(dac_config, I2C_PORT, &i2s_config) ? ESP_OK : ESP_FAIL; + res = adac->init(dac_config, I2C_PORT, &i2s_config, &mck_required) ? ESP_OK : ESP_FAIL; + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 0) + int mck_io_num = strcasestr(dac_config, "mck") || mck_required ? 0 : -1; + PARSE_PARAM(dac_config, "mck", '=', mck_io_num); + LOG_INFO("configuring MCLK on GPIO %d", mck_io_num); + + if (mck_io_num == GPIO_NUM_0) { + PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1); + WRITE_PERI_REG(PIN_CTRL, CONFIG_I2S_NUM == I2S_NUM_0 ? 0xFFF0 : 0xFFFF); + } else if (mck_io_num == GPIO_NUM_1) { + PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD_CLK_OUT3); + WRITE_PERI_REG(PIN_CTRL, CONFIG_I2S_NUM == I2S_NUM_0 ? 0xF0F0 : 0xF0FF); + } else if (mck_io_num == GPIO_NUM_2) { + PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_U0RXD_CLK_OUT2); + WRITE_PERI_REG(PIN_CTRL, CONFIG_I2S_NUM == I2S_NUM_0 ? 0xFF00 : 0xFF0F); + } else { + LOG_WARN("invalid MCK gpio %d", mck_io_num); + } +#else + if (mck_required && i2s_dac_pin.mck_io_num == -1) i2s_dac_pin.mck_io_num = 0; + LOG_INFO("configuring MCLK on GPIO %d", i2s_dac_pin.mck_io_num); +#endif + res |= i2s_driver_install(CONFIG_I2S_NUM, &i2s_config, 0, NULL); res |= i2s_set_pin(CONFIG_I2S_NUM, &i2s_dac_pin); - + if (res == ESP_OK && mute_control.gpio >= 0) { gpio_pad_select_gpio(mute_control.gpio); gpio_set_direction(mute_control.gpio, GPIO_MODE_OUTPUT); @@ -366,7 +401,7 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch // create task as a FreeRTOS task but uses stack in internal RAM { static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4))); - static DRAM_ATTR StackType_t xStack[OUTPUT_THREAD_STACK_SIZE] __attribute__ ((aligned (4))); + static EXT_RAM_ATTR StackType_t xStack[OUTPUT_THREAD_STACK_SIZE] __attribute__ ((aligned (4))); output_i2s_task = xTaskCreateStaticPinnedToCore( (TaskFunction_t) output_thread_i2s, "output_i2s", OUTPUT_THREAD_STACK_SIZE, NULL, CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 1, xStack, &xTaskBuffer, 0 ); } diff --git a/components/squeezelite/slimproto.c b/components/squeezelite/slimproto.c index 146ac381..7a70614d 100644 --- a/components/squeezelite/slimproto.c +++ b/components/squeezelite/slimproto.c @@ -99,7 +99,7 @@ static char *new_server_cap; static char player_name[PLAYER_NAME_LEN + 1] = ""; static const char *name_file = NULL; -void send_packet(u8_t *packet, size_t len) { +void slimproto_send_packet(u8_t *packet, size_t len) { u8_t *ptr = packet; unsigned try = 0; ssize_t n; diff --git a/components/squeezelite/squeezelite.h b/components/squeezelite/squeezelite.h index 6703b794..60ac4f64 100644 --- a/components/squeezelite/squeezelite.h +++ b/components/squeezelite/squeezelite.h @@ -559,7 +559,8 @@ void buf_destroy(struct buffer *buf); void slimproto(log_level level, char *server, u8_t mac[6], const char *name, const char *namefile, const char *modelname, int maxSampleRate); void slimproto_stop(void); void wake_controller(void); -void send_packet(u8_t *packet, size_t len); +void slimproto_send_packet(u8_t *packet, size_t len); +#define send_packet(p, s) slimproto_send_packet(p,s) // stream.c typedef enum { STOPPED = 0, DISCONNECT, STREAMING_WAIT, diff --git a/components/squeezelite/tas57xx/dac_5713.c b/components/squeezelite/tas57xx/dac_5713.c index 7bc77f9d..1a45efac 100644 --- a/components/squeezelite/tas57xx/dac_5713.c +++ b/components/squeezelite/tas57xx/dac_5713.c @@ -39,7 +39,7 @@ static const char TAG[] = "TAS5713"; -static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config); +static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config, bool *mck); static void speaker(bool active) { }; static void headset(bool active) { } ; static bool volume(unsigned left, unsigned right); @@ -65,7 +65,7 @@ typedef enum { /**************************************************************************************** * init */ -static bool init(char *config, int i2c_port, i2s_config_t *i2s_config) { +static bool init(char *config, int i2c_port, i2s_config_t *i2s_config, bool *mck) { /* find if there is a tas5713 attached. Reg 0 should read non-zero but not 255 if so */ adac_init(config, i2c_port); if (adac_read_byte(TAS5713, 0x00) == 255) { diff --git a/components/squeezelite/tas57xx/dac_57xx.c b/components/squeezelite/tas57xx/dac_57xx.c index c689a0d6..45b073c8 100644 --- a/components/squeezelite/tas57xx/dac_57xx.c +++ b/components/squeezelite/tas57xx/dac_57xx.c @@ -23,7 +23,7 @@ static const char TAG[] = "TAS575x/8x"; -static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config); +static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config, bool *mck); static void speaker(bool active); static void headset(bool active); static bool volume(unsigned left, unsigned right); @@ -71,7 +71,7 @@ static int tas57_detect(void); /**************************************************************************************** * init */ -static bool init(char *config, int i2c_port, i2s_config_t *i2s_config) { +static bool init(char *config, int i2c_port, i2s_config_t *i2s_config, bool *mck) { // find which TAS we are using (if any) tas57_addr = adac_init(config, i2c_port); if (!tas57_addr) tas57_addr = tas57_detect(); diff --git a/components/squeezelite/vorbis.c b/components/squeezelite/vorbis.c index db2e0cbb..a5f9d47e 100644 --- a/components/squeezelite/vorbis.c +++ b/components/squeezelite/vorbis.c @@ -152,9 +152,9 @@ static int get_ogg_packet(void) { if (status) OG(&go, stream_pagein, &v->state, &v->page); } - // only return a negative value when end of streaming is reached + // only return a negative value when true end of streaming is reached if (status > 0) packet = status; - else if (stream.state > DISCONNECT) packet = 0; + else if (stream.state > DISCONNECT || _buf_used(streambuf)) packet = 0; UNLOCK_S; return packet; diff --git a/components/squeezelite/wm8978/wm8978.c b/components/squeezelite/wm8978/wm8978.c index fd6b12f8..09621964 100644 --- a/components/squeezelite/wm8978/wm8978.c +++ b/components/squeezelite/wm8978/wm8978.c @@ -23,7 +23,7 @@ static void speaker(bool active) { } static void headset(bool active) { } static bool volume(unsigned left, unsigned right) { return false; } static void power(adac_power_e mode); -static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config); +static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config, bool *mck); static esp_err_t i2c_write_shadow(uint8_t reg, uint16_t val); static uint16_t i2c_read_shadow(uint8_t reg); @@ -47,12 +47,14 @@ static uint16_t WM8978_REGVAL_TBL[58] = { /**************************************************************************************** * init */ -static bool init(char *config, int i2c_port, i2s_config_t *i2s_config) { +static bool init(char *config, int i2c_port, i2s_config_t *i2s_config, bool *mck) { WM8978 = adac_init(config, i2c_port); if (!WM8978) WM8978 = 0x1a; ESP_LOGI(TAG, "WM8978 detected @%d", WM8978); - + + *mck = true; + // init sequence i2c_write_shadow(0, 0); i2c_write_shadow(4, 16); @@ -61,11 +63,6 @@ static bool init(char *config, int i2c_port, i2s_config_t *i2s_config) { i2c_write_shadow(43, 16); i2c_write_shadow(49, 102); - // Configure system clk to GPIO0 for DAC MCLK input - ESP_LOGI(TAG, "Configuring MCLK on GPIO0"); - PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1); - REG_WRITE(PIN_CTRL, 0xFFFFFFF0); - return true; } diff --git a/components/wifi-manager/CMakeLists.txt b/components/wifi-manager/CMakeLists.txt index c65e47fb..db3be0c6 100644 --- a/components/wifi-manager/CMakeLists.txt +++ b/components/wifi-manager/CMakeLists.txt @@ -1,10 +1,14 @@ - set( WEBPACK_DIR webapp/webpack/dist ) +# for the forgetful, REQUIRES cannot use CONFIG_XXX due to parsing order +if(IDF_TARGET STREQUAL "esp32") + set(target_requires "driver_bt") +endif() + idf_component_register( SRC_DIRS . webapp UML-State-Machine-in-C/src INCLUDE_DIRS . webapp UML-State-Machine-in-C/src REQUIRES squeezelite-ota json mdns - PRIV_REQUIRES tools services platform_config esp_common json newlib freertos spi_flash nvs_flash mdns pthread wpa_supplicant platform_console esp_http_server console driver_bt + PRIV_REQUIRES tools services platform_config esp_common json newlib freertos spi_flash nvs_flash mdns pthread wpa_supplicant platform_console esp_http_server console ${target_requires} ) include(webapp/webapp.cmake) \ No newline at end of file diff --git a/components/wifi-manager/network_status.c b/components/wifi-manager/network_status.c index 3080eb7f..d47fe418 100644 --- a/components/wifi-manager/network_status.c +++ b/components/wifi-manager/network_status.c @@ -4,7 +4,9 @@ #include "network_status.h" #include +#ifdef CONFIG_BT_ENABLED #include "bt_app_core.h" +#endif #include "esp_log.h" #include "lwip/inet.h" #include "monitor.h" @@ -264,8 +266,10 @@ cJSON* network_status_get_basic_info(cJSON** old) { *old = network_status_update_float(old, "Voltage", battery_value_svc()); *old = network_update_cjson_number(old, "disconnect_count", nm->num_disconnect); *old = network_status_update_float(old, "avg_conn_time", nm->num_disconnect > 0 ? (nm->total_connected_time / nm->num_disconnect) : 0); +#ifdef CONFIG_BT_ENABLED *old = network_update_cjson_number(old, "bt_status", bt_app_source_get_a2d_state()); *old = network_update_cjson_number(old, "bt_sub_status", bt_app_source_get_media_state()); +#endif #if DEPTH == 16 *old = network_update_cjson_number(old, "depth", 16); #elif DEPTH == 32 diff --git a/components/wifi-manager/webapp/.babelrc b/components/wifi-manager/webapp/.babelrc index f5b2d163..4fe0fa57 100644 --- a/components/wifi-manager/webapp/.babelrc +++ b/components/wifi-manager/webapp/.babelrc @@ -9,11 +9,5 @@ "minify" ] } - }, - "plugins": [ - "@babel/plugin-proposal-nullish-coalescing-operator", - "@babel/plugin-proposal-optional-chaining", - ["@babel/plugin-transform-runtime"], - - ], + } } diff --git a/components/wifi-manager/webapp/dist/dist/js/index.1a3b6c.bundle.d.ts b/components/wifi-manager/webapp/dist/dist/js/index.997af2.bundle.d.ts similarity index 100% rename from components/wifi-manager/webapp/dist/dist/js/index.1a3b6c.bundle.d.ts rename to components/wifi-manager/webapp/dist/dist/js/index.997af2.bundle.d.ts diff --git a/components/wifi-manager/webapp/dist/dist/js/node_vendors.1a3b6c.bundle.d.ts b/components/wifi-manager/webapp/dist/dist/js/node_vendors.997af2.bundle.d.ts similarity index 100% rename from components/wifi-manager/webapp/dist/dist/js/node_vendors.1a3b6c.bundle.d.ts rename to components/wifi-manager/webapp/dist/dist/js/node_vendors.997af2.bundle.d.ts diff --git a/components/wifi-manager/webapp/dist/index.html b/components/wifi-manager/webapp/dist/index.html index 2d912b86..4de342f0 100644 --- a/components/wifi-manager/webapp/dist/index.html +++ b/components/wifi-manager/webapp/dist/index.html @@ -1 +1 @@ -
Software Updates
VersionDate/TimePlatformBranchBit Depth
Local Firmware Upload
KeyValue
Usage Templates

Supported: flac,pcm,mp3,ogg (mad,mpg for specific mp3 codec)
Close output device after timeout seconds, default is to keep it open while player is 'on'
Logs: all|slimproto|stream|decode|output, level: info|debug|sdebug
Supported: flac,pcm,mp3,ogg (mad,mpg for specific mp3 codec)
Format: ab:cd:ef:12:34:56
<maxrate>|<minrate><maxrate><rate1><rate2><rate3>

WiFi Status
Logs
TimestampMessage
Tasks
#Task NameCPUStateMin StackBase PriorityCur Priority
Credits

squeezelite-esp32
© 2020, philippe44, sle118, daduke
This software is released under the MIT License.

This app would not be possible without the following libraries:

  • squeezelite, © 2012-2019, Adrian Smith and Ralph Irving. Licensed under the GPL License.
  • esp32-wifi-manager, © 2017-2019, Tony Pottier. Licensed under the MIT License.
  • SpinKit, © 2015, Tobias Ahlin. Licensed under the MIT License.
  • jQuery, The jQuery Foundation. Licensed under the MIT License.
  • cJSON, © 2009-2017, Dave Gamble and cJSON contributors. Licensed under the MIT License.
  • esp32-rotary-encoder, © 2011-2019, David Antliff and Ben Buxton. Licensed under the GPL License.
  • tarablessd1306, © 2017-2018, Tara Keeling. Licensed under the MIT license.
  • CSpot, © 2020 feelfreelinux & alufers. Licensed under the GPL License
Extras/Overrides
\ No newline at end of file +
Software Updates
VersionDate/TimePlatformBranchBit Depth
Local Firmware Upload
KeyValue
Usage Templates

Supported: flac,pcm,mp3,ogg (mad,mpg for specific mp3 codec)
Close output device after timeout seconds, default is to keep it open while player is 'on'
Logs: all|slimproto|stream|decode|output, level: info|debug|sdebug
Supported: flac,pcm,mp3,ogg (mad,mpg for specific mp3 codec)
Format: ab:cd:ef:12:34:56
<maxrate>|<minrate><maxrate><rate1><rate2><rate3>

WiFi Status
Logs
TimestampMessage
Tasks
#Task NameCPUStateMin StackBase PriorityCur Priority
Credits

squeezelite-esp32
© 2020, philippe44, sle118, daduke
This software is released under the MIT License.

This app would not be possible without the following libraries:

  • squeezelite, © 2012-2019, Adrian Smith and Ralph Irving. Licensed under the GPL License.
  • esp32-wifi-manager, © 2017-2019, Tony Pottier. Licensed under the MIT License.
  • SpinKit, © 2015, Tobias Ahlin. Licensed under the MIT License.
  • jQuery, The jQuery Foundation. Licensed under the MIT License.
  • cJSON, © 2009-2017, Dave Gamble and cJSON contributors. Licensed under the MIT License.
  • esp32-rotary-encoder, © 2011-2019, David Antliff and Ben Buxton. Licensed under the GPL License.
  • tarablessd1306, © 2017-2018, Tara Keeling. Licensed under the MIT license.
  • CSpot, © 2020 feelfreelinux & alufers. Licensed under the GPL License
Extras/Overrides
\ No newline at end of file diff --git a/components/wifi-manager/webapp/dist/index.html.gz b/components/wifi-manager/webapp/dist/index.html.gz index 1a441744..3b45afb4 100644 Binary files a/components/wifi-manager/webapp/dist/index.html.gz and b/components/wifi-manager/webapp/dist/index.html.gz differ diff --git a/components/wifi-manager/webapp/dist/js/index.997af2.bundle.js b/components/wifi-manager/webapp/dist/js/index.1e1c60.bundle.js similarity index 99% rename from components/wifi-manager/webapp/dist/js/index.997af2.bundle.js rename to components/wifi-manager/webapp/dist/js/index.1e1c60.bundle.js index 2b4d76ef..c2e0b94a 100644 --- a/components/wifi-manager/webapp/dist/js/index.997af2.bundle.js +++ b/components/wifi-manager/webapp/dist/js/index.1e1c60.bundle.js @@ -1,2 +1,2 @@ (()=>{"use strict";var t,e={322:(t,e,n)=>{n.r(e);var a=n(531),s=n(152),o=n(687),i=n.n(o),c=n(955),r=n(755);function l(t,e){var n="undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(!n){if(Array.isArray(t)||(n=function(t,e){if(!t)return;if("string"==typeof t)return u(t,e);var n=Object.prototype.toString.call(t).slice(8,-1);"Object"===n&&t.constructor&&(n=t.constructor.name);if("Map"===n||"Set"===n)return Array.from(t);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return u(t,e)}(t))||e&&t&&"number"==typeof t.length){n&&(t=n);var a=0,s=function(){};return{s,n:function(){return a>=t.length?{done:!0}:{done:!1,value:t[a++]}},e:function(t){throw t},f:s}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var o,i=!0,c=!1;return{s:function(){n=n.call(t)},n:function(){var t=n.next();return i=t.done,t},e:function(t){c=!0,o=t},f:function(){try{i||null==n.return||n.return()}finally{if(c)throw o}}}}function u(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,a=new Array(e);n")}}),Object.assign(Date.prototype,{toLocalShort:function(){return this.toLocaleString(void 0,{dateStyle:"short",timeStyle:"short"})}});var v=1,b=17,g=2,S=18,_=4,y=20,w=8,T=24,E={bt_playing:{label:"",icon:"media_bluetooth_on"},bt_disconnected:{label:"",icon:"media_bluetooth_off"},bt_neutral:{label:"",icon:"bluetooth"},bt_connecting:{label:"",icon:"bluetooth_searching"},bt_connected:{label:"",icon:"bluetooth_connected"},bt_disabled:{label:"",icon:"bluetooth_disabled"},play_arrow:{label:"",icon:"play_circle_filled"},pause:{label:"",icon:"pause_circle"},stop:{label:"",icon:"stop_circle"},"":{label:"",icon:""}},O=[{icon:"battery_0_bar",label:"â–Ē",ranges:[{f:5.8,t:6.8},{f:8.8,t:10.2}]},{icon:"battery_2_bar",label:"â–Ēâ–Ē",ranges:[{f:6.8,t:7.4},{f:10.2,t:11.1}]},{icon:"battery_3_bar",label:"â–Ēâ–Ēâ–Ē",ranges:[{f:7.4,t:7.5},{f:11.1,t:11.25}]},{icon:"battery_4_bar",label:"â–Ēâ–Ēâ–Ēâ–Ē",ranges:[{f:7.5,t:7.8},{f:11.25,t:11.7}]}],A=[{desc:"Idle",sub:["bt_neutral"]},{desc:"Discovering",sub:["bt_connecting"]},{desc:"Discovered",sub:["bt_connecting"]},{desc:"Unconnected",sub:["bt_disconnected"]},{desc:"Connecting",sub:["bt_connecting"]},{desc:"Connected",sub:["bt_connected","play_arrow","bt_playing","pause","stop"]},{desc:"Disconnecting",sub:["bt_disconnected"]}],k={MESSAGING_INFO:"badge-success",MESSAGING_WARNING:"badge-warning",MESSAGING_ERROR:"badge-danger"},x={OK:0,FAIL:1,DISC:2,LOST:3,RESTORE:4,ETH:5},N={0:"eRunning",1:"eReady",2:"eBlocked",3:"eSuspended",4:"eDeleted"},R={NONE:0,REBOOT_TO_RECOVERY:2,SET_FWURL:5,FLASHING:6,DONE:7,UPLOADING:8,ERROR:9,UPLOADCOMPLETE:10,_state:-1,olderRecovery:!1,statusText:"",flashURL:"",flashFileName:"",statusPercent:0,Completed:!1,recovery:!1,prevRecovery:!1,updateModal:new bootstrap.Modal(document.getElementById("otadiv"),{}),reset:function(){return this.olderRecovery=!1,this.statusText="",this.statusPercent=-1,this.flashURL="",this.flashFileName=void 0,this.UpdateProgress(),r("#rTable tr.release").removeClass("table-success table-warning"),r(".flact").prop("disabled",!1),r("#flashfilename").value=null,r("#fw-url-input").value=null,this.isStateError()||(r("span#flash-status").html(""),r("#fwProgressLabel").parent().removeClass("bg-danger")),this._state=this.NONE,this},isStateUploadComplete:function(){return this._state==this.UPLOADCOMPLETE},isStateError:function(){return this._state==this.ERROR},isStateNone:function(){return this._state==this.NONE},isStateRebootRecovery:function(){return this._state==this.REBOOT_TO_RECOVERY},isStateSetUrl:function(){return this._state==this.SET_FWURL},isStateFlashing:function(){return this._state==this.FLASHING},isStateDone:function(){return this._state==this.DONE},isStateUploading:function(){return this._state==this.UPLOADING},init:function(){return this._state=this.NONE,this},SetStateError:function(){return this._state=this.ERROR,r("#fwProgressLabel").parent().addClass("bg-danger"),this},SetStateNone:function(){return this._state=this.NONE,this},SetStateRebootRecovery:function(){return this._state=this.REBOOT_TO_RECOVERY,this.SetStatusText("Starting recovery mode."),r.ajax({url:"/recovery.json",context:this,dataType:"text",method:"POST",cache:!1,contentType:"application/json; charset=utf-8",data:JSON.stringify({timestamp:Date.now()}),error:function(t,e,n){var a;this.setOTAError("Unexpected error while trying to restart to recovery. (status=".concat(null!==(a=t.status)&&void 0!==a?a:"",", error=").concat(null!=n?n:""," ) "))},complete:function(t){this.SetStatusText("Waiting for system to boot.")}}),this},SetStateSetUrl:function(){return this._state=this.SET_FWURL,this.statusText="Sending firmware download location.",G({fwurl:{value:this.flashURL,type:33}}),this},SetStateFlashing:function(){return this._state=this.FLASHING,this},SetStateDone:function(){return this._state=this.DONE,this.reset(),this},SetStateUploading:function(){return this._state=this.UPLOADING,this.SetStatusText("Sending file to device.")},SetStateUploadComplete:function(){return this._state=this.UPLOADCOMPLETE,this},isFlashExecuting:function(){return!0==(this._state!=this.UPLOADING&&(""!==this.statusText||this.statusPercent>=0))},toString:function(){var t=this;return Object.keys(this).find((function(e){return t[e]===t._state}))},setOTATargets:function(){this.flashURL="",this.flashFileName="",this.flashURL=r("#fw-url-input").val();var t=r("#flashfilename")[0].files;return t.length>0&&(this.flashFileName=t[0]),0==this.flashFileName.length&&0==this.flashURL.length&&this.setOTAError("Invalid url or file. Cannot start OTA"),this},setOTAError:function(t){return this.SetStateError().SetStatusPercent(0).SetStatusText(t).reset(),this},ShowDialog:function(){return this.isStateNone()||(this.updateModal.show(),r(".flact").prop("disabled",!0)),this},SetStatusPercent:function(t){var e=this.statusPercent!=t;return this.statusPercent=t,e&&(this.isStateUploading()||this.isStateFlashing()||this.SetStateFlashing(),100==t&&(this.isStateFlashing()?this.SetStateDone():this.isStateUploading()&&(this.statusPercent=0,this.SetStateFlashing())),this.UpdateProgress().ShowDialog()),this},SetStatusText:function(t){var e=this.statusText!=t;return this.statusText=t,e&&(r("span#flash-status").html(this.statusText),this.ShowDialog()),this},UpdateProgress:function(){return r(".progress-bar").css("width",this.statusPercent+"%").attr("aria-valuenow",this.statusPercent).text(this.statusPercent+"%"),r(".progress-bar").html((this.isStateDone()?100:this.statusPercent)+"%"),this},StartOTA:function(){return this.logEvent(this.StartOTA.name),r("#fwProgressLabel").parent().removeClass("bg-danger"),this.setOTATargets(),this.isStateError()||(W?this.SetStateFlashing().TargetReadyStartOTA():this.SetStateRebootRecovery()),this},UploadLocalFile:function(){this.SetStateUploading();var t=new XMLHttpRequest;t.context=this;var e=this.HandleUploadProgressEvent.bind(this),n=this.setOTAError.bind(this);t.upload.addEventListener("progress",e,!1),t.onreadystatechange=function(){4===t.readyState&&(0!==t.status&&404!==t.status||n("Upload Failed. Recovery version might not support uploading. Please use web update instead."))},t.open("POST","/flash.json",!0),t.send(this.flashFileName)},TargetReadyStartOTA:function(){return W&&this.prevRecovery&&!this.isStateRebootRecovery()&&!this.isStateFlashing()?this:(this.logEvent(this.TargetReadyStartOTA.name),W?(this.prevRecovery=!0,void(""!==this.flashFileName?this.UploadLocalFile():""!=this.flashURL?this.SetStateSetUrl():this.setOTAError("Invalid URL or file name while trying to start the OTa process"))):(console.error("Event TargetReadyStartOTA fired in the wrong mode "),this))},HandleUploadProgressEvent:function(t){this.logEvent(this.HandleUploadProgressEvent.name),this.SetStateUploading().SetStatusPercent(Math.round(t.loaded/t.total*100)).SetStatusText("Uploading file to device")},EventTargetStatus:function(t){var e,n;this.isStateNone()||this.logEvent(this.EventTargetStatus.name),null!==(e=t.ota_pct)&&void 0!==e&&e&&(this.olderRecovery=!0,this.SetStatusPercent(t.ota_pct)),""!=(null!==(n=t.ota_dsc)&&void 0!==n?n:"")&&(this.olderRecovery=!0,this.SetStatusText(t.ota_dsc)),null!=t.recovery&&(this.recovery=1===t.recovery),this.isStateRebootRecovery()&&this.recovery&&this.TargetReadyStartOTA()},EventOTAMessageClass:function(t){this.logEvent(this.EventOTAMessageClass.name);var e=JSON.parse(t);this.SetStatusPercent(e.ota_pct).SetStatusText(e.ota_dsc)},logEvent:function(t){console.log("".concat(t,", flash state ").concat(this.toString(),", recovery: ").concat(this.recovery,", ota pct: ").concat(this.statusPercent,", ota desc: ").concat(this.statusText))}};window.hideSurrounding=function(t){r(t).parent().parent().hide()};var C=!1,I=2500;function G(t){var e={timestamp:Date.now(),config:t};r.ajax({url:"/config.json",dataType:"text",method:"POST",cache:!1,contentType:"application/json; charset=utf-8",data:JSON.stringify(e),error:L})}function j(t){for(var e,n,a={},s="",o=t.match(/("[^"]+"|'[^']+'|\S+)/g),i=0;i0&&(e=e.substring(0,e.indexOf(" ")));return e}(a),n=function(t){var e;t.n&&(e=t.n.replace(/"/g,"").replace(/'/g,""));return e}(a);var u={btname:null,n:null};if(a.o&&"BT"===e.toUpperCase()){var d=j(a.o);d.name&&(u.btname=d.name),delete a.o}return a.n&&(u.n=a.n,delete a.n),{name:n,output:e,options:a,otherValues:s,otherOptions:u}}function M(){return it.hasOwnProperty("ip")&&"0.0.0.0"!=it.ip&&""!=it.ip}function P(t){return M()?t.icon:t.label}function U(t){r("#o_type").children("span").css({display:"none"});var e=!1;"bt"===t?(e="bt"!==Q&&""!==Q,Q="bt"):"spdif"===t?(e="spdif"!==Q&&""!==Q,Q="spdif"):(e="i2s"!==Q&&""!==Q,Q="i2s"),r("#"+Q).prop("checked",!0),r("#o_"+Q).css({display:"inline"}),e&&Object.keys(q[Q]).forEach((function(t){r("#cmd_opt_".concat(t)).val(q[Q][t])}))}function L(t,e,n){console.log(t.status),console.log(n),""!==n&&xt(n,"MESSAGING_ERROR")}function F(t,e,n){var a=arguments.length>3&&void 0!==arguments[3]&&arguments[3],s="table-success";"MESSAGING_WARNING"===e?s="table-warning":"MESSAGING_ERROR"===e&&(s="table-danger"),r("#toast_"+t).removeClass("table-success").removeClass("table-warning").removeClass("table-danger").addClass(s).addClass("show");var o=n.substring(0,n.length-1).encodeHTML().replace(/\n/g,"
");o=(r("#msg_"+t).html().length>0&&a?r("#msg_"+t).html()+"
":"")+o,r("#msg_"+t).html(o)}window.hFlash=function(){r("#flashfilename").value=null,R.StartOTA()},window.handleReboot=function(t){"reboot_ota"==t?(r("#reboot_ota_nav").removeClass("active").prop("disabled",!0),dt(500,"","reboot_ota")):(r("#reboot_nav").removeClass("active"),dt(500,"",t))};var D,J="https://api.github.com/repos/sle118/squeezelite-esp32/releases",W=!1,H=!1,B="",q={i2s:{b:"500:2000",C:"30",W:"",Z:"96000",o:"I2S"},spdif:{b:"500:2000",C:"30",W:"",Z:"48000",o:"SPDIF"},bt:{b:"500:2000",C:"30",W:"",Z:"44100",o:"BT"}},Y={codecs:["flac","pcm","mp3","ogg","aac","wma","alac","dsd","mad","mpg"]},z=0,Z="MESSAGING_INFO",V={},K=null,Q="",X="",$="Squeezelite-ESP32",tt="",et=$,nt="",at=$,st="",ot="#cfg-audio-bt_source-sink_name",it={},ct={},rt="",lt={CONN:0,MAN:1,STS:2};function ut(t){var e={};r("input.nvs").each((function(n,a){if(t)e[a.id]=a.value;else{var s=parseInt(a.attributes.nvs_type.value,10);""!==a.id&&(e[a.id]={},e[a.id].value=s===v||s===b||s===g||s===S||s===_||s===y||s===w||s===T?parseInt(a.value):a.value,e[a.id].type=s)}}));var n=r("#nvs-new-key").val(),a=r("#nvs-new-value").val();return""!==n&&(t?e[n]=a:(e[n]={},e[n].value=a,e[n].type=33)),e}function dt(t,e){var n="/"+(arguments.length>2&&void 0!==arguments[2]?arguments[2]:"reboot")+".json";r("tbody#tasks").empty(),r("#tasks_sect").css("visibility","collapse"),h.resolve({cmdname:e,url:n}).delay(t).then((function(t){t.cmdname.length>0?F(t.cmdname,"MESSAGING_WARNING","System is rebooting.\n",!0):xt("System is rebooting.\n","MESSAGING_WARNING"),console.log("now triggering reboot"),r("button[onclick*='handleReboot']").addClass("rebooting"),r.ajax({url:t.url,dataType:"text",method:"POST",cache:!1,contentType:"application/json; charset=utf-8",data:JSON.stringify({timestamp:Date.now()}),error:L,complete:function(){console.log("reboot call completed"),h.resolve(t).delay(6e3).then((function(t){t.cmdname.length>0&&function(t){r("#toast_"+t).removeClass("table-success").removeClass("table-warning").removeClass("table-danger").addClass("table-success").removeClass("show"),r("#msg_"+t).html("")}(t.cmdname),At(),kt()}))}})}))}function ht(t){return r(".upf").filter((function(){return r(this).text().toUpperCase()===t.toUpperCase()})).length>0&&(r("#splf").val(t).trigger("input"),!0)}function pt(t,e){var n="cmd_opt_".concat(t),a="".concat(n,"-error"),s=r("#".concat(a)),o=r("#".concat(n));return s&&0!=s.length||(o.after('
')),s=r("#".concat(a))),0==e.length?(s.hide(),o.removeClass("is-invalid"),o.addClass("is-valid"),s.text("")):(s.show(),s.text(e),o.removeClass("is-valid"),o.addClass("is-invalid")),s}function ft(t){return t>=-55?{label:"****",icon:"signal_wifi_statusbar_4_bar"}:t>=-60?{label:"***",icon:"network_wifi_3_bar"}:t>=-65?{label:"**",icon:"network_wifi_2_bar"}:t>=-70?{label:"*",icon:"network_wifi_1_bar"}:{label:".",icon:"signal_wifi_statusbar_null"}}function mt(){var t;(null===(t=it)||void 0===t?void 0:t.urc)!==x.ETH&&(r.ajaxSetup({timeout:3e3}),r.getJSON("/scan.json",(0,a.Z)(i().mark((function t(){return i().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return t.next=2,Rt(2e3);case 2:r.getJSON("/ap.json",(function(t){t.length>0&&(t.sort((function(t,e){var n=t.rssi,a=e.rssi;return na?-1:0})),bt(t))}));case 3:case"end":return t.stop()}}),t)})))))}function vt(t,e,n){var a=ft(e),s={label:0==n?"🔓":"🔒",icon:0==n?"no_encryption":"lock"};return''.concat(t,'\n ').concat(P(a),'\n \t\n ').concat(P(s),"\n ")}function bt(t){var e,n="";if(r("#wifiTable tr td:first-of-type").text(""),r("#wifiTable tr").removeClass("table-success table-warning"),t&&(t.forEach((function(t){n+=vt(t.ssid,t.rssi,t.auth)})),r("#wifiTable").html(n)),0==r(".manual_add").length&&(r("#wifiTable").append(vt("Manual add",0,0)),r("#wifiTable tr:last").addClass("table-light text-dark").addClass("manual_add")),!it.ssid||it.urc!==x.OK&&it.urc!==x.RESTORE)(null===(e=it)||void 0===e?void 0:e.urc)!==x.ETH&&r("span#foot-if").html("");else{var a,s='#wifiTable td:contains("'.concat(it.ssid,'")');if(0==r(s).filter((function(){return r(this).text()===it.ssid})).length)r("#wifiTable").prepend("".concat(vt(it.ssid,null!==(a=it.rssi)&&void 0!==a?a:0,0)));r(s).filter((function(){return r(this).text()===it.ssid})).siblings().first().html("✓").parent().addClass(it.urc===x.OK?"table-success":"table-warning"),r("span#foot-if").html("SSID: ".concat(it.ssid,", IP: ").concat(it.ip,"")),r("#wifiStsIcon").html(ft(it.rssi))}}function gt(t){console.debug(this.toLocaleString()+"\t"+t.nme+"\t"+t.cpu+"\t"+N[t.st]+"\t"+t.minstk+"\t"+t.bprio+"\t"+t.cprio+"\t"+t.num),r("tbody#tasks").append(''+t.num+""+t.nme+""+t.cpu+""+N[t.st]+""+t.minstk+""+t.bprio+""+t.cprio+"")}function St(t){return r("".concat(ot," option:contains('").concat(t,"')"))}function _t(){r.ajaxSetup({timeout:I}),r.getJSON("/messages.json",function(){var t=(0,a.Z)(i().mark((function t(e){var n,a,s,o,c,u,d,h,p,f;return i().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:n=l(e),t.prev=1,s=i().mark((function t(){var e,n;return i().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:e=a.value,n=e.current_time-e.sent_time,(o=new Date).setTime(o.getTime()-n),t.t0=e.class,t.next="MESSAGING_CLASS_OTA"===t.t0?7:"MESSAGING_CLASS_STATS"===t.t0?9:"MESSAGING_CLASS_SYSTEM"===t.t0?14:"MESSAGING_CLASS_CFGCMD"===t.t0?16:"MESSAGING_CLASS_BT"===t.t0?19:23;break;case 7:return R.EventOTAMessageClass(e.message),t.abrupt("break",24);case 9:return c=JSON.parse(e.message),console.debug(o.toLocalShort()+" - Number of running tasks: "+c.ntasks),console.debug(o.toLocalShort()+"\tname\tcpu\tstate\tminstk\tbprio\tcprio\tnum"),c.tasks?("collapse"===r("#tasks_sect").css("visibility")&&r("#tasks_sect").css("visibility","visible"),r("tbody#tasks").html(""),c.tasks.sort((function(t,e){return e.cpu-t.cpu})).forEach(gt,o)):"visible"===r("#tasks_sect").css("visibility")&&(r("tbody#tasks").empty(),r("#tasks_sect").css("visibility","collapse")),t.abrupt("break",24);case 14:return Nt(e,o),t.abrupt("break",24);case 16:return F((u=e.message.split(/([^\n]*)\n([\s\S]*)/g))[1],e.type,u[2],!0),t.abrupt("break",24);case 19:if(r("#cfg-audio-bt_source-sink_name").is("input")){for(d=r("#cfg-audio-bt_source-sink_name")[0].attributes,h="",p=0;p "))}return JSON.parse(e.message).forEach((function(t){St(t.name).length>0||(r("#cfg-audio-bt_source-sink_name").append("")),Nt({type:e.type,message:"BT Audio device found: ".concat(t.name," RSSI: ").concat(t.rssi," ")},o)),St(t.name).attr("data-bs-description","".concat(t.name," (").concat(t.rssi,"dB)")).attr("rssi",t.rssi).attr("value",t.name).text("".concat(t.name," [").concat(t.rssi,"dB]")).trigger("change")})),r(ot).append(r("".concat(ot," option")).remove().sort((function(t,e){return console.log("".concat(parseInt(r(t).attr("rssi"))," < ").concat(parseInt(r(e).attr("rssi"))," ? ")),parseInt(r(t).attr("rssi"))".concat(it.ip,""))):(r(".if_wifi").show(),bt())),yt(t)}function Et(){r.ajaxSetup({timeout:2e3}),r.getJSON("/status.json",(function(t){var e;if(function(t){var e;1===(null!==(e=t.recovery)&&void 0!==e?e:0)?(W=!0,r(".recovery_element").show(),r(".ota_element").hide(),r("#boot-button").html("Reboot"),r("#boot-form").attr("action","/reboot_ota.json")):(!W&&H&&(H=!1,setTimeout(_t,I)),W=!1,r(".recovery_element").hide(),r(".ota_element").show(),r("#boot-button").html("Recovery"),r("#boot-form").attr("action","/recovery.json"))}(t),f(),Tt(t),function(t){var e="",n="";if(void 0!==t.bt_status&&void 0!==t.bt_sub_status){var a=A[t.bt_status].sub[t.bt_sub_status];a?(e=E[a],n=A[t.bt_status].desc):(e=E.bt_connected,n="Output status")}r("#o_type").attr("title",n),r("#o_bt").html(M()?e.label:e.text)}(t),R.EventTargetStatus(t),t.depth&&(16==t.depth?r("#cmd_opt_R").show():r("#cmd_opt_R").hide()),t.project_name&&""!==t.project_name&&(et=t.project_name),t.platform_name&&""!==t.platform_name&&(at=t.platform_name),""===nt&&(nt=et),""===nt&&(nt="Squeezelite-ESP32"),t.version&&""!==t.version?($=t.version,r("#navtitle").html("".concat(nt).concat(W?"
[recovery]":"")),r("span#foot-fw").html("fw: ".concat($,", mode: ").concat(W?"Recovery":et,""))):r("span#flash-status").html(""),t.Voltage){var n=function(t){for(var e=0,n=O;e
'.concat(e.help.encodeHTML().replace(/\n/g,"
"),'
'),e.argtable&&e.argtable.forEach((function(n){var a=n.datatype||"",s=e.name+"-"+n.longopts,i=Ot(t,e.name,n.longopts),c="hasvalue="+n.hasvalue+" ";c+='longopts="'+n.longopts+'" ',c+='shortopts="'+n.shortopts+'" ',c+="checkbox="+n.checkbox+" ",c+='cmdname="'+e.name+'" ',c+='id="'+s+'" name="'+s+'" hasvalue="'+n.hasvalue+'" ';var r=n.mincount>0?"bg-success":"";"hidden"===n.glossary&&(c+=' style="visibility: hidden;"'),n.checkbox?o+='
"):(o+='
"),a.includes("|")?(r=a.startsWith("+")?" multiple ":"",a=a.replace("<","").replace("=","").replace(">",""),o+=""):o+='")),o+="".concat(n.checkbox?"
":"",'Previous value: ').concat(n.checkbox?i?"Checked":"Unchecked":i||"","").concat(n.checkbox?"":"
")})),o+='
\n '),o+=a?'\n'):''),o+="
",a?r(s).append(o):r("#commands-list").append(o)}})),r(".sclk").off("click").on("click",(function(){runCommand(this,!1)})),r(".cclk").off("click").on("click",(function(){runCommand(this,!0)})),t.commands.forEach((function(e){r("[cmdname="+e.name+"]:input").val(""),r("[cmdname="+e.name+"]:checkbox").prop("checked",!1),e.argtable&&e.argtable.forEach((function(n){var a="#"+e.name+"-"+n.longopts,s=Ot(t,e.name,n.longopts);n.checkbox?r(a)[0].checked=s:(void 0!==s&&r(a).val(s).trigger("change"),0===r(a)[0].value.length&&(n.datatype||"").includes("|")&&(r(a)[0].value="--"))}))})),0!=r("#cfg-hw-preset-model_config").length&&(C||(C=!0,r("#cfg-hw-preset-model_config").html(""),r.getJSON("https://gist.githubusercontent.com/sle118/dae585e157b733a639c12dc70f0910c5/raw/",{_:(new Date).getTime()},(function(t){r.each(t,(function(t,e){r("#cfg-hw-preset-model_config").append("")),""!==st&&st==e.name&&r("#cfg-hw-preset-model_config").val(st)})),""!==st&&"#prev_preset".show().val(st)})).fail((function(t,e,n){var a=e+", "+n;console.log("Request Failed: "+a)}))))})).fail((function(t,e,n){404==t.status?r(".orec").hide():L(t,0,n),r("#commands-list").empty()}))}function kt(){r.ajaxSetup({timeout:7e3}),r.getJSON("/config.json",(function(t){r("#nvsTable tr").remove();var e=t.config?t.config:t;V=e,B="",Object.keys(e).sort().forEach((function(t){var n=e[t].value;"autoexec"===t?"0"===e.autoexec.value?r("#disable-squeezelite")[0].checked=!0:r("#disable-squeezelite")[0].checked=!1:"autoexec1"===t?function(t){var e=j(t);e.output.toUpperCase().startsWith("I2S")?U("i2s"):e.output.toUpperCase().startsWith("SPDIF")?U("spdif"):e.output.toUpperCase().startsWith("BT")&&(e.otherOptions.btname&&(B=e.otherOptions.btname),U("bt"));if(Object.keys(e.options).forEach((function(t){var n=e.options[t];r("#cmd_opt_".concat(t)).hasOwnProperty("checked")?r("#cmd_opt_".concat(t))[0].checked=n:r("#cmd_opt_".concat(t)).val(n)})),e.options.hasOwnProperty("u")){var n=e.options.u.split(":"),a=(0,s.Z)(n,2),o=a[0],i=a[1];r("#resample_".concat(o)).prop("checked",!0),i&&r("#resample_i").prop("checked",!0)}}(n):"host_name"===t?(n=n.replaceAll('"',""),r("input#dhcp-name1").val(n),r("input#dhcp-name2").val(n),0==r("#cmd_opt_n").length&&r("#cmd_opt_n").val(n),document.title=n,X=n):"rel_api"===t?J=n:"enable_airplay"===t?r("#s_airplay").css({display:m(n)?"inline":"none"}):"enable_cspot"===t?r("#s_cspot").css({display:m(n)?"inline":"none"}):"preset_name"==t?st=n:"board_model"==t&&(nt=n),r("tbody#nvsTable").append(""+t+""),r("input#"+t).val(e[t].value)})),B.length>0&&r("#cfg-audio-bt_source-sink_name").val(B),r("tbody#nvsTable").append(""),t.gpio?(r("#pins").show(),r("tbody#gpiotable tr").remove(),t.gpio.forEach((function(t){r("tbody#gpiotable").append("'+t.group+""+t.name+""+t.gpio+""+(t.fixed?"Fixed":"Configuration")+"")}))):r("#pins").hide()})).fail((function(t,e,n){L(t,0,n)}))}function xt(t,e){Nt({message:t,type:e},new Date)}function Nt(t,e){var n="table-success";"MESSAGING_WARNING"===t.type?(n="table-warning","MESSAGING_INFO"===Z&&(Z="MESSAGING_WARNING")):"MESSAGING_ERROR"===t.type&&("MESSAGING_INFO"!==Z&&"MESSAGING_WARNING"!==Z||(Z="MESSAGING_ERROR"),n="table-danger"),++z>0&&(r("#msgcnt").removeClass("badge-success"),r("#msgcnt").removeClass("badge-warning"),r("#msgcnt").removeClass("badge-danger"),r("#msgcnt").addClass(k[Z]),r("#msgcnt").text(z)),r("#syslogTable").append(""+e.toLocalShort()+""+t.message.encodeHTML()+"")}function Rt(t){return new h((function(e){return setTimeout(e,t)}))}h.prototype.delay=function(t){return this.then((function(e){return new h((function(n){setTimeout((function(){n(e)}),t)}))}),(function(e){return new h((function(n,a){setTimeout((function(){a(e)}),t)}))}))},window.saveAutoexec1=function(t){F("cfg-audio-tmpl","MESSAGING_INFO","Saving.\n",!1);var e="".concat("squeezelite "," -o ").concat(Q," ");r(".sqcmd").each((function(){var t=p(r(this)),n=t.opt,a=t.val;if(n&&n.length>0&&"boolean"==typeof a||a.length>0){var s=":"===n?n:" -".concat(n," ");a="boolean"==typeof a?"":a,e+="".concat(s," ").concat(a)}}));var n=r("#cmd_opt_R input[name=resample]:checked");n.length>0&&""!==n.attr("suffix")&&(e+=n.attr("suffix"),r("#resample_i").is(":checked")&&"true"==n.attr("aint")&&(e+=r("#resample_i").attr("suffix"))),"bt"===Q&&F("cfg-audio-tmpl","MESSAGING_INFO","Remember to configure the Bluetooth audio device name.\n",!0),e+=function(t){for(var e=" ",n=0,a=Object.entries(t);n8&&(r(this).val().startsWith("http://")||r(this).val().startsWith("https://"))?r("#start-flash").show():r("#start-flash").hide()})),r(".upSrch").on("input",(function(){var t=this.value;r("#rTable tr").removeClass(this.id+"_hide"),t.length>0&&r("#rTable td:nth-child(".concat(r(this).parent().index()+1,")")).filter((function(){return!r(this).text().toUpperCase().includes(t.toUpperCase())})).parent().addClass(this.id+"_hide"),r('[class*="_hide"]').hide(),r("#rTable tr").not('[class*="_hide"]').show()})),setTimeout(mt,1500),r("#options input").on("input",(function(){var t=p(this),e=t.opt,n=t.val;if("c"===e||"e"===e){"cmd_opt_".concat(e,"_codec-error");var a=n.split(",").map((function(t){return t.trim()})).filter((function(t){return!Y.codecs.includes(t)}));pt(e,a.length>0?"Invalid codec(s) ".concat(a.join(", ")):"")}if("m"===e){pt(e,/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/.test(n)?"":"Invalid MAC address")}if("r"===e){pt(e,/^(\d+\.?\d*|\.\d+)-(\d+\.?\d*|\.\d+)$|^(\d+\.?\d*)$|^(\d+\.?\d*,)+\d+\.?\d*$/.test(n)?"":"Invalid rate(s) ".concat(n,". Acceptable format: |-|,,"))}})),r("#WifiConnectDialog")[0].addEventListener("shown.bs.modal",(function(t){r("*[class*='connecting']").hide(),null!=t&&t.relatedTarget&&(ct.Action=lt.CONN,r(t.relatedTarget).children("td:eq(1)").text()==it.ssid?ct.Action=lt.STS:r(t.relatedTarget).is(":last-child")?(ct.Action=lt.MAN,ct.ssid="",r("#manual_ssid").val(ct.ssid)):(ct.ssid=r(t.relatedTarget).children("td:eq(1)").text(),r("#manual_ssid").val(ct.ssid))),ct.Action!==lt.STS?(r(".connecting-init").show(),r("#manual_ssid").trigger("focus")):yt()})),r("#WifiConnectDialog")[0].addEventListener("hidden.bs.modal",(function(){r("#WifiConnectDialog input").val("")})),r("#uCnfrm")[0].addEventListener("shown.bs.modal",(function(){r("#selectedFWURL").text(r("#fw-url-input").val())})),r("input#show-commands")[0].checked=1===K,r('a[href^="#tab-commands"]').hide(),r("#load-nvs").on("click",(function(){r("#nvsfilename").trigger("click")})),r("#nvsfilename").on("change",(function(){if("function"!=typeof window.FileReader)throw"The file API isn't supported on this browser.";if(!this.files)throw"This browser does not support the `files` property of the file input.";if(this.files[0]){var t=this.files[0],e=new FileReader;e.onload=function(t){var e={};try{e=JSON.parse(t.target.result)}catch(t){alert("Parsing failed!\r\n "+t)}r("input.nvs").each((function(t,n){r(this).parent().removeClass("bg-warning").removeClass("bg-success"),e[n.id]&&(e[n.id]!==n.value?(console.log("Changed "+n.id+" "+n.value+"==>"+e[n.id]),r(this).parent().addClass("bg-warning"),r(this).val(e[n.id])):r(this).parent().addClass("bg-success"))})),r("input.nvs").children(".bg-warning")&&alert("Highlighted values were changed. Press Commit to change on the device")},e.readAsText(t),this.value=null}})),r("#clear-syslog").on("click",(function(){z=0,Z="MESSAGING_INFO",r("#msgcnt").text(""),r("#syslogTable").html("")})),r("#ok-credits").on("click",(function(){r("#credits").slideUp("fast",(function(){})),r("#app").slideDown("fast",(function(){}))})),r("#acredits").on("click",(function(t){t.preventDefault(),r("#app").slideUp("fast",(function(){})),r("#credits").slideDown("fast",(function(){}))})),r("input#show-commands").on("click",(function(){this.checked=this.checked?1:0,this.checked?(r('a[href^="#tab-commands"]').show(),K=1):(K=0,r('a[href^="#tab-commands"]').hide())})),r("input#show-nvs").on("click",(function(){this.checked=this.checked?1:0,c.Z.set("show-nvs",this.checked?"Y":"N"),f()})),r("#btn_reboot_recovery").on("click",(function(){handleReboot("recovery")})),r("#btn_reboot").on("click",(function(){handleReboot("reboot")})),r("#btn_flash").on("click",(function(){hFlash()})),r("#save-autoexec1").on("click",(function(){saveAutoexec1(!1)})),r("#commit-autoexec1").on("click",(function(){saveAutoexec1(!0)})),r("#btn_disconnect").on("click",(function(){it={},bt(),r.ajax({url:"/connect.json",dataType:"text",method:"DELETE",cache:!1,contentType:"application/json; charset=utf-8",data:JSON.stringify({timestamp:Date.now()})})})),r("#btnJoin").on("click",(function(){handleConnect()})),r("#reboot_nav").on("click",(function(){handleReboot("reboot")})),r("#reboot_ota_nav").on("click",(function(){handleReboot("reboot_ota")})),r("#save-as-nvs").on("click",(function(){var t=ut(!0),e=document.createElement("a");e.href=URL.createObjectURL(new Blob([JSON.stringify(t,null,2)],{type:"text/plain"})),e.setAttribute("download","nvs_config_"+X+"_"+Date.now()+"json"),document.body.appendChild(e),e.click(),document.body.removeChild(e)})),r("#save-nvs").on("click",(function(){G(ut(!1))})),r("#fwUpload").on("click",(function(){0===document.getElementById("flashfilename").files.length?alert("No file selected!"):(r("#fw-url-input").value=null,R.StartOTA())})),r("[name=output-tmpl]").on("click",(function(){U(this.id)})),r("#chkUpdates").on("click",(function(){r("#rTable").html(""),r.getJSON(J,(function(t){var e=[];t.forEach((function(t){var n=t.name.split("#")[3];e.includes(n)||e.push(n)}));var n="";e.forEach((function(t){n+='"})),r("#fwbranch").append(n),t.forEach((function(t){var e="";t.assets.forEach((function(t){t.name.match(/\.bin$/)&&(e=t.browser_download_url)}));var n=t.name.split("#"),a=n[0],s=n[2],o=n[3],i=a.substr(a.lastIndexOf("-")+1);i="32"==i||"16"==i?i:"";var c=t.body;c=(c=(c=c.replace(/'/gi,'"')).replace(/[\s\S]+(### Revision Log[\s\S]+)### ESP-IDF Version Used[\s\S]+/,"$1")).replace(/- \(.+?\) /g,"- ").encodeHTML(),r("#rTable").append("\n ").concat(a,"").concat(new Date(t.created_at).toLocalShort(),"\n ").concat(s,"").concat(o,"").concat(i,""))})),r("#searchfw").css("display","inline"),ht(at)||ht(et),r("#rTable tr.release").on("click",(function(){var t=this.attributes.fwurl.value;D&&(t=t.replace(/.*\/download\//,D+"/plugins/SqueezeESP32/firmware/")),r("#fw-url-input").val(t),r("#start-flash").show(),r("#rTable tr.release").removeClass("table-success table-warning"),r(this).addClass("table-success table-warning")}))})).fail((function(){alert("failed to fetch release history!")}))})),r("#fwcheck").on("click",(function(){r("#releaseTable").html(""),r("#fwbranch").empty(),r.getJSON(J,(function(t){var e,n=0,a=[];t.forEach((function(t){var e=t.name.split("#")[3];a.includes(e)||a.push(e)})),a.forEach((function(t){e+='"})),r("#fwbranch").append(e),t.forEach((function(t){var e="";t.assets.forEach((function(t){t.name.match(/\.bin$/)&&(e=t.browser_download_url)}));var a=t.name.split("#"),s=a[0],o=a[1],i=a[2],c=a[3],l=t.body;l=(l=(l=l.replace(/'/gi,'"')).replace(/[\s\S]+(### Revision Log[\s\S]+)### ESP-IDF Version Used[\s\S]+/,"$1")).replace(/- \(.+?\) /g,"- ");var u=n++>6?" hide":"";r("#releaseTable").append(""+s+""+new Date(t.created_at).toLocalShort()+""+i+""+o+""+c+"")})),n>7&&(r("#releaseTable").append(""),r("#showallbutton").on("click",(function(){r("tr.hide").removeClass("hide"),r("tr#showall").addClass("hide")}))),r("#searchfw").css("display","inline")})).fail((function(){alert("failed to fetch release history!")}))})),r("#updateAP").on("click",(function(){mt(),console.log("refresh AP")})),kt(),At(),_t(),Et()})),window.setURL=function(t){var e=t.dataset.url;r('[data-bs-url^="http"]').addClass("btn-success").removeClass("btn-danger"),r('[data-bs-url="'+e+'"]').addClass("btn-danger").removeClass("btn-success"),D&&(e=e.replace(/.*\/download\//,D+"/plugins/SqueezeESP32/firmware/")),r("#fwurl").val(e)},window.runCommand=function(t,e){var n=t.attributes.cmdname.value;F(t.attributes.cmdname.value,"MESSAGING_INFO","Executing.",!1);var a=document.getElementById("flds-"+n),o=null==a?void 0:a.querySelectorAll("select,input");if("cfg-hw-preset"===n)return function(t,e){var n=JSON.parse(t[0].value),a=t[0].attributes.cmdname.value;console.log("selected model: ".concat(n.name));for(var o={timestamp:Date.now(),config:{model_config:{value:n.name,type:33}}},i=0,c=Object.entries(n.config);i{n.r(e)},607:(t,e,n)=>{n(138),n(393),n(861),n(322)},861:t=>{t.exports="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAb1BMVEXIycuswsKMjI4rqqZyc3RQlpQ6jIEmJifW2dq5ursppJ8Om4zC0NAFdGYmmpb///8Hg3O4x8cHkoEggX0jko5Ks6/P0dM5r6ocoZb3+PgiiYVevrp/y8bg4uOS09FtxMDs7+7M6um529qoysik2tiNn72gAAAAF3RSTlP94Fr/Wf39BP26/////////////////kibhL0AAAGjSURBVDjLbZMJkoMgEEWtmETEJWpkiSC45P5nnF4wk7HmW2jLfzYIdFYUxbXUYp5nIbTOUFoLAR2ivIKZFQXYuu6TahSHmdAlAqWub0/QNI1jSxrHacKeWw9EdtH1xHbbyiRgCJn67JqVAr9nO2fJnBDMoUuYEvsfmxnJBM66Zj8/iYmaAPKlOvRNJAC/fz8OefINEAngAbYPEMiHTJCCAZrACciVMpCCgDEBKwsAowymMO3IAP3Btqa5vYJx0ZlcOSUZaE/AWznvnTHOyfZ/wMUQvAIg/wb27QNEH94BgGj+APsZiF8AXAhQQEMwkIYYLW7xvsENoyUoF0I0ysf0F2O743kDQNXzXM8+j8Eb6byzDEz7gtpsO1PgrXG5Nd6btNTP+YXarKTny1uQ9JiAN6vbqT9au+BzMQjAWtlq6BiYttdjiVVVqfXxWFWFkk6Cz0DTdYOFPmpHAAK/YQCJoTppQJ8A3TAxVAAhR439Bg5tKe7NgSDEje3mDsf+ovuGCUbYZb/BwoHS6ykHMYfo/U6lx8Xb/+qo3U/x/lf+VP9c/j9c3zy20WEMxgAAAABJRU5ErkJggg=="}},n={};function a(t){var s=n[t];if(void 0!==s)return s.exports;var o=n[t]={id:t,loaded:!1,exports:{}};return e[t].call(o.exports,o,o.exports,a),o.loaded=!0,o.exports}a.m=e,t=[],a.O=(e,n,s,o)=>{if(!n){var i=1/0;for(u=0;u=o)&&Object.keys(a.O).every((t=>a.O[t](n[r])))?n.splice(r--,1):(c=!1,o0&&t[u-1][2]>o;u--)t[u]=t[u-1];t[u]=[n,s,o]},a.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return a.d(e,{a:e}),e},a.d=(t,e)=>{for(var n in e)a.o(e,n)&&!a.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},a.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),a.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),a.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},a.nmd=t=>(t.paths=[],t.children||(t.children=[]),t),(()=>{var t={826:0};a.O.j=e=>0===t[e];var e=(e,n)=>{var s,o,[i,c,r]=n,l=0;if(i.some((e=>0!==t[e]))){for(s in c)a.o(c,s)&&(a.m[s]=c[s]);if(r)var u=r(a)}for(e&&e(n);la(607)));s=a.O(s)})(); -//# sourceMappingURL=index.997af2.bundle.js.map \ No newline at end of file +//# sourceMappingURL=index.1e1c60.bundle.js.map \ No newline at end of file diff --git a/components/wifi-manager/webapp/dist/js/index.997af2.bundle.js.gz b/components/wifi-manager/webapp/dist/js/index.1e1c60.bundle.js.gz similarity index 99% rename from components/wifi-manager/webapp/dist/js/index.997af2.bundle.js.gz rename to components/wifi-manager/webapp/dist/js/index.1e1c60.bundle.js.gz index 54aeff11..f10093f2 100644 Binary files a/components/wifi-manager/webapp/dist/js/index.997af2.bundle.js.gz and b/components/wifi-manager/webapp/dist/js/index.1e1c60.bundle.js.gz differ diff --git a/components/wifi-manager/webapp/dist/js/index.997af2.bundle.js.map b/components/wifi-manager/webapp/dist/js/index.1e1c60.bundle.js.map similarity index 99% rename from components/wifi-manager/webapp/dist/js/index.997af2.bundle.js.map rename to components/wifi-manager/webapp/dist/js/index.1e1c60.bundle.js.map index a1668af3..0bc42ab0 100644 --- a/components/wifi-manager/webapp/dist/js/index.997af2.bundle.js.map +++ b/components/wifi-manager/webapp/dist/js/index.1e1c60.bundle.js.map @@ -1 +1 @@ -{"version":3,"file":"./js/index.997af2.bundle.js","mappings":"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","sources":["webpack://squeezelite-esp32/webpack/runtime/chunk loaded","webpack://squeezelite-esp32/./src/js/custom.js","webpack://squeezelite-esp32/./src/index.ts","webpack://squeezelite-esp32/webpack/bootstrap","webpack://squeezelite-esp32/webpack/runtime/compat get default export","webpack://squeezelite-esp32/webpack/runtime/define property getters","webpack://squeezelite-esp32/webpack/runtime/global","webpack://squeezelite-esp32/webpack/runtime/hasOwnProperty shorthand","webpack://squeezelite-esp32/webpack/runtime/make namespace object","webpack://squeezelite-esp32/webpack/runtime/node module decorator","webpack://squeezelite-esp32/webpack/runtime/jsonp chunk loading","webpack://squeezelite-esp32/webpack/startup"],"sourcesContent":["var deferred = [];\n__webpack_require__.O = (result, chunkIds, fn, priority) => {\n\tif(chunkIds) {\n\t\tpriority = priority || 0;\n\t\tfor(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1];\n\t\tdeferred[i] = [chunkIds, fn, priority];\n\t\treturn;\n\t}\n\tvar notFulfilled = Infinity;\n\tfor (var i = 0; i < deferred.length; i++) {\n\t\tvar [chunkIds, fn, priority] = deferred[i];\n\t\tvar fulfilled = true;\n\t\tfor (var j = 0; j < chunkIds.length; j++) {\n\t\t\tif ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every((key) => (__webpack_require__.O[key](chunkIds[j])))) {\n\t\t\t\tchunkIds.splice(j--, 1);\n\t\t\t} else {\n\t\t\t\tfulfilled = false;\n\t\t\t\tif(priority < notFulfilled) notFulfilled = priority;\n\t\t\t}\n\t\t}\n\t\tif(fulfilled) {\n\t\t\tdeferred.splice(i--, 1)\n\t\t\tvar r = fn();\n\t\t\tif (r !== undefined) result = r;\n\t\t}\n\t}\n\treturn result;\n};","var he = require('he');\nvar Promise = require('es6-promise').Promise;\nwindow.bootstrap = require('bootstrap');\nimport Cookies from 'js-cookie';\n\n\n\nif (!String.prototype.format) {\n Object.assign(String.prototype, {\n format() {\n const args = arguments;\n return this.replace(/{(\\d+)}/g, function (match, number) {\n return typeof args[number] !== 'undefined' ? args[number] : match;\n });\n },\n });\n}\nif (!String.prototype.encodeHTML) {\n Object.assign(String.prototype, {\n encodeHTML() {\n return he.encode(this).replace(/\\n/g, '
');\n },\n });\n}\nObject.assign(Date.prototype, {\n toLocalShort() {\n const opt = { dateStyle: 'short', timeStyle: 'short' };\n return this.toLocaleString(undefined, opt);\n },\n});\nfunction get_control_option_value(obj) {\n let ctrl,id,val,opt;\n let radio = false;\n let checked = false;\n if (typeof (obj) === 'string') {\n id = obj;\n ctrl = $(`#${id}`);\n } else {\n id = $(obj).attr('id');\n ctrl = $(obj);\n }\n if(ctrl.attr('type') === 'checkbox'){\n opt = $(obj).checked?id.replace('cmd_opt_', ''):'';\n val = true;\n }\n else {\n opt = id.replace('cmd_opt_', '');\n val = $(obj).val();\n val = `${val.includes(\" \") ? '\"' : ''}${val}${val.includes(\" \") ? '\"' : ''}`;\n }\n\n return { opt, val };\n}\nfunction handleNVSVisible() {\n let nvs_previous_checked = isEnabled(Cookies.get(\"show-nvs\"));\n $('input#show-nvs')[0].checked = nvs_previous_checked;\n if ($('input#show-nvs')[0].checked || recovery) {\n $('*[href*=\"-nvs\"]').show();\n } else {\n $('*[href*=\"-nvs\"]').hide();\n }\n}\nfunction concatenateOptions(options) {\n let commandLine = ' ';\n for (const [option, value] of Object.entries(options)) {\n if (option !== 'n' && option !== 'o') {\n commandLine += `-${option} `;\n if (value !== true) {\n commandLine += `${value} `;\n }\n }\n }\n return commandLine;\n}\n\nfunction isEnabled(val) {\n return val != undefined && typeof val === 'string' && val.match(\"[Yy1]\");\n}\n\nconst nvsTypes = {\n NVS_TYPE_U8: 0x01,\n /*! < Type uint8_t */\n NVS_TYPE_I8: 0x11,\n /*! < Type int8_t */\n NVS_TYPE_U16: 0x02,\n /*! < Type uint16_t */\n NVS_TYPE_I16: 0x12,\n /*! < Type int16_t */\n NVS_TYPE_U32: 0x04,\n /*! < Type uint32_t */\n NVS_TYPE_I32: 0x14,\n /*! < Type int32_t */\n NVS_TYPE_U64: 0x08,\n /*! < Type uint64_t */\n NVS_TYPE_I64: 0x18,\n /*! < Type int64_t */\n NVS_TYPE_STR: 0x21,\n /*! < Type string */\n NVS_TYPE_BLOB: 0x42,\n /*! < Type blob */\n NVS_TYPE_ANY: 0xff /*! < Must be last */,\n};\nconst btIcons = {\n bt_playing: { 'label': '', 'icon': 'media_bluetooth_on' },\n bt_disconnected: { 'label': '', 'icon': 'media_bluetooth_off' },\n bt_neutral: { 'label': '', 'icon': 'bluetooth' },\n bt_connecting: { 'label': '', 'icon': 'bluetooth_searching' },\n bt_connected: { 'label': '', 'icon': 'bluetooth_connected' },\n bt_disabled: { 'label': '', 'icon': 'bluetooth_disabled' },\n play_arrow: { 'label': '', 'icon': 'play_circle_filled' },\n pause: { 'label': '', 'icon': 'pause_circle' },\n stop: { 'label': '', 'icon': 'stop_circle' },\n '': { 'label': '', 'icon': '' }\n};\nconst batIcons = [\n { icon: \"battery_0_bar\", label: 'â–Ē', ranges: [{ f: 5.8, t: 6.8 }, { f: 8.8, t: 10.2 }] },\n { icon: \"battery_2_bar\", label: 'â–Ēâ–Ē', ranges: [{ f: 6.8, t: 7.4 }, { f: 10.2, t: 11.1 }] },\n { icon: \"battery_3_bar\", label: 'â–Ēâ–Ēâ–Ē', ranges: [{ f: 7.4, t: 7.5 }, { f: 11.1, t: 11.25 }] },\n { icon: \"battery_4_bar\", label: 'â–Ēâ–Ēâ–Ēâ–Ē', ranges: [{ f: 7.5, t: 7.8 }, { f: 11.25, t: 11.7 }] }\n];\nconst btStateIcons = [\n { desc: 'Idle', sub: ['bt_neutral'] },\n { desc: 'Discovering', sub: ['bt_connecting'] },\n { desc: 'Discovered', sub: ['bt_connecting'] },\n { desc: 'Unconnected', sub: ['bt_disconnected'] },\n { desc: 'Connecting', sub: ['bt_connecting'] },\n {\n desc: 'Connected',\n sub: ['bt_connected', 'play_arrow', 'bt_playing', 'pause', 'stop'],\n },\n { desc: 'Disconnecting', sub: ['bt_disconnected'] },\n];\n\nconst pillcolors = {\n MESSAGING_INFO: 'badge-success',\n MESSAGING_WARNING: 'badge-warning',\n MESSAGING_ERROR: 'badge-danger',\n};\nconst connectReturnCode = {\n OK: 0,\n FAIL: 1,\n DISC: 2,\n LOST: 3,\n RESTORE: 4,\n ETH: 5\n}\nconst taskStates = {\n 0: 'eRunning',\n /*! < A task is querying the state of itself, so must be running. */\n 1: 'eReady',\n /*! < The task being queried is in a read or pending ready list. */\n 2: 'eBlocked',\n /*! < The task being queried is in the Blocked state. */\n 3: 'eSuspended',\n /*! < The task being queried is in the Suspended state, or is in the Blocked state with an infinite time out. */\n 4: 'eDeleted',\n};\nlet flashState = {\n NONE: 0,\n REBOOT_TO_RECOVERY: 2,\n SET_FWURL: 5,\n FLASHING: 6,\n DONE: 7,\n UPLOADING: 8,\n ERROR: 9,\n UPLOADCOMPLETE: 10,\n _state: -1,\n olderRecovery: false,\n statusText: '',\n flashURL: '',\n flashFileName: '',\n statusPercent: 0,\n Completed: false,\n recovery: false,\n prevRecovery: false,\n updateModal: new bootstrap.Modal(document.getElementById('otadiv'), {}),\n reset: function () {\n\n this.olderRecovery = false;\n this.statusText = '';\n this.statusPercent = -1;\n this.flashURL = '';\n this.flashFileName = undefined;\n this.UpdateProgress();\n $('#rTable tr.release').removeClass('table-success table-warning');\n $('.flact').prop('disabled', false);\n $('#flashfilename').value = null;\n $('#fw-url-input').value = null;\n if (!this.isStateError()) {\n $('span#flash-status').html('');\n $('#fwProgressLabel').parent().removeClass('bg-danger');\n }\n this._state = this.NONE\n return this;\n },\n isStateUploadComplete: function () {\n return this._state == this.UPLOADCOMPLETE;\n },\n isStateError: function () {\n return this._state == this.ERROR;\n },\n isStateNone: function () {\n return this._state == this.NONE;\n },\n isStateRebootRecovery: function () {\n return this._state == this.REBOOT_TO_RECOVERY;\n },\n isStateSetUrl: function () {\n return this._state == this.SET_FWURL;\n },\n isStateFlashing: function () {\n return this._state == this.FLASHING;\n },\n isStateDone: function () {\n return this._state == this.DONE;\n },\n isStateUploading: function () {\n return this._state == this.UPLOADING;\n },\n init: function () {\n this._state = this.NONE;\n return this;\n },\n\n SetStateError: function () {\n this._state = this.ERROR;\n $('#fwProgressLabel').parent().addClass('bg-danger');\n return this;\n },\n SetStateNone: function () {\n this._state = this.NONE;\n return this;\n },\n SetStateRebootRecovery: function () {\n this._state = this.REBOOT_TO_RECOVERY;\n // Reboot system to recovery mode\n this.SetStatusText('Starting recovery mode.')\n $.ajax({\n url: '/recovery.json',\n context: this,\n dataType: 'text',\n method: 'POST',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify({\n timestamp: Date.now(),\n }),\n error: function (xhr, _ajaxOptions, thrownError) {\n this.setOTAError(`Unexpected error while trying to restart to recovery. (status=${xhr.status ?? ''}, error=${thrownError ?? ''} ) `);\n },\n complete: function (response) {\n this.SetStatusText('Waiting for system to boot.')\n },\n });\n return this;\n },\n SetStateSetUrl: function () {\n this._state = this.SET_FWURL;\n this.statusText = 'Sending firmware download location.';\n let confData = {\n fwurl: {\n value: this.flashURL,\n type: 33,\n }\n };\n post_config(confData);\n return this;\n },\n SetStateFlashing: function () {\n this._state = this.FLASHING;\n return this;\n },\n SetStateDone: function () {\n this._state = this.DONE;\n this.reset();\n return this;\n },\n SetStateUploading: function () {\n this._state = this.UPLOADING;\n return this.SetStatusText('Sending file to device.');\n },\n SetStateUploadComplete: function () {\n this._state = this.UPLOADCOMPLETE;\n return this;\n },\n\n isFlashExecuting: function () {\n return true === (this._state != this.UPLOADING && (this.statusText !== '' || this.statusPercent >= 0));\n },\n\n\n\n toString: function () {\n let keys = Object.keys(this);\n return keys.find(x => this[x] === this._state);\n },\n\n setOTATargets: function () {\n this.flashURL = '';\n this.flashFileName = '';\n this.flashURL = $('#fw-url-input').val();\n let fileInput = $('#flashfilename')[0].files;\n if (fileInput.length > 0) {\n this.flashFileName = fileInput[0];\n }\n if (this.flashFileName.length == 0 && this.flashURL.length == 0) {\n this.setOTAError('Invalid url or file. Cannot start OTA');\n }\n return this;\n },\n\n setOTAError: function (message) {\n this.SetStateError().SetStatusPercent(0).SetStatusText(message).reset();\n return this;\n },\n\n ShowDialog: function () {\n if (!this.isStateNone()) {\n this.updateModal.show();\n $('.flact').prop('disabled', true);\n }\n return this;\n },\n\n SetStatusPercent: function (pct) {\n var pctChanged = (this.statusPercent != pct);\n this.statusPercent = pct;\n if (pctChanged) {\n if (!this.isStateUploading() && !this.isStateFlashing()) {\n this.SetStateFlashing();\n }\n if (pct == 100) {\n if (this.isStateFlashing()) {\n this.SetStateDone();\n }\n else if (this.isStateUploading()) {\n this.statusPercent = 0;\n this.SetStateFlashing();\n }\n }\n this.UpdateProgress().ShowDialog();\n }\n return this;\n },\n SetStatusText: function (txt) {\n var changed = (this.statusText != txt);\n this.statusText = txt;\n if (changed) {\n $('span#flash-status').html(this.statusText);\n this.ShowDialog();\n }\n\n return this;\n },\n UpdateProgress: function () {\n $('.progress-bar')\n .css('width', this.statusPercent + '%')\n .attr('aria-valuenow', this.statusPercent)\n .text(this.statusPercent + '%')\n $('.progress-bar').html((this.isStateDone() ? 100 : this.statusPercent) + '%');\n return this;\n },\n StartOTA: function () {\n this.logEvent(this.StartOTA.name);\n $('#fwProgressLabel').parent().removeClass('bg-danger');\n this.setOTATargets();\n if (this.isStateError()) {\n return this;\n }\n if (!recovery) {\n this.SetStateRebootRecovery();\n }\n else {\n this.SetStateFlashing().TargetReadyStartOTA();\n }\n\n return this;\n },\n UploadLocalFile: function () {\n this.SetStateUploading();\n const xhttp = new XMLHttpRequest();\n xhttp.context = this;\n var boundHandleUploadProgressEvent = this.HandleUploadProgressEvent.bind(this);\n var boundsetOTAError = this.setOTAError.bind(this);\n xhttp.upload.addEventListener(\"progress\", boundHandleUploadProgressEvent, false);\n xhttp.onreadystatechange = function () {\n if (xhttp.readyState === 4) {\n if (xhttp.status === 0 || xhttp.status === 404) {\n boundsetOTAError(`Upload Failed. Recovery version might not support uploading. Please use web update instead.`);\n }\n }\n };\n xhttp.open('POST', '/flash.json', true);\n xhttp.send(this.flashFileName);\n },\n TargetReadyStartOTA: function () {\n if (recovery && this.prevRecovery && !this.isStateRebootRecovery() && !this.isStateFlashing()) {\n // this should only execute once, while being in a valid state\n return this;\n }\n\n this.logEvent(this.TargetReadyStartOTA.name);\n if (!recovery) {\n console.error('Event TargetReadyStartOTA fired in the wrong mode ');\n return this;\n }\n this.prevRecovery = true;\n\n if (this.flashFileName !== '') {\n this.UploadLocalFile();\n }\n else if (this.flashURL != '') {\n this.SetStateSetUrl();\n }\n else {\n this.setOTAError('Invalid URL or file name while trying to start the OTa process')\n }\n },\n HandleUploadProgressEvent: function (data) {\n this.logEvent(this.HandleUploadProgressEvent.name);\n this.SetStateUploading().SetStatusPercent(Math.round(data.loaded / data.total * 100)).SetStatusText('Uploading file to device');\n },\n EventTargetStatus: function (data) {\n if (!this.isStateNone()) {\n this.logEvent(this.EventTargetStatus.name);\n }\n if (data.ota_pct ?? -1 >= 0) {\n this.olderRecovery = true;\n this.SetStatusPercent(data.ota_pct);\n }\n if ((data.ota_dsc ?? '') != '') {\n this.olderRecovery = true;\n this.SetStatusText(data.ota_dsc);\n }\n\n if (data.recovery != undefined) {\n this.recovery = data.recovery === 1 ? true : false;\n }\n if (this.isStateRebootRecovery() && this.recovery) {\n this.TargetReadyStartOTA();\n }\n },\n EventOTAMessageClass: function (data) {\n this.logEvent(this.EventOTAMessageClass.name);\n var otaData = JSON.parse(data);\n this.SetStatusPercent(otaData.ota_pct).SetStatusText(otaData.ota_dsc);\n },\n logEvent: function (fun) {\n console.log(`${fun}, flash state ${this.toString()}, recovery: ${this.recovery}, ota pct: ${this.statusPercent}, ota desc: ${this.statusText}`);\n }\n\n};\nwindow.hideSurrounding = function (obj) {\n $(obj).parent().parent().hide();\n}\n\nlet presetsloaded = false;\nlet is_i2c_locked = false;\nlet statusInterval = 2000;\nlet messageInterval = 2500;\nfunction post_config(data) {\n let confPayload = {\n timestamp: Date.now(),\n config: data\n };\n $.ajax({\n url: '/config.json',\n dataType: 'text',\n method: 'POST',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify(confPayload),\n error: handleExceptionResponse,\n });\n}\n\n\nwindow.hFlash = function () {\n // reset file upload selection if any;\n $('#flashfilename').value = null\n flashState.StartOTA();\n}\nwindow.handleReboot = function (link) {\n if (link == 'reboot_ota') {\n $('#reboot_ota_nav').removeClass('active').prop(\"disabled\", true); delayReboot(500, '', 'reboot_ota');\n }\n else {\n $('#reboot_nav').removeClass('active'); delayReboot(500, '', link);\n }\n}\n\nfunction parseSqueezeliteCommandLine(commandLine) {\n const options = {};\n let output, name;\n let otherValues = '';\n\n const argRegex = /(\"[^\"]+\"|'[^']+'|\\S+)/g;\n const args = commandLine.match(argRegex);\n\n let i = 0;\n\n while (i < args.length) {\n const arg = args[i];\n\n if (arg.startsWith('-')) {\n const option = arg.slice(1);\n\n if (option === '') {\n otherValues += args.slice(i).join(' ');\n break;\n }\n\n let value = true;\n\n if (i + 1 < args.length && !args[i + 1].startsWith('-')) {\n value = args[i + 1].replace(/\"/g, '').replace(/'/g, '');\n i++;\n }\n\n options[option] = value;\n } else {\n otherValues += arg + ' ';\n }\n\n i++;\n }\n\n otherValues = otherValues.trim();\n output = getOutput(options);\n name = getName(options);\n let otherOptions={btname:null,n:null};\n // assign o and n options to otheroptions if present\n if (options.o && output.toUpperCase() === 'BT') {\n let temp = parseSqueezeliteCommandLine(options.o);\n if(temp.name) {\n otherOptions.btname = temp.name;\n }\n delete options.o;\n }\n if (options.n) {\n otherOptions['n'] = options.n;\n delete options.n;\n }\n return { name, output, options, otherValues,otherOptions }; \n}\n\nfunction getOutput(options) {\n let output;\n if (options.o){\n output = options.o.replace(/\"/g, '').replace(/'/g, '');\n /* set output as the first alphanumerical word in the command line */\n if (output.indexOf(' ') > 0) {\n output = output.substring(0, output.indexOf(' '));\n }\n }\n return output;\n}\n\nfunction getName(options) {\n let name;\n /* if n option present, assign to name variable */\n if (options.n){\n name = options.n.replace(/\"/g, '').replace(/'/g, '');\n }\n return name;\n}\n\n\nfunction isConnected() {\n return ConnectedTo.hasOwnProperty('ip') && ConnectedTo.ip != '0.0.0.0' && ConnectedTo.ip != '';\n}\nfunction getIcon(icons) {\n return isConnected() ? icons.icon : icons.label;\n}\nfunction handlebtstate(data) {\n let icon = '';\n let tt = '';\n if (data.bt_status !== undefined && data.bt_sub_status !== undefined) {\n const iconindex = btStateIcons[data.bt_status].sub[data.bt_sub_status];\n if (iconindex) {\n icon = btIcons[iconindex];\n tt = btStateIcons[data.bt_status].desc;\n } else {\n icon = btIcons.bt_connected;\n tt = 'Output status';\n }\n }\n\n $('#o_type').attr('title', tt);\n $('#o_bt').html(isConnected() ? icon.label : icon.text);\n}\nfunction handleTemplateTypeRadio(outtype) {\n $('#o_type').children('span').css({ display: 'none' });\n let changed = false;\n if (outtype === 'bt') {\n changed = output !== 'bt' && output !== '';\n output = 'bt';\n } else if (outtype === 'spdif') {\n changed = output !== 'spdif' && output !== '';\n output = 'spdif';\n } else {\n changed = output !== 'i2s' && output !== '';\n output = 'i2s';\n }\n $('#' + output).prop('checked', true);\n $('#o_' + output).css({ display: 'inline' });\n if (changed) {\n Object.keys(commandDefaults[output]).forEach(function (key) {\n $(`#cmd_opt_${key}`).val(commandDefaults[output][key]);\n });\n }\n}\n\nfunction handleExceptionResponse(xhr, _ajaxOptions, thrownError) {\n console.log(xhr.status);\n console.log(thrownError);\n if (thrownError !== '') {\n showLocalMessage(thrownError, 'MESSAGING_ERROR');\n }\n}\nfunction HideCmdMessage(cmdname) {\n $('#toast_' + cmdname)\n .removeClass('table-success')\n .removeClass('table-warning')\n .removeClass('table-danger')\n .addClass('table-success')\n .removeClass('show');\n $('#msg_' + cmdname).html('');\n}\nfunction showCmdMessage(cmdname, msgtype, msgtext, append = false) {\n let color = 'table-success';\n if (msgtype === 'MESSAGING_WARNING') {\n color = 'table-warning';\n } else if (msgtype === 'MESSAGING_ERROR') {\n color = 'table-danger';\n }\n $('#toast_' + cmdname)\n .removeClass('table-success')\n .removeClass('table-warning')\n .removeClass('table-danger')\n .addClass(color)\n .addClass('show');\n let escapedtext = msgtext\n .substring(0, msgtext.length - 1)\n .encodeHTML()\n .replace(/\\n/g, '
');\n escapedtext =\n ($('#msg_' + cmdname).html().length > 0 && append\n ? $('#msg_' + cmdname).html() + '
'\n : '') + escapedtext;\n $('#msg_' + cmdname).html(escapedtext);\n}\n\nlet releaseURL =\n 'https://api.github.com/repos/sle118/squeezelite-esp32/releases';\n\nlet recovery = false;\nlet messagesHeld = false;\nlet commandBTSinkName = '';\nconst commandHeader = 'squeezelite ';\nconst commandDefaults = {\n i2s: { b: \"500:2000\", C: \"30\", W: \"\", Z: \"96000\", o: \"I2S\" },\n spdif: { b: \"500:2000\", C: \"30\", W: \"\", Z: \"48000\", o: \"SPDIF\" },\n bt: { b: \"500:2000\", C: \"30\", W: \"\", Z: \"44100\", o: \"BT\" },\n};\nlet validOptions = {\n codecs: ['flac', 'pcm', 'mp3', 'ogg', 'aac', 'wma', 'alac', 'dsd', 'mad', 'mpg']\n};\n\n//let blockFlashButton = false;\nlet apList = null;\n//let selectedSSID = '';\n//let checkStatusInterval = null;\nlet messagecount = 0;\nlet messageseverity = 'MESSAGING_INFO';\nlet SystemConfig = {};\nlet LastCommandsState = null;\nvar output = '';\nlet hostName = '';\nlet versionName = 'Squeezelite-ESP32';\nlet prevmessage = '';\nlet project_name = versionName;\nlet depth = 16;\nlet board_model = '';\nlet platform_name = versionName;\nlet preset_name = '';\nlet btSinkNamesOptSel = '#cfg-audio-bt_source-sink_name';\nlet ConnectedTo = {};\nlet ConnectingToSSID = {};\nlet lmsBaseUrl;\nlet prevLMSIP = '';\nconst ConnectingToActions = {\n 'CONN': 0, 'MAN': 1, 'STS': 2,\n}\n\nPromise.prototype.delay = function (duration) {\n return this.then(\n function (value) {\n return new Promise(function (resolve) {\n setTimeout(function () {\n resolve(value);\n }, duration);\n });\n },\n function (reason) {\n return new Promise(function (_resolve, reject) {\n setTimeout(function () {\n reject(reason);\n }, duration);\n });\n }\n );\n};\n\nfunction getConfigJson(slimMode) {\n const config = {};\n $('input.nvs').each(function (_index, entry) {\n if (!slimMode) {\n const nvsType = parseInt(entry.attributes.nvs_type.value, 10);\n if (entry.id !== '') {\n config[entry.id] = {};\n if (\n nvsType === nvsTypes.NVS_TYPE_U8 ||\n nvsType === nvsTypes.NVS_TYPE_I8 ||\n nvsType === nvsTypes.NVS_TYPE_U16 ||\n nvsType === nvsTypes.NVS_TYPE_I16 ||\n nvsType === nvsTypes.NVS_TYPE_U32 ||\n nvsType === nvsTypes.NVS_TYPE_I32 ||\n nvsType === nvsTypes.NVS_TYPE_U64 ||\n nvsType === nvsTypes.NVS_TYPE_I64\n ) {\n config[entry.id].value = parseInt(entry.value);\n } else {\n config[entry.id].value = entry.value;\n }\n config[entry.id].type = nvsType;\n }\n } else {\n config[entry.id] = entry.value;\n }\n });\n const key = $('#nvs-new-key').val();\n const val = $('#nvs-new-value').val();\n if (key !== '') {\n if (!slimMode) {\n config[key] = {};\n config[key].value = val;\n config[key].type = 33;\n } else {\n config[key] = val;\n }\n }\n return config;\n}\n\nfunction handleHWPreset(allfields, reboot) {\n\n const selJson = JSON.parse(allfields[0].value);\n var cmd = allfields[0].attributes.cmdname.value;\n\n console.log(`selected model: ${selJson.name}`);\n let confPayload = {\n timestamp: Date.now(),\n config: { model_config: { value: selJson.name, type: 33 } }\n };\n for (const [name, value] of Object.entries(selJson.config)) {\n const storedval = (typeof value === 'string' || value instanceof String) ? value : JSON.stringify(value);\n confPayload.config[name] = {\n value: storedval,\n type: 33,\n }\n showCmdMessage(\n cmd,\n 'MESSAGING_INFO',\n `Setting ${name}=${storedval} `,\n true\n );\n }\n\n showCmdMessage(\n cmd,\n 'MESSAGING_INFO',\n `Committing `,\n true\n );\n $.ajax({\n url: '/config.json',\n dataType: 'text',\n method: 'POST',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify(confPayload),\n error: function (xhr, _ajaxOptions, thrownError) {\n handleExceptionResponse(xhr, _ajaxOptions, thrownError);\n showCmdMessage(\n cmd,\n 'MESSAGING_ERROR',\n `Unexpected error ${(thrownError !== '') ? thrownError : 'with return status = ' + xhr.status} `,\n true\n );\n },\n success: function (response) {\n showCmdMessage(\n cmd,\n 'MESSAGING_INFO',\n `Saving complete `,\n true\n );\n console.log(response);\n if (reboot) {\n delayReboot(2500, cmd);\n }\n },\n });\n}\n\n\n// pull json file from https://gist.githubusercontent.com/sle118/dae585e157b733a639c12dc70f0910c5/raw/b462691f69e2ad31ac95c547af6ec97afb0f53db/squeezelite-esp32-presets.json and\nfunction loadPresets() {\n if ($(\"#cfg-hw-preset-model_config\").length == 0) return;\n if (presetsloaded) return;\n presetsloaded = true;\n $('#cfg-hw-preset-model_config').html('');\n $.getJSON(\n 'https://gist.githubusercontent.com/sle118/dae585e157b733a639c12dc70f0910c5/raw/',\n { _: new Date().getTime() },\n function (data) {\n $.each(data, function (key, val) {\n $('#cfg-hw-preset-model_config').append(``);\n if (preset_name !== '' && preset_name == val.name) {\n $('#cfg-hw-preset-model_config').val(preset_name);\n }\n });\n if (preset_name !== '') {\n ('#prev_preset').show().val(preset_name);\n }\n }\n\n ).fail(function (jqxhr, textStatus, error) {\n const err = textStatus + ', ' + error;\n console.log('Request Failed: ' + err);\n }\n );\n}\n\nfunction delayReboot(duration, cmdname, ota = 'reboot') {\n const url = '/' + ota + '.json';\n $('tbody#tasks').empty();\n $('#tasks_sect').css('visibility', 'collapse');\n Promise.resolve({ cmdname: cmdname, url: url })\n .delay(duration)\n .then(function (data) {\n if (data.cmdname.length > 0) {\n showCmdMessage(\n data.cmdname,\n 'MESSAGING_WARNING',\n 'System is rebooting.\\n',\n true\n );\n } else {\n showLocalMessage('System is rebooting.\\n', 'MESSAGING_WARNING');\n }\n console.log('now triggering reboot');\n $(\"button[onclick*='handleReboot']\").addClass('rebooting');\n $.ajax({\n url: data.url,\n dataType: 'text',\n method: 'POST',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify({\n timestamp: Date.now(),\n }),\n error: handleExceptionResponse,\n complete: function () {\n console.log('reboot call completed');\n Promise.resolve(data)\n .delay(6000)\n .then(function (rdata) {\n if (rdata.cmdname.length > 0) {\n HideCmdMessage(rdata.cmdname);\n }\n getCommands();\n getConfig();\n });\n },\n });\n });\n}\n// eslint-disable-next-line no-unused-vars\nwindow.saveAutoexec1 = function (apply) {\n showCmdMessage('cfg-audio-tmpl', 'MESSAGING_INFO', 'Saving.\\n', false);\n let commandLine = `${commandHeader} -o ${output} `;\n $('.sqcmd').each(function () {\n let { opt, val } = get_control_option_value($(this));\n if ((opt && opt.length>0 ) && typeof(val) == 'boolean' || val.length > 0) {\n const optStr=opt===':'?opt:(` -${opt} `);\n val = typeof(val) == 'boolean'?'':val;\n commandLine += `${optStr} ${val}`;\n }\n });\n const resample=$('#cmd_opt_R input[name=resample]:checked');\n if (resample.length>0 && resample.attr('suffix')!=='') {\n commandLine += resample.attr('suffix');\n // now check resample_i option and if checked, add suffix to command line\n if ($('#resample_i').is(\":checked\") && resample.attr('aint') =='true') {\n commandLine += $('#resample_i').attr('suffix');\n }\n}\n\n \n if (output === 'bt') {\n showCmdMessage(\n 'cfg-audio-tmpl',\n 'MESSAGING_INFO',\n 'Remember to configure the Bluetooth audio device name.\\n',\n true\n );\n }\n commandLine += concatenateOptions(options);\n const data = {\n timestamp: Date.now(),\n };\n data.config = {\n autoexec1: { value: commandLine, type: 33 },\n autoexec: {\n value: $('#disable-squeezelite').prop('checked') ? '0' : '1',\n type: 33,\n },\n };\n\n $.ajax({\n url: '/config.json',\n dataType: 'text',\n method: 'POST',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify(data),\n error: handleExceptionResponse,\n complete: function (response) {\n if (\n response.responseText &&\n JSON.parse(response.responseText).result === 'OK'\n ) {\n showCmdMessage('cfg-audio-tmpl', 'MESSAGING_INFO', 'Done.\\n', true);\n if (apply) {\n delayReboot(1500, 'cfg-audio-tmpl');\n }\n } else if (JSON.parse(response.responseText).result) {\n showCmdMessage(\n 'cfg-audio-tmpl',\n 'MESSAGING_WARNING',\n JSON.parse(response.responseText).Result + '\\n',\n true\n );\n } else {\n showCmdMessage(\n 'cfg-audio-tmpl',\n 'MESSAGING_ERROR',\n response.statusText + '\\n'\n );\n }\n console.log(response.responseText);\n },\n });\n console.log('sent data:', JSON.stringify(data));\n}\nwindow.handleDisconnect = function () {\n $.ajax({\n url: '/connect.json',\n dataType: 'text',\n method: 'DELETE',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify({\n timestamp: Date.now(),\n }),\n });\n}\nfunction setPlatformFilter(val) {\n if ($('.upf').filter(function () { return $(this).text().toUpperCase() === val.toUpperCase() }).length > 0) {\n $('#splf').val(val).trigger('input');\n return true;\n }\n return false;\n}\nwindow.handleConnect = function () {\n ConnectingToSSID.ssid = $('#manual_ssid').val();\n ConnectingToSSID.pwd = $('#manual_pwd').val();\n ConnectingToSSID.dhcpname = $('#dhcp-name2').val();\n $(\"*[class*='connecting']\").hide();\n $('#ssid-wait').text(ConnectingToSSID.ssid);\n $('.connecting').show();\n $.ajax({\n url: '/connect.json',\n dataType: 'text',\n method: 'POST',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify({\n timestamp: Date.now(),\n ssid: ConnectingToSSID.ssid,\n pwd: ConnectingToSSID.pwd\n }),\n error: handleExceptionResponse,\n });\n\n // now we can re-set the intervals regardless of result\n\n}\nfunction renderError(opt,error){\n const fieldname = `cmd_opt_${opt}`;\n let errorFieldName=`${fieldname}-error`;\n let errorField=$(`#${errorFieldName}`);\n let field=$(`#${fieldname}`);\n \n if (!errorField || errorField.length ==0) {\n field.after(`
`);\n errorField=$(`#${errorFieldName}`);\n }\n if(error.length ==0){\n errorField.hide();\n field.removeClass('is-invalid');\n field.addClass('is-valid');\n errorField.text('');\n }\n else { \n errorField.show();\n errorField.text(error);\n field.removeClass('is-valid');\n field.addClass('is-invalid');\n }\n return errorField;\n}\n$(document).ready(function () {\n $('.material-icons').each(function (_index, entry) {\n entry.attributes['icon'] = entry.textContent;\n });\n setIcons(true);\n handleNVSVisible();\n flashState.init();\n $('#fw-url-input').on('input', function () {\n if ($(this).val().length > 8 && ($(this).val().startsWith('http://') || $(this).val().startsWith('https://'))) {\n $('#start-flash').show();\n }\n else {\n $('#start-flash').hide();\n }\n });\n $('.upSrch').on('input', function () {\n const val = this.value;\n $(\"#rTable tr\").removeClass(this.id + '_hide');\n if (val.length > 0) {\n $(`#rTable td:nth-child(${$(this).parent().index() + 1})`).filter(function () {\n return !$(this).text().toUpperCase().includes(val.toUpperCase());\n }).parent().addClass(this.id + '_hide');\n }\n $('[class*=\"_hide\"]').hide();\n $('#rTable tr').not('[class*=\"_hide\"]').show()\n\n });\n setTimeout(refreshAP, 1500);\n /* add validation for cmd_opt_c, which accepts a comma separated list. \n getting known codecs from validOptions.codecs array\n use bootstrap classes to highlight the error with an overlay message */\n $('#options input').on('input', function () {\n const { opt, val } = get_control_option_value(this);\n if (opt === 'c' || opt === 'e') {\n const fieldname = `cmd_opt_${opt}_codec-error`;\n \n const values = val.split(',').map(function (item) {\n return item.trim();\n });\n /* get a list of invalid codecs */\n const invalid = values.filter(function (item) {\n return !validOptions.codecs.includes(item);\n });\n renderError(opt,invalid.length > 0 ? `Invalid codec(s) ${invalid.join(', ')}` : '');\n }\n /* add validation for cmd_opt_m, which accepts a mac_address */\n if (opt === 'm') {\n const mac_regex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;\n renderError(opt,mac_regex.test(val) ? '' : 'Invalid MAC address');\n }\n if (opt === 'r') {\n const rateRegex = /^(\\d+\\.?\\d*|\\.\\d+)-(\\d+\\.?\\d*|\\.\\d+)$|^(\\d+\\.?\\d*)$|^(\\d+\\.?\\d*,)+\\d+\\.?\\d*$/;\n renderError(opt,rateRegex.test(val)?'':`Invalid rate(s) ${val}. Acceptable format: |-|,,`);\n }\n\n\n\n }\n\n\n );\n\n\n\n\n\n $('#WifiConnectDialog')[0].addEventListener('shown.bs.modal', function (event) {\n $(\"*[class*='connecting']\").hide();\n\n if (event?.relatedTarget) {\n ConnectingToSSID.Action = ConnectingToActions.CONN;\n if ($(event.relatedTarget).children('td:eq(1)').text() == ConnectedTo.ssid) {\n ConnectingToSSID.Action = ConnectingToActions.STS;\n }\n else {\n if (!$(event.relatedTarget).is(':last-child')) {\n ConnectingToSSID.ssid = $(event.relatedTarget).children('td:eq(1)').text();\n $('#manual_ssid').val(ConnectingToSSID.ssid);\n }\n else {\n ConnectingToSSID.Action = ConnectingToActions.MAN;\n ConnectingToSSID.ssid = '';\n $('#manual_ssid').val(ConnectingToSSID.ssid);\n }\n }\n }\n\n\n if (ConnectingToSSID.Action !== ConnectingToActions.STS) {\n $('.connecting-init').show();\n $('#manual_ssid').trigger('focus');\n }\n else {\n handleWifiDialog();\n }\n });\n\n $('#WifiConnectDialog')[0].addEventListener('hidden.bs.modal', function () {\n $('#WifiConnectDialog input').val('');\n });\n\n $('#uCnfrm')[0].addEventListener('shown.bs.modal', function () {\n $('#selectedFWURL').text($('#fw-url-input').val());\n });\n\n $('input#show-commands')[0].checked = LastCommandsState === 1;\n $('a[href^=\"#tab-commands\"]').hide();\n $('#load-nvs').on('click', function () {\n $('#nvsfilename').trigger('click');\n });\n $('#nvsfilename').on('change', function () {\n if (typeof window.FileReader !== 'function') {\n throw \"The file API isn't supported on this browser.\";\n }\n if (!this.files) {\n throw 'This browser does not support the `files` property of the file input.';\n }\n if (!this.files[0]) {\n return undefined;\n }\n\n const file = this.files[0];\n let fr = new FileReader();\n fr.onload = function (e) {\n let data = {};\n try {\n data = JSON.parse(e.target.result);\n } catch (ex) {\n alert('Parsing failed!\\r\\n ' + ex);\n }\n $('input.nvs').each(function (_index, entry) {\n $(this).parent().removeClass('bg-warning').removeClass('bg-success');\n if (data[entry.id]) {\n if (data[entry.id] !== entry.value) {\n console.log(\n 'Changed ' + entry.id + ' ' + entry.value + '==>' + data[entry.id]\n );\n $(this).parent().addClass('bg-warning');\n $(this).val(data[entry.id]);\n }\n else {\n $(this).parent().addClass('bg-success');\n }\n }\n });\n var changed = $(\"input.nvs\").children('.bg-warning');\n if (changed) {\n alert('Highlighted values were changed. Press Commit to change on the device');\n }\n }\n fr.readAsText(file);\n this.value = null;\n\n }\n );\n $('#clear-syslog').on('click', function () {\n messagecount = 0;\n messageseverity = 'MESSAGING_INFO';\n $('#msgcnt').text('');\n $('#syslogTable').html('');\n });\n\n $('#ok-credits').on('click', function () {\n $('#credits').slideUp('fast', function () { });\n $('#app').slideDown('fast', function () { });\n });\n\n $('#acredits').on('click', function (event) {\n event.preventDefault();\n $('#app').slideUp('fast', function () { });\n $('#credits').slideDown('fast', function () { });\n });\n\n $('input#show-commands').on('click', function () {\n this.checked = this.checked ? 1 : 0;\n if (this.checked) {\n $('a[href^=\"#tab-commands\"]').show();\n LastCommandsState = 1;\n } else {\n LastCommandsState = 0;\n $('a[href^=\"#tab-commands\"]').hide();\n }\n });\n\n $('input#show-nvs').on('click', function () {\n this.checked = this.checked ? 1 : 0;\n Cookies.set(\"show-nvs\", this.checked ? 'Y' : 'N');\n handleNVSVisible();\n });\n $('#btn_reboot_recovery').on('click', function () {\n handleReboot('recovery');\n });\n $('#btn_reboot').on('click', function () {\n handleReboot('reboot');\n });\n $('#btn_flash').on('click', function () {\n hFlash();\n });\n $('#save-autoexec1').on('click', function () {\n saveAutoexec1(false);\n });\n $('#commit-autoexec1').on('click', function () {\n saveAutoexec1(true);\n });\n $('#btn_disconnect').on('click', function () {\n ConnectedTo = {};\n refreshAPHTML2();\n $.ajax({\n url: '/connect.json',\n dataType: 'text',\n method: 'DELETE',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify({\n timestamp: Date.now(),\n }),\n });\n });\n $('#btnJoin').on('click', function () {\n handleConnect();\n });\n $('#reboot_nav').on('click', function () {\n handleReboot('reboot');\n });\n $('#reboot_ota_nav').on('click', function () {\n handleReboot('reboot_ota');\n });\n\n $('#save-as-nvs').on('click', function () {\n const config = getConfigJson(true);\n const a = document.createElement('a');\n a.href = URL.createObjectURL(\n new Blob([JSON.stringify(config, null, 2)], {\n type: 'text/plain',\n })\n );\n a.setAttribute(\n 'download',\n 'nvs_config_' + hostName + '_' + Date.now() + 'json'\n );\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n });\n\n $('#save-nvs').on('click', function () {\n post_config(getConfigJson(false));\n });\n\n $('#fwUpload').on('click', function () {\n const fileInput = document.getElementById('flashfilename').files;\n if (fileInput.length === 0) {\n alert('No file selected!');\n } else {\n $('#fw-url-input').value = null;\n flashState.StartOTA();\n }\n\n });\n $('[name=output-tmpl]').on('click', function () {\n handleTemplateTypeRadio(this.id);\n });\n\n $('#chkUpdates').on('click', function () {\n $('#rTable').html('');\n $.getJSON(releaseURL, function (data) {\n let i = 0;\n const branches = [];\n data.forEach(function (release) {\n const namecomponents = release.name.split('#');\n const branch = namecomponents[3];\n if (!branches.includes(branch)) {\n branches.push(branch);\n }\n });\n let fwb = '';\n branches.forEach(function (branch) {\n fwb += '';\n });\n $('#fwbranch').append(fwb);\n\n data.forEach(function (release) {\n let url = '';\n release.assets.forEach(function (asset) {\n if (asset.name.match(/\\.bin$/)) {\n url = asset.browser_download_url;\n }\n });\n const namecomponents = release.name.split('#');\n const ver = namecomponents[0];\n const cfg = namecomponents[2];\n const branch = namecomponents[3];\n var bits = ver.substr(ver.lastIndexOf('-') + 1);\n bits = (bits == '32' || bits == '16') ? bits : '';\n\n let body = release.body;\n body = body.replace(/'/gi, '\"');\n body = body.replace(\n /[\\s\\S]+(### Revision Log[\\s\\S]+)### ESP-IDF Version Used[\\s\\S]+/,\n '$1'\n );\n body = body.replace(/- \\(.+?\\) /g, '- ').encodeHTML();\n $('#rTable').append(`\n ${ver}${new Date(release.created_at).toLocalShort()}\n ${cfg}${branch}${bits}`\n );\n });\n if (i > 7) {\n $('#releaseTable').append(\n \"\" +\n \"\" +\n \"\" +\n '' +\n ''\n );\n $('#showallbutton').on('click', function () {\n $('tr.hide').removeClass('hide');\n $('tr#showall').addClass('hide');\n });\n }\n $('#searchfw').css('display', 'inline');\n if (!setPlatformFilter(platform_name)) {\n setPlatformFilter(project_name)\n }\n $('#rTable tr.release').on('click', function () {\n var url = this.attributes['fwurl'].value;\n if (lmsBaseUrl) {\n url = url.replace(/.*\\/download\\//, lmsBaseUrl + '/plugins/SqueezeESP32/firmware/');\n }\n $('#fw-url-input').val(url);\n $('#start-flash').show();\n $('#rTable tr.release').removeClass('table-success table-warning');\n $(this).addClass('table-success table-warning');\n });\n\n }).fail(function () {\n alert('failed to fetch release history!');\n });\n });\n $('#fwcheck').on('click', function () {\n $('#releaseTable').html('');\n $('#fwbranch').empty();\n $.getJSON(releaseURL, function (data) {\n let i = 0;\n const branches = [];\n data.forEach(function (release) {\n const namecomponents = release.name.split('#');\n const branch = namecomponents[3];\n if (!branches.includes(branch)) {\n branches.push(branch);\n }\n });\n let fwb;\n branches.forEach(function (branch) {\n fwb += '';\n });\n $('#fwbranch').append(fwb);\n\n data.forEach(function (release) {\n let url = '';\n release.assets.forEach(function (asset) {\n if (asset.name.match(/\\.bin$/)) {\n url = asset.browser_download_url;\n }\n });\n const namecomponents = release.name.split('#');\n const ver = namecomponents[0];\n const idf = namecomponents[1];\n const cfg = namecomponents[2];\n const branch = namecomponents[3];\n\n let body = release.body;\n body = body.replace(/'/gi, '\"');\n body = body.replace(\n /[\\s\\S]+(### Revision Log[\\s\\S]+)### ESP-IDF Version Used[\\s\\S]+/,\n '$1'\n );\n body = body.replace(/- \\(.+?\\) /g, '- ');\n const trclass = i++ > 6 ? ' hide' : '';\n $('#releaseTable').append(\n \"\" +\n \"\" +\n ver +\n '' +\n '' +\n new Date(release.created_at).toLocalShort() +\n '' +\n '' +\n cfg +\n '' +\n '' +\n idf +\n '' +\n '' +\n branch +\n '' +\n \"\" +\n ''\n );\n });\n if (i > 7) {\n $('#releaseTable').append(\n \"\" +\n \"\" +\n \"\" +\n '' +\n ''\n );\n $('#showallbutton').on('click', function () {\n $('tr.hide').removeClass('hide');\n $('tr#showall').addClass('hide');\n });\n }\n $('#searchfw').css('display', 'inline');\n }).fail(function () {\n alert('failed to fetch release history!');\n });\n });\n\n $('#updateAP').on('click', function () {\n refreshAP();\n console.log('refresh AP');\n });\n\n // first time the page loads: attempt to get the connection status and start the wifi scan\n getConfig();\n getCommands();\n getMessages();\n checkStatus();\n\n});\n\n// eslint-disable-next-line no-unused-vars\nwindow.setURL = function (button) {\n let url = button.dataset.url;\n\n $('[data-bs-url^=\"http\"]')\n .addClass('btn-success')\n .removeClass('btn-danger');\n $('[data-bs-url=\"' + url + '\"]')\n .addClass('btn-danger')\n .removeClass('btn-success');\n\n // if user can proxy download through LMS, modify the URL\n if (lmsBaseUrl) {\n url = url.replace(/.*\\/download\\//, lmsBaseUrl + '/plugins/SqueezeESP32/firmware/');\n }\n\n $('#fwurl').val(url);\n}\n\n\nfunction rssiToIcon(rssi) {\n if (rssi >= -55) {\n return { 'label': '****', 'icon': `signal_wifi_statusbar_4_bar` };\n } else if (rssi >= -60) {\n return { 'label': '***', 'icon': `network_wifi_3_bar` };\n } else if (rssi >= -65) {\n return { 'label': '**', 'icon': `network_wifi_2_bar` };\n } else if (rssi >= -70) {\n return { 'label': '*', 'icon': `network_wifi_1_bar` };\n } else {\n return { 'label': '.', 'icon': `signal_wifi_statusbar_null` };\n }\n}\n\nfunction refreshAP() {\n if (ConnectedTo?.urc === connectReturnCode.ETH) return;\n $.ajaxSetup({\n timeout: 3000 //Time in milliseconds\n });\n $.getJSON('/scan.json', async function () {\n await sleep(2000);\n $.getJSON('/ap.json', function (data) {\n if (data.length > 0) {\n // sort by signal strength\n data.sort(function (a, b) {\n const x = a.rssi;\n const y = b.rssi;\n // eslint-disable-next-line no-nested-ternary\n return x < y ? 1 : x > y ? -1 : 0;\n });\n apList = data;\n refreshAPHTML2(apList);\n\n }\n });\n });\n}\nfunction formatAP(ssid, rssi, auth) {\n const rssi_icon = rssiToIcon(rssi);\n const auth_icon = { label: auth == 0 ? '🔓' : '🔒', icon: auth == 0 ? 'no_encryption' : 'lock' };\n\n return `${ssid}\n ${getIcon(rssi_icon)}\n \t\n ${getIcon(auth_icon)}\n `;\n}\nfunction refreshAPHTML2(data) {\n let h = '';\n $('#wifiTable tr td:first-of-type').text('');\n $('#wifiTable tr').removeClass('table-success table-warning');\n if (data) {\n data.forEach(function (e) {\n h += formatAP(e.ssid, e.rssi, e.auth);\n });\n $('#wifiTable').html(h);\n }\n if ($('.manual_add').length == 0) {\n $('#wifiTable').append(formatAP('Manual add', 0, 0));\n $('#wifiTable tr:last').addClass('table-light text-dark').addClass('manual_add');\n }\n if (ConnectedTo.ssid && (ConnectedTo.urc === connectReturnCode.OK || ConnectedTo.urc === connectReturnCode.RESTORE)) {\n const wifiSelector = `#wifiTable td:contains(\"${ConnectedTo.ssid}\")`;\n if ($(wifiSelector).filter(function () { return $(this).text() === ConnectedTo.ssid; }).length == 0) {\n $('#wifiTable').prepend(`${formatAP(ConnectedTo.ssid, ConnectedTo.rssi ?? 0, 0)}`);\n }\n $(wifiSelector).filter(function () { return $(this).text() === ConnectedTo.ssid; }).siblings().first().html('✓').parent().addClass((ConnectedTo.urc === connectReturnCode.OK ? 'table-success' : 'table-warning'));\n $('span#foot-if').html(`SSID: ${ConnectedTo.ssid}, IP: ${ConnectedTo.ip}`);\n $('#wifiStsIcon').html(rssiToIcon(ConnectedTo.rssi));\n\n }\n else if (ConnectedTo?.urc !== connectReturnCode.ETH) {\n $('span#foot-if').html('');\n }\n\n}\nfunction refreshETH() {\n\n if (ConnectedTo.urc === connectReturnCode.ETH) {\n $('span#foot-if').html(`Network: Ethernet, IP: ${ConnectedTo.ip}`);\n }\n}\nfunction showTask(task) {\n console.debug(\n this.toLocaleString() +\n '\\t' +\n task.nme +\n '\\t' +\n task.cpu +\n '\\t' +\n taskStates[task.st] +\n '\\t' +\n task.minstk +\n '\\t' +\n task.bprio +\n '\\t' +\n task.cprio +\n '\\t' +\n task.num\n );\n $('tbody#tasks').append(\n '' +\n task.num +\n '' +\n task.nme +\n '' +\n task.cpu +\n '' +\n taskStates[task.st] +\n '' +\n task.minstk +\n '' +\n task.bprio +\n '' +\n task.cprio +\n ''\n );\n}\nfunction btExists(name) {\n return getBTSinkOpt(name).length > 0;\n}\nfunction getBTSinkOpt(name) {\n return $(`${btSinkNamesOptSel} option:contains('${name}')`);\n}\nfunction getMessages() {\n $.ajaxSetup({\n timeout: messageInterval //Time in milliseconds\n });\n $.getJSON('/messages.json', async function (data) {\n for (const msg of data) {\n const msgAge = msg.current_time - msg.sent_time;\n var msgTime = new Date();\n msgTime.setTime(msgTime.getTime() - msgAge);\n switch (msg.class) {\n case 'MESSAGING_CLASS_OTA':\n flashState.EventOTAMessageClass(msg.message);\n break;\n case 'MESSAGING_CLASS_STATS':\n // for task states, check structure : task_state_t\n var statsData = JSON.parse(msg.message);\n console.debug(\n msgTime.toLocalShort() +\n ' - Number of running tasks: ' +\n statsData.ntasks\n );\n console.debug(\n msgTime.toLocalShort() +\n '\\tname' +\n '\\tcpu' +\n '\\tstate' +\n '\\tminstk' +\n '\\tbprio' +\n '\\tcprio' +\n '\\tnum'\n );\n if (statsData.tasks) {\n if ($('#tasks_sect').css('visibility') === 'collapse') {\n $('#tasks_sect').css('visibility', 'visible');\n }\n $('tbody#tasks').html('');\n statsData.tasks\n .sort(function (a, b) {\n return b.cpu - a.cpu;\n })\n .forEach(showTask, msgTime);\n } else if ($('#tasks_sect').css('visibility') === 'visible') {\n $('tbody#tasks').empty();\n $('#tasks_sect').css('visibility', 'collapse');\n }\n break;\n case 'MESSAGING_CLASS_SYSTEM':\n showMessage(msg, msgTime);\n break;\n case 'MESSAGING_CLASS_CFGCMD':\n var msgparts = msg.message.split(/([^\\n]*)\\n(.*)/gs);\n showCmdMessage(msgparts[1], msg.type, msgparts[2], true);\n break;\n case 'MESSAGING_CLASS_BT':\n if ($(\"#cfg-audio-bt_source-sink_name\").is('input')) {\n var attr = $(\"#cfg-audio-bt_source-sink_name\")[0].attributes;\n var attrs = '';\n for (var j = 0; j < attr.length; j++) {\n if (attr.item(j).name != \"type\") {\n attrs += `${attr.item(j).name} = \"${attr.item(j).value}\" `;\n }\n }\n var curOpt = $(\"#cfg-audio-bt_source-sink_name\")[0].value;\n $(\"#cfg-audio-bt_source-sink_name\").replaceWith(` `);\n }\n JSON.parse(msg.message).forEach(function (btEntry) {\n //\n // \n if (!btExists(btEntry.name)) {\n $(\"#cfg-audio-bt_source-sink_name\").append(``);\n showMessage({ type: msg.type, message: `BT Audio device found: ${btEntry.name} RSSI: ${btEntry.rssi} ` }, msgTime);\n }\n getBTSinkOpt(btEntry.name).attr('data-bs-description', `${btEntry.name} (${btEntry.rssi}dB)`)\n .attr('rssi', btEntry.rssi)\n .attr('value', btEntry.name)\n .text(`${btEntry.name} [${btEntry.rssi}dB]`).trigger('change');\n\n });\n $(btSinkNamesOptSel).append($(`${btSinkNamesOptSel} option`).remove().sort(function (a, b) {\n console.log(`${parseInt($(a).attr('rssi'))} < ${parseInt($(b).attr('rssi'))} ? `);\n return parseInt($(a).attr('rssi')) < parseInt($(b).attr('rssi')) ? 1 : -1;\n }));\n break;\n default:\n break;\n }\n }\n setTimeout(getMessages, messageInterval);\n }).fail(function (xhr, ajaxOptions, thrownError) {\n\n if (xhr.status == 404) {\n $('.orec').hide(); // system commands won't be available either\n messagesHeld = true;\n }\n else {\n handleExceptionResponse(xhr, ajaxOptions, thrownError);\n }\n if (xhr.status == 0 && xhr.readyState == 0) {\n // probably a timeout. Target is rebooting? \n setTimeout(getMessages, messageInterval * 2); // increase duration if a failure happens\n }\n else if (!messagesHeld) {\n // 404 here means we rebooted to an old recovery\n setTimeout(getMessages, messageInterval); // increase duration if a failure happens\n }\n\n }\n );\n\n /*\n Minstk is minimum stack space left\nBprio is base priority\ncprio is current priority\nnme is name\nst is task state. I provided a \"typedef\" that you can use to convert to text\ncpu is cpu percent used\n*/\n}\nfunction handleRecoveryMode(data) {\n const locRecovery = data.recovery ?? 0;\n if (locRecovery === 1) {\n recovery = true;\n $('.recovery_element').show();\n $('.ota_element').hide();\n $('#boot-button').html('Reboot');\n $('#boot-form').attr('action', '/reboot_ota.json');\n } else {\n if (!recovery && messagesHeld) {\n messagesHeld = false;\n setTimeout(getMessages, messageInterval); // increase duration if a failure happens\n }\n recovery = false;\n\n $('.recovery_element').hide();\n $('.ota_element').show();\n $('#boot-button').html('Recovery');\n $('#boot-form').attr('action', '/recovery.json');\n }\n\n}\n\nfunction hasConnectionChanged(data) {\n // gw: \"192.168.10.1\"\n // ip: \"192.168.10.225\"\n // netmask: \"255.255.255.0\"\n // ssid: \"MyTestSSID\"\n\n return (data.urc !== ConnectedTo.urc ||\n data.ssid !== ConnectedTo.ssid ||\n data.gw !== ConnectedTo.gw ||\n data.netmask !== ConnectedTo.netmask ||\n data.ip !== ConnectedTo.ip || data.rssi !== ConnectedTo.rssi)\n}\nfunction handleWifiDialog(data) {\n if ($('#WifiConnectDialog').is(':visible')) {\n if (ConnectedTo.ip) {\n $('#ipAddress').text(ConnectedTo.ip);\n }\n if (ConnectedTo.ssid) {\n $('#connectedToSSID').text(ConnectedTo.ssid);\n }\n if (ConnectedTo.gw) {\n $('#gateway').text(ConnectedTo.gw);\n }\n if (ConnectedTo.netmask) {\n $('#netmask').text(ConnectedTo.netmask);\n }\n if (ConnectingToSSID.Action === undefined || (ConnectingToSSID.Action && ConnectingToSSID.Action == ConnectingToActions.STS)) {\n $(\"*[class*='connecting']\").hide();\n $('.connecting-status').show();\n }\n if (SystemConfig.ap_ssid) {\n $('#apName').text(SystemConfig.ap_ssid.value);\n }\n if (SystemConfig.ap_pwd) {\n $('#apPass').text(SystemConfig.ap_pwd.value);\n }\n if (!data) {\n return;\n }\n else {\n switch (data.urc) {\n case connectReturnCode.OK:\n if (data.ssid && data.ssid === ConnectingToSSID.ssid) {\n $(\"*[class*='connecting']\").hide();\n $('.connecting-success').show();\n ConnectingToSSID.Action = ConnectingToActions.STS;\n }\n break;\n case connectReturnCode.FAIL:\n // \n if (ConnectingToSSID.Action != ConnectingToActions.STS && ConnectingToSSID.ssid == data.ssid) {\n $(\"*[class*='connecting']\").hide();\n $('.connecting-fail').show();\n }\n break;\n case connectReturnCode.LOST:\n\n break;\n case connectReturnCode.RESTORE:\n if (ConnectingToSSID.Action != ConnectingToActions.STS && ConnectingToSSID.ssid != data.ssid) {\n $(\"*[class*='connecting']\").hide();\n $('.connecting-fail').show();\n }\n break;\n case connectReturnCode.DISC:\n // that's a manual disconnect\n // if ($('#wifi-status').is(':visible')) {\n // $('#wifi-status').slideUp('fast', function() {});\n // $('span#foot-wifi').html('');\n\n // } \n break;\n default:\n break;\n }\n }\n\n }\n}\nfunction setIcons(offline) {\n $('.material-icons').each(function (_index, entry) {\n entry.textContent = entry.attributes[offline ? 'aria-label' : 'icon'].value;\n });\n}\nfunction handleNetworkStatus(data) {\n setIcons(!isConnected());\n if (hasConnectionChanged(data) || !data.urc) {\n ConnectedTo = data;\n $(\".if_eth\").hide();\n $('.if_wifi').hide();\n if (!data.urc || ConnectedTo.urc != connectReturnCode.ETH) {\n $('.if_wifi').show();\n refreshAPHTML2();\n }\n else {\n $(\".if_eth\").show();\n refreshETH();\n }\n\n }\n handleWifiDialog(data);\n}\n\n\n\nfunction batteryToIcon(voltage) {\n /* Assuming Li-ion 18650s as a power source, 3.9V per cell, or above is treated\n as full charge (>75% of capacity). 3.4V is empty. The gauge is loosely\n following the graph here:\n https://learn.adafruit.com/li-ion-and-lipoly-batteries/voltages\n using the 0.2C discharge profile for the rest of the values.\n*/\n\n for (const iconEntry of batIcons) {\n for (const entryRanges of iconEntry.ranges) {\n if (inRange(voltage, entryRanges.f, entryRanges.t)) {\n return { label: iconEntry.label, icon: iconEntry.icon };\n }\n }\n }\n\n\n return { label: 'â–Ēâ–Ēâ–Ēâ–Ē', icon: \"battery_full\" };\n}\nfunction checkStatus() {\n $.ajaxSetup({\n timeout: statusInterval //Time in milliseconds\n });\n $.getJSON('/status.json', function (data) {\n handleRecoveryMode(data);\n handleNVSVisible();\n handleNetworkStatus(data);\n handlebtstate(data);\n flashState.EventTargetStatus(data);\n if(data.depth) {\n depth = data.depth;\n if(depth==16){\n $('#cmd_opt_R').show();\n }\n else{\n $('#cmd_opt_R').hide();\n }\n }\n\n\n if (data.project_name && data.project_name !== '') {\n project_name = data.project_name;\n }\n if (data.platform_name && data.platform_name !== '') {\n platform_name = data.platform_name;\n }\n if (board_model === '') board_model = project_name;\n if (board_model === '') board_model = 'Squeezelite-ESP32';\n if (data.version && data.version !== '') {\n versionName = data.version;\n $(\"#navtitle\").html(`${board_model}${recovery ? '
[recovery]' : ''}`);\n $('span#foot-fw').html(`fw: ${versionName}, mode: ${recovery ? \"Recovery\" : project_name}`);\n } else {\n $('span#flash-status').html('');\n }\n if (data.Voltage) {\n const bat_icon = batteryToIcon(data.Voltage);\n $('#battery').html(`${getIcon(bat_icon)}`);\n $('#battery').attr(\"aria-label\", bat_icon.label);\n $('#battery').attr(\"icon\", bat_icon.icon);\n $('#battery').show();\n } else {\n $('#battery').hide();\n }\n if ((data.message ?? '') != '' && prevmessage != data.message) {\n // supporting older recovery firmwares - messages will come from the status.json structure\n prevmessage = data.message;\n showLocalMessage(data.message, 'MESSAGING_INFO')\n }\n is_i2c_locked = data.is_i2c_locked;\n if (is_i2c_locked) {\n $('flds-cfg-hw-preset').hide();\n }\n else {\n $('flds-cfg-hw-preset').show();\n }\n $(\"button[onclick*='handleReboot']\").removeClass('rebooting');\n\n if (typeof lmsBaseUrl == \"undefined\" || data.lms_ip != prevLMSIP && data.lms_ip && data.lms_port) {\n const baseUrl = 'http://' + data.lms_ip + ':' + data.lms_port;\n prevLMSIP = data.lms_ip;\n $.ajax({\n url: baseUrl + '/plugins/SqueezeESP32/firmware/-check.bin',\n type: 'HEAD',\n dataType: 'text',\n cache: false,\n error: function () {\n // define the value, so we don't check it any more.\n lmsBaseUrl = '';\n },\n success: function () {\n lmsBaseUrl = baseUrl;\n }\n });\n }\n $('#o_jack').css({ display: Number(data.Jack) ? 'inline' : 'none' });\n setTimeout(checkStatus, statusInterval);\n }).fail(function (xhr, ajaxOptions, thrownError) {\n handleExceptionResponse(xhr, ajaxOptions, thrownError);\n if (xhr.status == 0 && xhr.readyState == 0) {\n // probably a timeout. Target is rebooting? \n setTimeout(checkStatus, messageInterval * 2); // increase duration if a failure happens\n }\n else {\n setTimeout(checkStatus, messageInterval); // increase duration if a failure happens\n }\n });\n}\n// eslint-disable-next-line no-unused-vars\nwindow.runCommand = function (button, reboot) {\n let cmdstring = button.attributes.cmdname.value;\n showCmdMessage(\n button.attributes.cmdname.value,\n 'MESSAGING_INFO',\n 'Executing.',\n false\n );\n const fields = document.getElementById('flds-' + cmdstring);\n const allfields = fields?.querySelectorAll('select,input');\n if (cmdstring === 'cfg-hw-preset') return handleHWPreset(allfields, reboot);\n cmdstring += ' ';\n if (fields) {\n\n for (const field of allfields) {\n let qts = '';\n let opt = '';\n let attr = field.attributes;\n let isSelect = $(field).is('select');\n const hasValue = attr?.hasvalue?.value === 'true';\n const validVal = (isSelect && field.value !== '--') || (!isSelect && field.value !== '');\n\n if (!hasValue || hasValue && validVal) {\n if (attr?.longopts?.value !== 'undefined') {\n opt += '--' + attr?.longopts?.value;\n } else if (attr?.shortopts?.value !== 'undefined') {\n opt = '-' + attr.shortopts.value;\n }\n\n if (attr?.hasvalue?.value === 'true') {\n if (attr?.value !== '') {\n qts = /\\s/.test(field.value) ? '\"' : '';\n cmdstring += opt + ' ' + qts + field.value + qts + ' ';\n }\n } else {\n // this is a checkbox\n if (field?.checked) {\n cmdstring += opt + ' ';\n }\n }\n }\n }\n }\n\n console.log(cmdstring);\n\n const data = {\n timestamp: Date.now(),\n };\n data.command = cmdstring;\n\n $.ajax({\n url: '/commands.json',\n dataType: 'text',\n method: 'POST',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify(data),\n error: function (xhr, _ajaxOptions, thrownError) {\n var cmd = JSON.parse(this.data).command;\n if (xhr.status == 404) {\n showCmdMessage(\n cmd.substr(0, cmd.indexOf(' ')),\n 'MESSAGING_ERROR',\n `${recovery ? 'Limited recovery mode active. Unsupported action ' : 'Unexpected error while processing command'}`,\n true\n );\n }\n else {\n handleExceptionResponse(xhr, _ajaxOptions, thrownError);\n showCmdMessage(\n cmd.substr(0, cmd.indexOf(' ') - 1),\n 'MESSAGING_ERROR',\n `Unexpected error ${(thrownError !== '') ? thrownError : 'with return status = ' + xhr.status}`,\n true\n );\n }\n },\n success: function (response) {\n $('.orec').show();\n console.log(response);\n if (\n JSON.parse(response).Result === 'Success' &&\n reboot\n ) {\n delayReboot(2500, button.attributes.cmdname.value);\n }\n },\n });\n}\nfunction getLongOps(data, name, longopts) {\n return data.values[name] !== undefined ? data.values[name][longopts] : \"\";\n}\nfunction getCommands() {\n $.ajaxSetup({\n timeout: 7000 //Time in milliseconds\n });\n $.getJSON('/commands.json', function (data) {\n console.log(data);\n $('.orec').show();\n data.commands.forEach(function (command) {\n if ($('#flds-' + command.name).length === 0) {\n const cmdParts = command.name.split('-');\n const isConfig = cmdParts[0] === 'cfg';\n const targetDiv = '#tab-' + cmdParts[0] + '-' + cmdParts[1];\n let innerhtml = '';\n innerhtml += `
${command.help.encodeHTML().replace(/\\n/g, '
')}
`;\n if (command.argtable) {\n command.argtable.forEach(function (arg) {\n let placeholder = arg.datatype || '';\n const ctrlname = command.name + '-' + arg.longopts;\n const curvalue = getLongOps(data, command.name, arg.longopts);\n\n let attributes = 'hasvalue=' + arg.hasvalue + ' ';\n attributes += 'longopts=\"' + arg.longopts + '\" ';\n attributes += 'shortopts=\"' + arg.shortopts + '\" ';\n attributes += 'checkbox=' + arg.checkbox + ' ';\n attributes += 'cmdname=\"' + command.name + '\" ';\n attributes +=\n 'id=\"' +\n ctrlname +\n '\" name=\"' +\n ctrlname +\n '\" hasvalue=\"' +\n arg.hasvalue +\n '\" ';\n let extraclass = arg.mincount > 0 ? 'bg-success' : '';\n if (arg.glossary === 'hidden') {\n attributes += ' style=\"visibility: hidden;\"';\n }\n if (arg.checkbox) {\n innerhtml += `
`;\n } else {\n innerhtml += `
`;\n if (placeholder.includes('|')) {\n extraclass = placeholder.startsWith('+') ? ' multiple ' : '';\n placeholder = placeholder\n .replace('<', '')\n .replace('=', '')\n .replace('>', '');\n innerhtml += `';\n } else {\n innerhtml += ``;\n }\n }\n\n innerhtml += `${arg.checkbox ? '
' : ''}Previous value: ${arg.checkbox ? (curvalue ? 'Checked' : 'Unchecked') : (curvalue || '')}${arg.checkbox ? '' : '
'}`;\n });\n }\n innerhtml += `
\n
\n
\n Result\n
\n
\n
`;\n if (isConfig) {\n innerhtml +=\n `\n`;\n } else {\n innerhtml += ``;\n }\n innerhtml += '
';\n if (isConfig) {\n $(targetDiv).append(innerhtml);\n } else {\n $('#commands-list').append(innerhtml);\n }\n }\n });\n $(\".sclk\").off('click').on('click', function () { runCommand(this, false); });\n $(\".cclk\").off('click').on('click', function () { runCommand(this, true); });\n data.commands.forEach(function (command) {\n $('[cmdname=' + command.name + ']:input').val('');\n $('[cmdname=' + command.name + ']:checkbox').prop('checked', false);\n if (command.argtable) {\n command.argtable.forEach(function (arg) {\n const ctrlselector = '#' + command.name + '-' + arg.longopts;\n const ctrlValue = getLongOps(data, command.name, arg.longopts);\n if (arg.checkbox) {\n $(ctrlselector)[0].checked = ctrlValue;\n } else {\n if (ctrlValue !== undefined) {\n $(ctrlselector)\n .val(ctrlValue)\n .trigger('change');\n }\n if (\n $(ctrlselector)[0].value.length === 0 &&\n (arg.datatype || '').includes('|')\n ) {\n $(ctrlselector)[0].value = '--';\n }\n }\n });\n }\n });\n loadPresets();\n }).fail(function (xhr, ajaxOptions, thrownError) {\n if (xhr.status == 404) {\n $('.orec').hide();\n }\n else {\n handleExceptionResponse(xhr, ajaxOptions, thrownError);\n }\n $('#commands-list').empty();\n\n });\n}\n\nfunction getConfig() {\n $.ajaxSetup({\n timeout: 7000 //Time in milliseconds\n });\n $.getJSON('/config.json', function (entries) {\n $('#nvsTable tr').remove();\n const data = (entries.config ? entries.config : entries);\n SystemConfig = data;\n commandBTSinkName = '';\n Object.keys(data)\n .sort()\n .forEach(function (key) {\n let val = data[key].value;\n if (key === 'autoexec') {\n if (data.autoexec.value === '0') {\n $('#disable-squeezelite')[0].checked = true;\n } else {\n $('#disable-squeezelite')[0].checked = false;\n }\n } else if (key === 'autoexec1') {\n /* call new function to parse the squeezelite options */\n processSqueezeliteCommandLine(val);\n } else if (key === 'host_name') {\n val = val.replaceAll('\"', '');\n $('input#dhcp-name1').val(val);\n $('input#dhcp-name2').val(val);\n if ($('#cmd_opt_n').length == 0) {\n $('#cmd_opt_n').val(val);\n }\n document.title = val;\n hostName = val;\n } else if (key === 'rel_api') {\n releaseURL = val;\n }\n else if (key === 'enable_airplay') {\n $(\"#s_airplay\").css({ display: isEnabled(val) ? 'inline' : 'none' })\n }\n else if (key === 'enable_cspot') {\n $(\"#s_cspot\").css({ display: isEnabled(val) ? 'inline' : 'none' })\n }\n else if (key == 'preset_name') {\n preset_name = val;\n }\n else if (key == 'board_model') {\n board_model = val;\n }\n\n $('tbody#nvsTable').append(\n '' +\n '' +\n key +\n '' +\n \"\" +\n \"' +\n '' +\n ''\n );\n $('input#' + key).val(data[key].value);\n });\n if(commandBTSinkName.length > 0) {\n // persist the sink name found in the autoexec1 command line\n $('#cfg-audio-bt_source-sink_name').val(commandBTSinkName);\n }\n $('tbody#nvsTable').append(\n \"\"\n );\n if (entries.gpio) {\n $('#pins').show();\n $('tbody#gpiotable tr').remove();\n entries.gpio.forEach(function (gpioEntry) {\n $('tbody#gpiotable').append(\n '' +\n gpioEntry.group +\n '' +\n gpioEntry.name +\n '' +\n gpioEntry.gpio +\n '' +\n (gpioEntry.fixed ? 'Fixed' : 'Configuration') +\n ''\n );\n });\n }\n else {\n $('#pins').hide();\n }\n }).fail(function (xhr, ajaxOptions, thrownError) {\n handleExceptionResponse(xhr, ajaxOptions, thrownError);\n });\n}\n\nfunction processSqueezeliteCommandLine(val) {\n const parsed = parseSqueezeliteCommandLine(val);\n if (parsed.output.toUpperCase().startsWith('I2S')) {\n handleTemplateTypeRadio('i2s');\n } else if (parsed.output.toUpperCase().startsWith('SPDIF')) {\n handleTemplateTypeRadio('spdif');\n } else if (parsed.output.toUpperCase().startsWith('BT')) {\n if(parsed.otherOptions.btname){ \n commandBTSinkName= parsed.otherOptions.btname;\n }\n handleTemplateTypeRadio('bt');\n }\n Object.keys(parsed.options).forEach(function (key) {\n const option = parsed.options[key];\n if (!$(`#cmd_opt_${key}`).hasOwnProperty('checked')) {\n $(`#cmd_opt_${key}`).val(option);\n } else {\n $(`#cmd_opt_${key}`)[0].checked = option;\n }\n });\n if (parsed.options.hasOwnProperty('u')) {\n // parse -u v[:i] and check the appropriate radio button with id #resample_v\n const [resampleValue, resampleInterpolation] = parsed.options.u.split(':');\n $(`#resample_${resampleValue}`).prop('checked', true);\n // if resampleinterpolation is set, check resample_i checkbox\n if (resampleInterpolation) {\n $('#resample_i').prop('checked', true);\n }\n }\n\n\n}\n\nfunction showLocalMessage(message, severity) {\n const msg = {\n message: message,\n type: severity,\n };\n showMessage(msg, new Date());\n}\n\nfunction showMessage(msg, msgTime) {\n let color = 'table-success';\n\n if (msg.type === 'MESSAGING_WARNING') {\n color = 'table-warning';\n if (messageseverity === 'MESSAGING_INFO') {\n messageseverity = 'MESSAGING_WARNING';\n }\n } else if (msg.type === 'MESSAGING_ERROR') {\n if (\n messageseverity === 'MESSAGING_INFO' ||\n messageseverity === 'MESSAGING_WARNING'\n ) {\n messageseverity = 'MESSAGING_ERROR';\n }\n color = 'table-danger';\n }\n if (++messagecount > 0) {\n $('#msgcnt').removeClass('badge-success');\n $('#msgcnt').removeClass('badge-warning');\n $('#msgcnt').removeClass('badge-danger');\n $('#msgcnt').addClass(pillcolors[messageseverity]);\n $('#msgcnt').text(messagecount);\n }\n\n $('#syslogTable').append(\n \"\" +\n '' +\n msgTime.toLocalShort() +\n '' +\n '' +\n msg.message.encodeHTML() +\n '' +\n ''\n );\n}\n\nfunction inRange(x, min, max) {\n return (x - min) * (x - max) <= 0;\n}\n\nfunction sleep(ms) {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nrequire(\"bootstrap\");\nrequire(\"./sass/main.scss\");\nrequire(\"./assets/images/favicon-32x32.png\");\nrequire(\"./js/custom.js\");\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\tid: moduleId,\n\t\tloaded: false,\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Flag the module as loaded\n\tmodule.loaded = true;\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","__webpack_require__.nmd = (module) => {\n\tmodule.paths = [];\n\tif (!module.children) module.children = [];\n\treturn module;\n};","// no baseURI\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t826: 0\n};\n\n// no chunk on demand loading\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n__webpack_require__.O.j = (chunkId) => (installedChunks[chunkId] === 0);\n\n// install a JSONP callback for chunk loading\nvar webpackJsonpCallback = (parentChunkLoadingFunction, data) => {\n\tvar [chunkIds, moreModules, runtime] = data;\n\t// add \"moreModules\" to the modules object,\n\t// then flag all \"chunkIds\" as loaded and fire callback\n\tvar moduleId, chunkId, i = 0;\n\tif(chunkIds.some((id) => (installedChunks[id] !== 0))) {\n\t\tfor(moduleId in moreModules) {\n\t\t\tif(__webpack_require__.o(moreModules, moduleId)) {\n\t\t\t\t__webpack_require__.m[moduleId] = moreModules[moduleId];\n\t\t\t}\n\t\t}\n\t\tif(runtime) var result = runtime(__webpack_require__);\n\t}\n\tif(parentChunkLoadingFunction) parentChunkLoadingFunction(data);\n\tfor(;i < chunkIds.length; i++) {\n\t\tchunkId = chunkIds[i];\n\t\tif(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {\n\t\t\tinstalledChunks[chunkId][0]();\n\t\t}\n\t\tinstalledChunks[chunkId] = 0;\n\t}\n\treturn __webpack_require__.O(result);\n}\n\nvar chunkLoadingGlobal = self[\"webpackChunksqueezelite_esp32\"] = self[\"webpackChunksqueezelite_esp32\"] || [];\nchunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));\nchunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));","// startup\n// Load entry module and return exports\n// This entry module depends on other loaded chunks and execution need to be delayed\nvar __webpack_exports__ = __webpack_require__.O(undefined, [987], () => (__webpack_require__(607)))\n__webpack_exports__ = __webpack_require__.O(__webpack_exports__);\n"],"names":["deferred","he","require","Promise","get_control_option_value","obj","ctrl","id","val","opt","$","concat","attr","checked","replace","includes","handleNVSVisible","nvs_previous_checked","isEnabled","Cookies","recovery","show","hide","undefined","match","window","bootstrap","String","prototype","format","Object","assign","args","arguments","this","number","encodeHTML","encode","Date","toLocalShort","toLocaleString","dateStyle","timeStyle","nvsTypes","btIcons","bt_playing","bt_disconnected","bt_neutral","bt_connecting","bt_connected","bt_disabled","play_arrow","pause","stop","batIcons","icon","label","ranges","f","t","btStateIcons","desc","sub","pillcolors","MESSAGING_INFO","MESSAGING_WARNING","MESSAGING_ERROR","connectReturnCode","OK","FAIL","DISC","LOST","RESTORE","ETH","taskStates","flashState","NONE","REBOOT_TO_RECOVERY","SET_FWURL","FLASHING","DONE","UPLOADING","ERROR","UPLOADCOMPLETE","_state","olderRecovery","statusText","flashURL","flashFileName","statusPercent","Completed","prevRecovery","updateModal","Modal","document","getElementById","reset","UpdateProgress","removeClass","prop","value","isStateError","html","parent","isStateUploadComplete","isStateNone","isStateRebootRecovery","isStateSetUrl","isStateFlashing","isStateDone","isStateUploading","init","SetStateError","addClass","SetStateNone","SetStateRebootRecovery","SetStatusText","ajax","url","context","dataType","method","cache","contentType","data","JSON","stringify","timestamp","now","error","xhr","_ajaxOptions","thrownError","_xhr$status","setOTAError","status","complete","response","SetStateSetUrl","post_config","fwurl","type","SetStateFlashing","SetStateDone","SetStateUploading","SetStateUploadComplete","isFlashExecuting","toString","_this","keys","find","x","setOTATargets","fileInput","files","length","message","SetStatusPercent","ShowDialog","pct","pctChanged","txt","changed","css","text","StartOTA","logEvent","name","TargetReadyStartOTA","UploadLocalFile","xhttp","XMLHttpRequest","boundHandleUploadProgressEvent","HandleUploadProgressEvent","bind","boundsetOTAError","upload","addEventListener","onreadystatechange","readyState","open","send","console","Math","round","loaded","total","EventTargetStatus","_data$ota_pct","_data$ota_dsc","ota_pct","ota_dsc","EventOTAMessageClass","otaData","parse","fun","log","hideSurrounding","presetsloaded","messageInterval","confPayload","config","handleExceptionResponse","parseSqueezeliteCommandLine","commandLine","output","options","otherValues","i","arg","startsWith","option","slice","join","trim","o","indexOf","substring","getOutput","n","getName","otherOptions","btname","toUpperCase","temp","isConnected","ConnectedTo","hasOwnProperty","ip","getIcon","icons","handleTemplateTypeRadio","outtype","children","display","commandDefaults","forEach","key","showLocalMessage","showCmdMessage","cmdname","msgtype","msgtext","append","color","escapedtext","hFlash","handleReboot","link","delayReboot","lmsBaseUrl","releaseURL","messagesHeld","commandBTSinkName","i2s","b","C","W","Z","spdif","bt","validOptions","codecs","messagecount","messageseverity","SystemConfig","LastCommandsState","hostName","versionName","prevmessage","project_name","board_model","platform_name","preset_name","btSinkNamesOptSel","ConnectingToSSID","prevLMSIP","ConnectingToActions","getConfigJson","slimMode","each","_index","entry","nvsType","parseInt","attributes","nvs_type","duration","empty","resolve","delay","then","rdata","HideCmdMessage","getCommands","getConfig","setPlatformFilter","filter","trigger","renderError","fieldname","errorFieldName","errorField","field","after","rssiToIcon","rssi","refreshAP","_ConnectedTo","urc","ajaxSetup","timeout","getJSON","_asyncToGenerator","_regeneratorRuntime","_callee","_context","prev","next","sleep","sort","a","y","refreshAPHTML2","formatAP","ssid","auth","rssi_icon","auth_icon","_ConnectedTo2","h","e","_ConnectedTo$rssi","wifiSelector","prepend","siblings","first","showTask","task","debug","nme","cpu","st","minstk","bprio","cprio","num","getBTSinkOpt","getMessages","_ref2","_callee2","_iterator","_step","_loop","msgTime","statsData","msgparts","attrs","j","curOpt","_context3","_createForOfIteratorHelper","msg","msgAge","_context2","current_time","sent_time","setTime","getTime","t0","abrupt","ntasks","tasks","showMessage","split","is","item","replaceWith","btEntry","remove","s","done","delegateYield","t1","finish","setTimeout","_x","apply","fail","ajaxOptions","handleWifiDialog","gw","netmask","Action","STS","ap_ssid","ap_pwd","setIcons","offline","textContent","handleNetworkStatus","hasConnectionChanged","checkStatus","_data$message","_data$recovery","handleRecoveryMode","tt","bt_status","bt_sub_status","iconindex","handlebtstate","depth","version","Voltage","bat_icon","voltage","_i3","_batIcons","_step2","iconEntry","_iterator2","entryRanges","err","batteryToIcon","is_i2c_locked","lms_ip","lms_port","baseUrl","success","Number","Jack","getLongOps","longopts","values","commands","command","cmdParts","isConfig","targetDiv","innerhtml","help","argtable","placeholder","datatype","ctrlname","curvalue","hasvalue","shortopts","checkbox","extraclass","mincount","glossary","choice","off","on","runCommand","ctrlselector","ctrlValue","_","jqxhr","textStatus","entries","autoexec","parsed","_parsed$options$u$spl","u","_parsed$options$u$spl2","_slicedToArray","resampleValue","resampleInterpolation","processSqueezeliteCommandLine","replaceAll","title","gpio","gpioEntry","fixed","group","severity","ms","reason","_resolve","reject","saveAutoexec1","_get_control_option_v","optStr","resample","_i","_Object$entries","_Object$entries$_i","concatenateOptions","autoexec1","responseText","result","Result","handleDisconnect","handleConnect","pwd","dhcpname","ready","index","not","_get_control_option_v2","invalid","map","test","event","relatedTarget","CONN","MAN","FileReader","file","fr","onload","target","ex","alert","readAsText","slideUp","slideDown","preventDefault","createElement","href","URL","createObjectURL","Blob","setAttribute","body","appendChild","click","removeChild","branches","release","branch","push","fwb","assets","asset","browser_download_url","namecomponents","ver","cfg","bits","substr","lastIndexOf","created_at","idf","trclass","setURL","button","dataset","reboot","cmdstring","fields","allfields","querySelectorAll","selJson","cmd","model_config","_i2","_Object$entries2","_Object$entries2$_i","storedval","handleHWPreset","_step3","_iterator3","_attr$hasvalue","qts","isSelect","hasValue","validVal","_attr$longopts","_attr$shortopts","_attr$hasvalue2","_attr$longopts2","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","exports","module","__webpack_modules__","call","m","O","chunkIds","fn","priority","notFulfilled","Infinity","fulfilled","every","splice","r","getter","__esModule","d","definition","defineProperty","enumerable","get","g","globalThis","Function","Symbol","toStringTag","nmd","paths","installedChunks","chunkId","webpackJsonpCallback","parentChunkLoadingFunction","moreModules","runtime","some","chunkLoadingGlobal","self","__webpack_exports__"],"sourceRoot":""} \ No newline at end of file +{"version":3,"file":"./js/index.1e1c60.bundle.js","mappings":"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","sources":["webpack://squeezelite-esp32/webpack/runtime/chunk loaded","webpack://squeezelite-esp32/./src/js/custom.js","webpack://squeezelite-esp32/./src/index.ts","webpack://squeezelite-esp32/webpack/bootstrap","webpack://squeezelite-esp32/webpack/runtime/compat get default export","webpack://squeezelite-esp32/webpack/runtime/define property getters","webpack://squeezelite-esp32/webpack/runtime/global","webpack://squeezelite-esp32/webpack/runtime/hasOwnProperty shorthand","webpack://squeezelite-esp32/webpack/runtime/make namespace object","webpack://squeezelite-esp32/webpack/runtime/node module decorator","webpack://squeezelite-esp32/webpack/runtime/jsonp chunk loading","webpack://squeezelite-esp32/webpack/startup"],"sourcesContent":["var deferred = [];\n__webpack_require__.O = (result, chunkIds, fn, priority) => {\n\tif(chunkIds) {\n\t\tpriority = priority || 0;\n\t\tfor(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1];\n\t\tdeferred[i] = [chunkIds, fn, priority];\n\t\treturn;\n\t}\n\tvar notFulfilled = Infinity;\n\tfor (var i = 0; i < deferred.length; i++) {\n\t\tvar [chunkIds, fn, priority] = deferred[i];\n\t\tvar fulfilled = true;\n\t\tfor (var j = 0; j < chunkIds.length; j++) {\n\t\t\tif ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every((key) => (__webpack_require__.O[key](chunkIds[j])))) {\n\t\t\t\tchunkIds.splice(j--, 1);\n\t\t\t} else {\n\t\t\t\tfulfilled = false;\n\t\t\t\tif(priority < notFulfilled) notFulfilled = priority;\n\t\t\t}\n\t\t}\n\t\tif(fulfilled) {\n\t\t\tdeferred.splice(i--, 1)\n\t\t\tvar r = fn();\n\t\t\tif (r !== undefined) result = r;\n\t\t}\n\t}\n\treturn result;\n};","var he = require('he');\nvar Promise = require('es6-promise').Promise;\nwindow.bootstrap = require('bootstrap');\nimport Cookies from 'js-cookie';\n\n\n\nif (!String.prototype.format) {\n Object.assign(String.prototype, {\n format() {\n const args = arguments;\n return this.replace(/{(\\d+)}/g, function (match, number) {\n return typeof args[number] !== 'undefined' ? args[number] : match;\n });\n },\n });\n}\nif (!String.prototype.encodeHTML) {\n Object.assign(String.prototype, {\n encodeHTML() {\n return he.encode(this).replace(/\\n/g, '
');\n },\n });\n}\nObject.assign(Date.prototype, {\n toLocalShort() {\n const opt = { dateStyle: 'short', timeStyle: 'short' };\n return this.toLocaleString(undefined, opt);\n },\n});\nfunction get_control_option_value(obj) {\n let ctrl,id,val,opt;\n let radio = false;\n let checked = false;\n if (typeof (obj) === 'string') {\n id = obj;\n ctrl = $(`#${id}`);\n } else {\n id = $(obj).attr('id');\n ctrl = $(obj);\n }\n if(ctrl.attr('type') === 'checkbox'){\n opt = $(obj).checked?id.replace('cmd_opt_', ''):'';\n val = true;\n }\n else {\n opt = id.replace('cmd_opt_', '');\n val = $(obj).val();\n val = `${val.includes(\" \") ? '\"' : ''}${val}${val.includes(\" \") ? '\"' : ''}`;\n }\n\n return { opt, val };\n}\nfunction handleNVSVisible() {\n let nvs_previous_checked = isEnabled(Cookies.get(\"show-nvs\"));\n $('input#show-nvs')[0].checked = nvs_previous_checked;\n if ($('input#show-nvs')[0].checked || recovery) {\n $('*[href*=\"-nvs\"]').show();\n } else {\n $('*[href*=\"-nvs\"]').hide();\n }\n}\nfunction concatenateOptions(options) {\n let commandLine = ' ';\n for (const [option, value] of Object.entries(options)) {\n if (option !== 'n' && option !== 'o') {\n commandLine += `-${option} `;\n if (value !== true) {\n commandLine += `${value} `;\n }\n }\n }\n return commandLine;\n}\n\nfunction isEnabled(val) {\n return val != undefined && typeof val === 'string' && val.match(\"[Yy1]\");\n}\n\nconst nvsTypes = {\n NVS_TYPE_U8: 0x01,\n /*! < Type uint8_t */\n NVS_TYPE_I8: 0x11,\n /*! < Type int8_t */\n NVS_TYPE_U16: 0x02,\n /*! < Type uint16_t */\n NVS_TYPE_I16: 0x12,\n /*! < Type int16_t */\n NVS_TYPE_U32: 0x04,\n /*! < Type uint32_t */\n NVS_TYPE_I32: 0x14,\n /*! < Type int32_t */\n NVS_TYPE_U64: 0x08,\n /*! < Type uint64_t */\n NVS_TYPE_I64: 0x18,\n /*! < Type int64_t */\n NVS_TYPE_STR: 0x21,\n /*! < Type string */\n NVS_TYPE_BLOB: 0x42,\n /*! < Type blob */\n NVS_TYPE_ANY: 0xff /*! < Must be last */,\n};\nconst btIcons = {\n bt_playing: { 'label': '', 'icon': 'media_bluetooth_on' },\n bt_disconnected: { 'label': '', 'icon': 'media_bluetooth_off' },\n bt_neutral: { 'label': '', 'icon': 'bluetooth' },\n bt_connecting: { 'label': '', 'icon': 'bluetooth_searching' },\n bt_connected: { 'label': '', 'icon': 'bluetooth_connected' },\n bt_disabled: { 'label': '', 'icon': 'bluetooth_disabled' },\n play_arrow: { 'label': '', 'icon': 'play_circle_filled' },\n pause: { 'label': '', 'icon': 'pause_circle' },\n stop: { 'label': '', 'icon': 'stop_circle' },\n '': { 'label': '', 'icon': '' }\n};\nconst batIcons = [\n { icon: \"battery_0_bar\", label: 'â–Ē', ranges: [{ f: 5.8, t: 6.8 }, { f: 8.8, t: 10.2 }] },\n { icon: \"battery_2_bar\", label: 'â–Ēâ–Ē', ranges: [{ f: 6.8, t: 7.4 }, { f: 10.2, t: 11.1 }] },\n { icon: \"battery_3_bar\", label: 'â–Ēâ–Ēâ–Ē', ranges: [{ f: 7.4, t: 7.5 }, { f: 11.1, t: 11.25 }] },\n { icon: \"battery_4_bar\", label: 'â–Ēâ–Ēâ–Ēâ–Ē', ranges: [{ f: 7.5, t: 7.8 }, { f: 11.25, t: 11.7 }] }\n];\nconst btStateIcons = [\n { desc: 'Idle', sub: ['bt_neutral'] },\n { desc: 'Discovering', sub: ['bt_connecting'] },\n { desc: 'Discovered', sub: ['bt_connecting'] },\n { desc: 'Unconnected', sub: ['bt_disconnected'] },\n { desc: 'Connecting', sub: ['bt_connecting'] },\n {\n desc: 'Connected',\n sub: ['bt_connected', 'play_arrow', 'bt_playing', 'pause', 'stop'],\n },\n { desc: 'Disconnecting', sub: ['bt_disconnected'] },\n];\n\nconst pillcolors = {\n MESSAGING_INFO: 'badge-success',\n MESSAGING_WARNING: 'badge-warning',\n MESSAGING_ERROR: 'badge-danger',\n};\nconst connectReturnCode = {\n OK: 0,\n FAIL: 1,\n DISC: 2,\n LOST: 3,\n RESTORE: 4,\n ETH: 5\n}\nconst taskStates = {\n 0: 'eRunning',\n /*! < A task is querying the state of itself, so must be running. */\n 1: 'eReady',\n /*! < The task being queried is in a read or pending ready list. */\n 2: 'eBlocked',\n /*! < The task being queried is in the Blocked state. */\n 3: 'eSuspended',\n /*! < The task being queried is in the Suspended state, or is in the Blocked state with an infinite time out. */\n 4: 'eDeleted',\n};\nlet flashState = {\n NONE: 0,\n REBOOT_TO_RECOVERY: 2,\n SET_FWURL: 5,\n FLASHING: 6,\n DONE: 7,\n UPLOADING: 8,\n ERROR: 9,\n UPLOADCOMPLETE: 10,\n _state: -1,\n olderRecovery: false,\n statusText: '',\n flashURL: '',\n flashFileName: '',\n statusPercent: 0,\n Completed: false,\n recovery: false,\n prevRecovery: false,\n updateModal: new bootstrap.Modal(document.getElementById('otadiv'), {}),\n reset: function () {\n\n this.olderRecovery = false;\n this.statusText = '';\n this.statusPercent = -1;\n this.flashURL = '';\n this.flashFileName = undefined;\n this.UpdateProgress();\n $('#rTable tr.release').removeClass('table-success table-warning');\n $('.flact').prop('disabled', false);\n $('#flashfilename').value = null;\n $('#fw-url-input').value = null;\n if (!this.isStateError()) {\n $('span#flash-status').html('');\n $('#fwProgressLabel').parent().removeClass('bg-danger');\n }\n this._state = this.NONE\n return this;\n },\n isStateUploadComplete: function () {\n return this._state == this.UPLOADCOMPLETE;\n },\n isStateError: function () {\n return this._state == this.ERROR;\n },\n isStateNone: function () {\n return this._state == this.NONE;\n },\n isStateRebootRecovery: function () {\n return this._state == this.REBOOT_TO_RECOVERY;\n },\n isStateSetUrl: function () {\n return this._state == this.SET_FWURL;\n },\n isStateFlashing: function () {\n return this._state == this.FLASHING;\n },\n isStateDone: function () {\n return this._state == this.DONE;\n },\n isStateUploading: function () {\n return this._state == this.UPLOADING;\n },\n init: function () {\n this._state = this.NONE;\n return this;\n },\n\n SetStateError: function () {\n this._state = this.ERROR;\n $('#fwProgressLabel').parent().addClass('bg-danger');\n return this;\n },\n SetStateNone: function () {\n this._state = this.NONE;\n return this;\n },\n SetStateRebootRecovery: function () {\n this._state = this.REBOOT_TO_RECOVERY;\n // Reboot system to recovery mode\n this.SetStatusText('Starting recovery mode.')\n $.ajax({\n url: '/recovery.json',\n context: this,\n dataType: 'text',\n method: 'POST',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify({\n timestamp: Date.now(),\n }),\n error: function (xhr, _ajaxOptions, thrownError) {\n this.setOTAError(`Unexpected error while trying to restart to recovery. (status=${xhr.status ?? ''}, error=${thrownError ?? ''} ) `);\n },\n complete: function (response) {\n this.SetStatusText('Waiting for system to boot.')\n },\n });\n return this;\n },\n SetStateSetUrl: function () {\n this._state = this.SET_FWURL;\n this.statusText = 'Sending firmware download location.';\n let confData = {\n fwurl: {\n value: this.flashURL,\n type: 33,\n }\n };\n post_config(confData);\n return this;\n },\n SetStateFlashing: function () {\n this._state = this.FLASHING;\n return this;\n },\n SetStateDone: function () {\n this._state = this.DONE;\n this.reset();\n return this;\n },\n SetStateUploading: function () {\n this._state = this.UPLOADING;\n return this.SetStatusText('Sending file to device.');\n },\n SetStateUploadComplete: function () {\n this._state = this.UPLOADCOMPLETE;\n return this;\n },\n\n isFlashExecuting: function () {\n return true === (this._state != this.UPLOADING && (this.statusText !== '' || this.statusPercent >= 0));\n },\n\n\n\n toString: function () {\n let keys = Object.keys(this);\n return keys.find(x => this[x] === this._state);\n },\n\n setOTATargets: function () {\n this.flashURL = '';\n this.flashFileName = '';\n this.flashURL = $('#fw-url-input').val();\n let fileInput = $('#flashfilename')[0].files;\n if (fileInput.length > 0) {\n this.flashFileName = fileInput[0];\n }\n if (this.flashFileName.length == 0 && this.flashURL.length == 0) {\n this.setOTAError('Invalid url or file. Cannot start OTA');\n }\n return this;\n },\n\n setOTAError: function (message) {\n this.SetStateError().SetStatusPercent(0).SetStatusText(message).reset();\n return this;\n },\n\n ShowDialog: function () {\n if (!this.isStateNone()) {\n this.updateModal.show();\n $('.flact').prop('disabled', true);\n }\n return this;\n },\n\n SetStatusPercent: function (pct) {\n var pctChanged = (this.statusPercent != pct);\n this.statusPercent = pct;\n if (pctChanged) {\n if (!this.isStateUploading() && !this.isStateFlashing()) {\n this.SetStateFlashing();\n }\n if (pct == 100) {\n if (this.isStateFlashing()) {\n this.SetStateDone();\n }\n else if (this.isStateUploading()) {\n this.statusPercent = 0;\n this.SetStateFlashing();\n }\n }\n this.UpdateProgress().ShowDialog();\n }\n return this;\n },\n SetStatusText: function (txt) {\n var changed = (this.statusText != txt);\n this.statusText = txt;\n if (changed) {\n $('span#flash-status').html(this.statusText);\n this.ShowDialog();\n }\n\n return this;\n },\n UpdateProgress: function () {\n $('.progress-bar')\n .css('width', this.statusPercent + '%')\n .attr('aria-valuenow', this.statusPercent)\n .text(this.statusPercent + '%')\n $('.progress-bar').html((this.isStateDone() ? 100 : this.statusPercent) + '%');\n return this;\n },\n StartOTA: function () {\n this.logEvent(this.StartOTA.name);\n $('#fwProgressLabel').parent().removeClass('bg-danger');\n this.setOTATargets();\n if (this.isStateError()) {\n return this;\n }\n if (!recovery) {\n this.SetStateRebootRecovery();\n }\n else {\n this.SetStateFlashing().TargetReadyStartOTA();\n }\n\n return this;\n },\n UploadLocalFile: function () {\n this.SetStateUploading();\n const xhttp = new XMLHttpRequest();\n xhttp.context = this;\n var boundHandleUploadProgressEvent = this.HandleUploadProgressEvent.bind(this);\n var boundsetOTAError = this.setOTAError.bind(this);\n xhttp.upload.addEventListener(\"progress\", boundHandleUploadProgressEvent, false);\n xhttp.onreadystatechange = function () {\n if (xhttp.readyState === 4) {\n if (xhttp.status === 0 || xhttp.status === 404) {\n boundsetOTAError(`Upload Failed. Recovery version might not support uploading. Please use web update instead.`);\n }\n }\n };\n xhttp.open('POST', '/flash.json', true);\n xhttp.send(this.flashFileName);\n },\n TargetReadyStartOTA: function () {\n if (recovery && this.prevRecovery && !this.isStateRebootRecovery() && !this.isStateFlashing()) {\n // this should only execute once, while being in a valid state\n return this;\n }\n\n this.logEvent(this.TargetReadyStartOTA.name);\n if (!recovery) {\n console.error('Event TargetReadyStartOTA fired in the wrong mode ');\n return this;\n }\n this.prevRecovery = true;\n\n if (this.flashFileName !== '') {\n this.UploadLocalFile();\n }\n else if (this.flashURL != '') {\n this.SetStateSetUrl();\n }\n else {\n this.setOTAError('Invalid URL or file name while trying to start the OTa process')\n }\n },\n HandleUploadProgressEvent: function (data) {\n this.logEvent(this.HandleUploadProgressEvent.name);\n this.SetStateUploading().SetStatusPercent(Math.round(data.loaded / data.total * 100)).SetStatusText('Uploading file to device');\n },\n EventTargetStatus: function (data) {\n if (!this.isStateNone()) {\n this.logEvent(this.EventTargetStatus.name);\n }\n if (data.ota_pct ?? -1 >= 0) {\n this.olderRecovery = true;\n this.SetStatusPercent(data.ota_pct);\n }\n if ((data.ota_dsc ?? '') != '') {\n this.olderRecovery = true;\n this.SetStatusText(data.ota_dsc);\n }\n\n if (data.recovery != undefined) {\n this.recovery = data.recovery === 1 ? true : false;\n }\n if (this.isStateRebootRecovery() && this.recovery) {\n this.TargetReadyStartOTA();\n }\n },\n EventOTAMessageClass: function (data) {\n this.logEvent(this.EventOTAMessageClass.name);\n var otaData = JSON.parse(data);\n this.SetStatusPercent(otaData.ota_pct).SetStatusText(otaData.ota_dsc);\n },\n logEvent: function (fun) {\n console.log(`${fun}, flash state ${this.toString()}, recovery: ${this.recovery}, ota pct: ${this.statusPercent}, ota desc: ${this.statusText}`);\n }\n\n};\nwindow.hideSurrounding = function (obj) {\n $(obj).parent().parent().hide();\n}\n\nlet presetsloaded = false;\nlet is_i2c_locked = false;\nlet statusInterval = 2000;\nlet messageInterval = 2500;\nfunction post_config(data) {\n let confPayload = {\n timestamp: Date.now(),\n config: data\n };\n $.ajax({\n url: '/config.json',\n dataType: 'text',\n method: 'POST',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify(confPayload),\n error: handleExceptionResponse,\n });\n}\n\n\nwindow.hFlash = function () {\n // reset file upload selection if any;\n $('#flashfilename').value = null\n flashState.StartOTA();\n}\nwindow.handleReboot = function (link) {\n if (link == 'reboot_ota') {\n $('#reboot_ota_nav').removeClass('active').prop(\"disabled\", true); delayReboot(500, '', 'reboot_ota');\n }\n else {\n $('#reboot_nav').removeClass('active'); delayReboot(500, '', link);\n }\n}\n\nfunction parseSqueezeliteCommandLine(commandLine) {\n const options = {};\n let output, name;\n let otherValues = '';\n\n const argRegex = /(\"[^\"]+\"|'[^']+'|\\S+)/g;\n const args = commandLine.match(argRegex);\n\n let i = 0;\n\n while (i < args.length) {\n const arg = args[i];\n\n if (arg.startsWith('-')) {\n const option = arg.slice(1);\n\n if (option === '') {\n otherValues += args.slice(i).join(' ');\n break;\n }\n\n let value = true;\n\n if (i + 1 < args.length && !args[i + 1].startsWith('-')) {\n value = args[i + 1].replace(/\"/g, '').replace(/'/g, '');\n i++;\n }\n\n options[option] = value;\n } else {\n otherValues += arg + ' ';\n }\n\n i++;\n }\n\n otherValues = otherValues.trim();\n output = getOutput(options);\n name = getName(options);\n let otherOptions={btname:null,n:null};\n // assign o and n options to otheroptions if present\n if (options.o && output.toUpperCase() === 'BT') {\n let temp = parseSqueezeliteCommandLine(options.o);\n if(temp.name) {\n otherOptions.btname = temp.name;\n }\n delete options.o;\n }\n if (options.n) {\n otherOptions['n'] = options.n;\n delete options.n;\n }\n return { name, output, options, otherValues,otherOptions }; \n}\n\nfunction getOutput(options) {\n let output;\n if (options.o){\n output = options.o.replace(/\"/g, '').replace(/'/g, '');\n /* set output as the first alphanumerical word in the command line */\n if (output.indexOf(' ') > 0) {\n output = output.substring(0, output.indexOf(' '));\n }\n }\n return output;\n}\n\nfunction getName(options) {\n let name;\n /* if n option present, assign to name variable */\n if (options.n){\n name = options.n.replace(/\"/g, '').replace(/'/g, '');\n }\n return name;\n}\n\n\nfunction isConnected() {\n return ConnectedTo.hasOwnProperty('ip') && ConnectedTo.ip != '0.0.0.0' && ConnectedTo.ip != '';\n}\nfunction getIcon(icons) {\n return isConnected() ? icons.icon : icons.label;\n}\nfunction handlebtstate(data) {\n let icon = '';\n let tt = '';\n if (data.bt_status !== undefined && data.bt_sub_status !== undefined) {\n const iconindex = btStateIcons[data.bt_status].sub[data.bt_sub_status];\n if (iconindex) {\n icon = btIcons[iconindex];\n tt = btStateIcons[data.bt_status].desc;\n } else {\n icon = btIcons.bt_connected;\n tt = 'Output status';\n }\n }\n\n $('#o_type').attr('title', tt);\n $('#o_bt').html(isConnected() ? icon.label : icon.text);\n}\nfunction handleTemplateTypeRadio(outtype) {\n $('#o_type').children('span').css({ display: 'none' });\n let changed = false;\n if (outtype === 'bt') {\n changed = output !== 'bt' && output !== '';\n output = 'bt';\n } else if (outtype === 'spdif') {\n changed = output !== 'spdif' && output !== '';\n output = 'spdif';\n } else {\n changed = output !== 'i2s' && output !== '';\n output = 'i2s';\n }\n $('#' + output).prop('checked', true);\n $('#o_' + output).css({ display: 'inline' });\n if (changed) {\n Object.keys(commandDefaults[output]).forEach(function (key) {\n $(`#cmd_opt_${key}`).val(commandDefaults[output][key]);\n });\n }\n}\n\nfunction handleExceptionResponse(xhr, _ajaxOptions, thrownError) {\n console.log(xhr.status);\n console.log(thrownError);\n if (thrownError !== '') {\n showLocalMessage(thrownError, 'MESSAGING_ERROR');\n }\n}\nfunction HideCmdMessage(cmdname) {\n $('#toast_' + cmdname)\n .removeClass('table-success')\n .removeClass('table-warning')\n .removeClass('table-danger')\n .addClass('table-success')\n .removeClass('show');\n $('#msg_' + cmdname).html('');\n}\nfunction showCmdMessage(cmdname, msgtype, msgtext, append = false) {\n let color = 'table-success';\n if (msgtype === 'MESSAGING_WARNING') {\n color = 'table-warning';\n } else if (msgtype === 'MESSAGING_ERROR') {\n color = 'table-danger';\n }\n $('#toast_' + cmdname)\n .removeClass('table-success')\n .removeClass('table-warning')\n .removeClass('table-danger')\n .addClass(color)\n .addClass('show');\n let escapedtext = msgtext\n .substring(0, msgtext.length - 1)\n .encodeHTML()\n .replace(/\\n/g, '
');\n escapedtext =\n ($('#msg_' + cmdname).html().length > 0 && append\n ? $('#msg_' + cmdname).html() + '
'\n : '') + escapedtext;\n $('#msg_' + cmdname).html(escapedtext);\n}\n\nlet releaseURL =\n 'https://api.github.com/repos/sle118/squeezelite-esp32/releases';\n\nlet recovery = false;\nlet messagesHeld = false;\nlet commandBTSinkName = '';\nconst commandHeader = 'squeezelite ';\nconst commandDefaults = {\n i2s: { b: \"500:2000\", C: \"30\", W: \"\", Z: \"96000\", o: \"I2S\" },\n spdif: { b: \"500:2000\", C: \"30\", W: \"\", Z: \"48000\", o: \"SPDIF\" },\n bt: { b: \"500:2000\", C: \"30\", W: \"\", Z: \"44100\", o: \"BT\" },\n};\nlet validOptions = {\n codecs: ['flac', 'pcm', 'mp3', 'ogg', 'aac', 'wma', 'alac', 'dsd', 'mad', 'mpg']\n};\n\n//let blockFlashButton = false;\nlet apList = null;\n//let selectedSSID = '';\n//let checkStatusInterval = null;\nlet messagecount = 0;\nlet messageseverity = 'MESSAGING_INFO';\nlet SystemConfig = {};\nlet LastCommandsState = null;\nvar output = '';\nlet hostName = '';\nlet versionName = 'Squeezelite-ESP32';\nlet prevmessage = '';\nlet project_name = versionName;\nlet depth = 16;\nlet board_model = '';\nlet platform_name = versionName;\nlet preset_name = '';\nlet btSinkNamesOptSel = '#cfg-audio-bt_source-sink_name';\nlet ConnectedTo = {};\nlet ConnectingToSSID = {};\nlet lmsBaseUrl;\nlet prevLMSIP = '';\nconst ConnectingToActions = {\n 'CONN': 0, 'MAN': 1, 'STS': 2,\n}\n\nPromise.prototype.delay = function (duration) {\n return this.then(\n function (value) {\n return new Promise(function (resolve) {\n setTimeout(function () {\n resolve(value);\n }, duration);\n });\n },\n function (reason) {\n return new Promise(function (_resolve, reject) {\n setTimeout(function () {\n reject(reason);\n }, duration);\n });\n }\n );\n};\n\nfunction getConfigJson(slimMode) {\n const config = {};\n $('input.nvs').each(function (_index, entry) {\n if (!slimMode) {\n const nvsType = parseInt(entry.attributes.nvs_type.value, 10);\n if (entry.id !== '') {\n config[entry.id] = {};\n if (\n nvsType === nvsTypes.NVS_TYPE_U8 ||\n nvsType === nvsTypes.NVS_TYPE_I8 ||\n nvsType === nvsTypes.NVS_TYPE_U16 ||\n nvsType === nvsTypes.NVS_TYPE_I16 ||\n nvsType === nvsTypes.NVS_TYPE_U32 ||\n nvsType === nvsTypes.NVS_TYPE_I32 ||\n nvsType === nvsTypes.NVS_TYPE_U64 ||\n nvsType === nvsTypes.NVS_TYPE_I64\n ) {\n config[entry.id].value = parseInt(entry.value);\n } else {\n config[entry.id].value = entry.value;\n }\n config[entry.id].type = nvsType;\n }\n } else {\n config[entry.id] = entry.value;\n }\n });\n const key = $('#nvs-new-key').val();\n const val = $('#nvs-new-value').val();\n if (key !== '') {\n if (!slimMode) {\n config[key] = {};\n config[key].value = val;\n config[key].type = 33;\n } else {\n config[key] = val;\n }\n }\n return config;\n}\n\nfunction handleHWPreset(allfields, reboot) {\n\n const selJson = JSON.parse(allfields[0].value);\n var cmd = allfields[0].attributes.cmdname.value;\n\n console.log(`selected model: ${selJson.name}`);\n let confPayload = {\n timestamp: Date.now(),\n config: { model_config: { value: selJson.name, type: 33 } }\n };\n for (const [name, value] of Object.entries(selJson.config)) {\n const storedval = (typeof value === 'string' || value instanceof String) ? value : JSON.stringify(value);\n confPayload.config[name] = {\n value: storedval,\n type: 33,\n }\n showCmdMessage(\n cmd,\n 'MESSAGING_INFO',\n `Setting ${name}=${storedval} `,\n true\n );\n }\n\n showCmdMessage(\n cmd,\n 'MESSAGING_INFO',\n `Committing `,\n true\n );\n $.ajax({\n url: '/config.json',\n dataType: 'text',\n method: 'POST',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify(confPayload),\n error: function (xhr, _ajaxOptions, thrownError) {\n handleExceptionResponse(xhr, _ajaxOptions, thrownError);\n showCmdMessage(\n cmd,\n 'MESSAGING_ERROR',\n `Unexpected error ${(thrownError !== '') ? thrownError : 'with return status = ' + xhr.status} `,\n true\n );\n },\n success: function (response) {\n showCmdMessage(\n cmd,\n 'MESSAGING_INFO',\n `Saving complete `,\n true\n );\n console.log(response);\n if (reboot) {\n delayReboot(2500, cmd);\n }\n },\n });\n}\n\n\n// pull json file from https://gist.githubusercontent.com/sle118/dae585e157b733a639c12dc70f0910c5/raw/b462691f69e2ad31ac95c547af6ec97afb0f53db/squeezelite-esp32-presets.json and\nfunction loadPresets() {\n if ($(\"#cfg-hw-preset-model_config\").length == 0) return;\n if (presetsloaded) return;\n presetsloaded = true;\n $('#cfg-hw-preset-model_config').html('');\n $.getJSON(\n 'https://gist.githubusercontent.com/sle118/dae585e157b733a639c12dc70f0910c5/raw/',\n { _: new Date().getTime() },\n function (data) {\n $.each(data, function (key, val) {\n $('#cfg-hw-preset-model_config').append(``);\n if (preset_name !== '' && preset_name == val.name) {\n $('#cfg-hw-preset-model_config').val(preset_name);\n }\n });\n if (preset_name !== '') {\n ('#prev_preset').show().val(preset_name);\n }\n }\n\n ).fail(function (jqxhr, textStatus, error) {\n const err = textStatus + ', ' + error;\n console.log('Request Failed: ' + err);\n }\n );\n}\n\nfunction delayReboot(duration, cmdname, ota = 'reboot') {\n const url = '/' + ota + '.json';\n $('tbody#tasks').empty();\n $('#tasks_sect').css('visibility', 'collapse');\n Promise.resolve({ cmdname: cmdname, url: url })\n .delay(duration)\n .then(function (data) {\n if (data.cmdname.length > 0) {\n showCmdMessage(\n data.cmdname,\n 'MESSAGING_WARNING',\n 'System is rebooting.\\n',\n true\n );\n } else {\n showLocalMessage('System is rebooting.\\n', 'MESSAGING_WARNING');\n }\n console.log('now triggering reboot');\n $(\"button[onclick*='handleReboot']\").addClass('rebooting');\n $.ajax({\n url: data.url,\n dataType: 'text',\n method: 'POST',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify({\n timestamp: Date.now(),\n }),\n error: handleExceptionResponse,\n complete: function () {\n console.log('reboot call completed');\n Promise.resolve(data)\n .delay(6000)\n .then(function (rdata) {\n if (rdata.cmdname.length > 0) {\n HideCmdMessage(rdata.cmdname);\n }\n getCommands();\n getConfig();\n });\n },\n });\n });\n}\n// eslint-disable-next-line no-unused-vars\nwindow.saveAutoexec1 = function (apply) {\n showCmdMessage('cfg-audio-tmpl', 'MESSAGING_INFO', 'Saving.\\n', false);\n let commandLine = `${commandHeader} -o ${output} `;\n $('.sqcmd').each(function () {\n let { opt, val } = get_control_option_value($(this));\n if ((opt && opt.length>0 ) && typeof(val) == 'boolean' || val.length > 0) {\n const optStr=opt===':'?opt:(` -${opt} `);\n val = typeof(val) == 'boolean'?'':val;\n commandLine += `${optStr} ${val}`;\n }\n });\n const resample=$('#cmd_opt_R input[name=resample]:checked');\n if (resample.length>0 && resample.attr('suffix')!=='') {\n commandLine += resample.attr('suffix');\n // now check resample_i option and if checked, add suffix to command line\n if ($('#resample_i').is(\":checked\") && resample.attr('aint') =='true') {\n commandLine += $('#resample_i').attr('suffix');\n }\n}\n\n \n if (output === 'bt') {\n showCmdMessage(\n 'cfg-audio-tmpl',\n 'MESSAGING_INFO',\n 'Remember to configure the Bluetooth audio device name.\\n',\n true\n );\n }\n commandLine += concatenateOptions(options);\n const data = {\n timestamp: Date.now(),\n };\n data.config = {\n autoexec1: { value: commandLine, type: 33 },\n autoexec: {\n value: $('#disable-squeezelite').prop('checked') ? '0' : '1',\n type: 33,\n },\n };\n\n $.ajax({\n url: '/config.json',\n dataType: 'text',\n method: 'POST',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify(data),\n error: handleExceptionResponse,\n complete: function (response) {\n if (\n response.responseText &&\n JSON.parse(response.responseText).result === 'OK'\n ) {\n showCmdMessage('cfg-audio-tmpl', 'MESSAGING_INFO', 'Done.\\n', true);\n if (apply) {\n delayReboot(1500, 'cfg-audio-tmpl');\n }\n } else if (JSON.parse(response.responseText).result) {\n showCmdMessage(\n 'cfg-audio-tmpl',\n 'MESSAGING_WARNING',\n JSON.parse(response.responseText).Result + '\\n',\n true\n );\n } else {\n showCmdMessage(\n 'cfg-audio-tmpl',\n 'MESSAGING_ERROR',\n response.statusText + '\\n'\n );\n }\n console.log(response.responseText);\n },\n });\n console.log('sent data:', JSON.stringify(data));\n}\nwindow.handleDisconnect = function () {\n $.ajax({\n url: '/connect.json',\n dataType: 'text',\n method: 'DELETE',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify({\n timestamp: Date.now(),\n }),\n });\n}\nfunction setPlatformFilter(val) {\n if ($('.upf').filter(function () { return $(this).text().toUpperCase() === val.toUpperCase() }).length > 0) {\n $('#splf').val(val).trigger('input');\n return true;\n }\n return false;\n}\nwindow.handleConnect = function () {\n ConnectingToSSID.ssid = $('#manual_ssid').val();\n ConnectingToSSID.pwd = $('#manual_pwd').val();\n ConnectingToSSID.dhcpname = $('#dhcp-name2').val();\n $(\"*[class*='connecting']\").hide();\n $('#ssid-wait').text(ConnectingToSSID.ssid);\n $('.connecting').show();\n $.ajax({\n url: '/connect.json',\n dataType: 'text',\n method: 'POST',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify({\n timestamp: Date.now(),\n ssid: ConnectingToSSID.ssid,\n pwd: ConnectingToSSID.pwd\n }),\n error: handleExceptionResponse,\n });\n\n // now we can re-set the intervals regardless of result\n\n}\nfunction renderError(opt,error){\n const fieldname = `cmd_opt_${opt}`;\n let errorFieldName=`${fieldname}-error`;\n let errorField=$(`#${errorFieldName}`);\n let field=$(`#${fieldname}`);\n \n if (!errorField || errorField.length ==0) {\n field.after(`
`);\n errorField=$(`#${errorFieldName}`);\n }\n if(error.length ==0){\n errorField.hide();\n field.removeClass('is-invalid');\n field.addClass('is-valid');\n errorField.text('');\n }\n else { \n errorField.show();\n errorField.text(error);\n field.removeClass('is-valid');\n field.addClass('is-invalid');\n }\n return errorField;\n}\n$(document).ready(function () {\n $('.material-icons').each(function (_index, entry) {\n entry.attributes['icon'] = entry.textContent;\n });\n setIcons(true);\n handleNVSVisible();\n flashState.init();\n $('#fw-url-input').on('input', function () {\n if ($(this).val().length > 8 && ($(this).val().startsWith('http://') || $(this).val().startsWith('https://'))) {\n $('#start-flash').show();\n }\n else {\n $('#start-flash').hide();\n }\n });\n $('.upSrch').on('input', function () {\n const val = this.value;\n $(\"#rTable tr\").removeClass(this.id + '_hide');\n if (val.length > 0) {\n $(`#rTable td:nth-child(${$(this).parent().index() + 1})`).filter(function () {\n return !$(this).text().toUpperCase().includes(val.toUpperCase());\n }).parent().addClass(this.id + '_hide');\n }\n $('[class*=\"_hide\"]').hide();\n $('#rTable tr').not('[class*=\"_hide\"]').show()\n\n });\n setTimeout(refreshAP, 1500);\n /* add validation for cmd_opt_c, which accepts a comma separated list. \n getting known codecs from validOptions.codecs array\n use bootstrap classes to highlight the error with an overlay message */\n $('#options input').on('input', function () {\n const { opt, val } = get_control_option_value(this);\n if (opt === 'c' || opt === 'e') {\n const fieldname = `cmd_opt_${opt}_codec-error`;\n \n const values = val.split(',').map(function (item) {\n return item.trim();\n });\n /* get a list of invalid codecs */\n const invalid = values.filter(function (item) {\n return !validOptions.codecs.includes(item);\n });\n renderError(opt,invalid.length > 0 ? `Invalid codec(s) ${invalid.join(', ')}` : '');\n }\n /* add validation for cmd_opt_m, which accepts a mac_address */\n if (opt === 'm') {\n const mac_regex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;\n renderError(opt,mac_regex.test(val) ? '' : 'Invalid MAC address');\n }\n if (opt === 'r') {\n const rateRegex = /^(\\d+\\.?\\d*|\\.\\d+)-(\\d+\\.?\\d*|\\.\\d+)$|^(\\d+\\.?\\d*)$|^(\\d+\\.?\\d*,)+\\d+\\.?\\d*$/;\n renderError(opt,rateRegex.test(val)?'':`Invalid rate(s) ${val}. Acceptable format: |-|,,`);\n }\n\n\n\n }\n\n\n );\n\n\n\n\n\n $('#WifiConnectDialog')[0].addEventListener('shown.bs.modal', function (event) {\n $(\"*[class*='connecting']\").hide();\n\n if (event?.relatedTarget) {\n ConnectingToSSID.Action = ConnectingToActions.CONN;\n if ($(event.relatedTarget).children('td:eq(1)').text() == ConnectedTo.ssid) {\n ConnectingToSSID.Action = ConnectingToActions.STS;\n }\n else {\n if (!$(event.relatedTarget).is(':last-child')) {\n ConnectingToSSID.ssid = $(event.relatedTarget).children('td:eq(1)').text();\n $('#manual_ssid').val(ConnectingToSSID.ssid);\n }\n else {\n ConnectingToSSID.Action = ConnectingToActions.MAN;\n ConnectingToSSID.ssid = '';\n $('#manual_ssid').val(ConnectingToSSID.ssid);\n }\n }\n }\n\n\n if (ConnectingToSSID.Action !== ConnectingToActions.STS) {\n $('.connecting-init').show();\n $('#manual_ssid').trigger('focus');\n }\n else {\n handleWifiDialog();\n }\n });\n\n $('#WifiConnectDialog')[0].addEventListener('hidden.bs.modal', function () {\n $('#WifiConnectDialog input').val('');\n });\n\n $('#uCnfrm')[0].addEventListener('shown.bs.modal', function () {\n $('#selectedFWURL').text($('#fw-url-input').val());\n });\n\n $('input#show-commands')[0].checked = LastCommandsState === 1;\n $('a[href^=\"#tab-commands\"]').hide();\n $('#load-nvs').on('click', function () {\n $('#nvsfilename').trigger('click');\n });\n $('#nvsfilename').on('change', function () {\n if (typeof window.FileReader !== 'function') {\n throw \"The file API isn't supported on this browser.\";\n }\n if (!this.files) {\n throw 'This browser does not support the `files` property of the file input.';\n }\n if (!this.files[0]) {\n return undefined;\n }\n\n const file = this.files[0];\n let fr = new FileReader();\n fr.onload = function (e) {\n let data = {};\n try {\n data = JSON.parse(e.target.result);\n } catch (ex) {\n alert('Parsing failed!\\r\\n ' + ex);\n }\n $('input.nvs').each(function (_index, entry) {\n $(this).parent().removeClass('bg-warning').removeClass('bg-success');\n if (data[entry.id]) {\n if (data[entry.id] !== entry.value) {\n console.log(\n 'Changed ' + entry.id + ' ' + entry.value + '==>' + data[entry.id]\n );\n $(this).parent().addClass('bg-warning');\n $(this).val(data[entry.id]);\n }\n else {\n $(this).parent().addClass('bg-success');\n }\n }\n });\n var changed = $(\"input.nvs\").children('.bg-warning');\n if (changed) {\n alert('Highlighted values were changed. Press Commit to change on the device');\n }\n }\n fr.readAsText(file);\n this.value = null;\n\n }\n );\n $('#clear-syslog').on('click', function () {\n messagecount = 0;\n messageseverity = 'MESSAGING_INFO';\n $('#msgcnt').text('');\n $('#syslogTable').html('');\n });\n\n $('#ok-credits').on('click', function () {\n $('#credits').slideUp('fast', function () { });\n $('#app').slideDown('fast', function () { });\n });\n\n $('#acredits').on('click', function (event) {\n event.preventDefault();\n $('#app').slideUp('fast', function () { });\n $('#credits').slideDown('fast', function () { });\n });\n\n $('input#show-commands').on('click', function () {\n this.checked = this.checked ? 1 : 0;\n if (this.checked) {\n $('a[href^=\"#tab-commands\"]').show();\n LastCommandsState = 1;\n } else {\n LastCommandsState = 0;\n $('a[href^=\"#tab-commands\"]').hide();\n }\n });\n\n $('input#show-nvs').on('click', function () {\n this.checked = this.checked ? 1 : 0;\n Cookies.set(\"show-nvs\", this.checked ? 'Y' : 'N');\n handleNVSVisible();\n });\n $('#btn_reboot_recovery').on('click', function () {\n handleReboot('recovery');\n });\n $('#btn_reboot').on('click', function () {\n handleReboot('reboot');\n });\n $('#btn_flash').on('click', function () {\n hFlash();\n });\n $('#save-autoexec1').on('click', function () {\n saveAutoexec1(false);\n });\n $('#commit-autoexec1').on('click', function () {\n saveAutoexec1(true);\n });\n $('#btn_disconnect').on('click', function () {\n ConnectedTo = {};\n refreshAPHTML2();\n $.ajax({\n url: '/connect.json',\n dataType: 'text',\n method: 'DELETE',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify({\n timestamp: Date.now(),\n }),\n });\n });\n $('#btnJoin').on('click', function () {\n handleConnect();\n });\n $('#reboot_nav').on('click', function () {\n handleReboot('reboot');\n });\n $('#reboot_ota_nav').on('click', function () {\n handleReboot('reboot_ota');\n });\n\n $('#save-as-nvs').on('click', function () {\n const config = getConfigJson(true);\n const a = document.createElement('a');\n a.href = URL.createObjectURL(\n new Blob([JSON.stringify(config, null, 2)], {\n type: 'text/plain',\n })\n );\n a.setAttribute(\n 'download',\n 'nvs_config_' + hostName + '_' + Date.now() + 'json'\n );\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n });\n\n $('#save-nvs').on('click', function () {\n post_config(getConfigJson(false));\n });\n\n $('#fwUpload').on('click', function () {\n const fileInput = document.getElementById('flashfilename').files;\n if (fileInput.length === 0) {\n alert('No file selected!');\n } else {\n $('#fw-url-input').value = null;\n flashState.StartOTA();\n }\n\n });\n $('[name=output-tmpl]').on('click', function () {\n handleTemplateTypeRadio(this.id);\n });\n\n $('#chkUpdates').on('click', function () {\n $('#rTable').html('');\n $.getJSON(releaseURL, function (data) {\n let i = 0;\n const branches = [];\n data.forEach(function (release) {\n const namecomponents = release.name.split('#');\n const branch = namecomponents[3];\n if (!branches.includes(branch)) {\n branches.push(branch);\n }\n });\n let fwb = '';\n branches.forEach(function (branch) {\n fwb += '';\n });\n $('#fwbranch').append(fwb);\n\n data.forEach(function (release) {\n let url = '';\n release.assets.forEach(function (asset) {\n if (asset.name.match(/\\.bin$/)) {\n url = asset.browser_download_url;\n }\n });\n const namecomponents = release.name.split('#');\n const ver = namecomponents[0];\n const cfg = namecomponents[2];\n const branch = namecomponents[3];\n var bits = ver.substr(ver.lastIndexOf('-') + 1);\n bits = (bits == '32' || bits == '16') ? bits : '';\n\n let body = release.body;\n body = body.replace(/'/gi, '\"');\n body = body.replace(\n /[\\s\\S]+(### Revision Log[\\s\\S]+)### ESP-IDF Version Used[\\s\\S]+/,\n '$1'\n );\n body = body.replace(/- \\(.+?\\) /g, '- ').encodeHTML();\n $('#rTable').append(`\n ${ver}${new Date(release.created_at).toLocalShort()}\n ${cfg}${branch}${bits}`\n );\n });\n if (i > 7) {\n $('#releaseTable').append(\n \"\" +\n \"\" +\n \"\" +\n '' +\n ''\n );\n $('#showallbutton').on('click', function () {\n $('tr.hide').removeClass('hide');\n $('tr#showall').addClass('hide');\n });\n }\n $('#searchfw').css('display', 'inline');\n if (!setPlatformFilter(platform_name)) {\n setPlatformFilter(project_name)\n }\n $('#rTable tr.release').on('click', function () {\n var url = this.attributes['fwurl'].value;\n if (lmsBaseUrl) {\n url = url.replace(/.*\\/download\\//, lmsBaseUrl + '/plugins/SqueezeESP32/firmware/');\n }\n $('#fw-url-input').val(url);\n $('#start-flash').show();\n $('#rTable tr.release').removeClass('table-success table-warning');\n $(this).addClass('table-success table-warning');\n });\n\n }).fail(function () {\n alert('failed to fetch release history!');\n });\n });\n $('#fwcheck').on('click', function () {\n $('#releaseTable').html('');\n $('#fwbranch').empty();\n $.getJSON(releaseURL, function (data) {\n let i = 0;\n const branches = [];\n data.forEach(function (release) {\n const namecomponents = release.name.split('#');\n const branch = namecomponents[3];\n if (!branches.includes(branch)) {\n branches.push(branch);\n }\n });\n let fwb;\n branches.forEach(function (branch) {\n fwb += '';\n });\n $('#fwbranch').append(fwb);\n\n data.forEach(function (release) {\n let url = '';\n release.assets.forEach(function (asset) {\n if (asset.name.match(/\\.bin$/)) {\n url = asset.browser_download_url;\n }\n });\n const namecomponents = release.name.split('#');\n const ver = namecomponents[0];\n const idf = namecomponents[1];\n const cfg = namecomponents[2];\n const branch = namecomponents[3];\n\n let body = release.body;\n body = body.replace(/'/gi, '\"');\n body = body.replace(\n /[\\s\\S]+(### Revision Log[\\s\\S]+)### ESP-IDF Version Used[\\s\\S]+/,\n '$1'\n );\n body = body.replace(/- \\(.+?\\) /g, '- ');\n const trclass = i++ > 6 ? ' hide' : '';\n $('#releaseTable').append(\n \"\" +\n \"\" +\n ver +\n '' +\n '' +\n new Date(release.created_at).toLocalShort() +\n '' +\n '' +\n cfg +\n '' +\n '' +\n idf +\n '' +\n '' +\n branch +\n '' +\n \"\" +\n ''\n );\n });\n if (i > 7) {\n $('#releaseTable').append(\n \"\" +\n \"\" +\n \"\" +\n '' +\n ''\n );\n $('#showallbutton').on('click', function () {\n $('tr.hide').removeClass('hide');\n $('tr#showall').addClass('hide');\n });\n }\n $('#searchfw').css('display', 'inline');\n }).fail(function () {\n alert('failed to fetch release history!');\n });\n });\n\n $('#updateAP').on('click', function () {\n refreshAP();\n console.log('refresh AP');\n });\n\n // first time the page loads: attempt to get the connection status and start the wifi scan\n getConfig();\n getCommands();\n getMessages();\n checkStatus();\n\n});\n\n// eslint-disable-next-line no-unused-vars\nwindow.setURL = function (button) {\n let url = button.dataset.url;\n\n $('[data-bs-url^=\"http\"]')\n .addClass('btn-success')\n .removeClass('btn-danger');\n $('[data-bs-url=\"' + url + '\"]')\n .addClass('btn-danger')\n .removeClass('btn-success');\n\n // if user can proxy download through LMS, modify the URL\n if (lmsBaseUrl) {\n url = url.replace(/.*\\/download\\//, lmsBaseUrl + '/plugins/SqueezeESP32/firmware/');\n }\n\n $('#fwurl').val(url);\n}\n\n\nfunction rssiToIcon(rssi) {\n if (rssi >= -55) {\n return { 'label': '****', 'icon': `signal_wifi_statusbar_4_bar` };\n } else if (rssi >= -60) {\n return { 'label': '***', 'icon': `network_wifi_3_bar` };\n } else if (rssi >= -65) {\n return { 'label': '**', 'icon': `network_wifi_2_bar` };\n } else if (rssi >= -70) {\n return { 'label': '*', 'icon': `network_wifi_1_bar` };\n } else {\n return { 'label': '.', 'icon': `signal_wifi_statusbar_null` };\n }\n}\n\nfunction refreshAP() {\n if (ConnectedTo?.urc === connectReturnCode.ETH) return;\n $.ajaxSetup({\n timeout: 3000 //Time in milliseconds\n });\n $.getJSON('/scan.json', async function () {\n await sleep(2000);\n $.getJSON('/ap.json', function (data) {\n if (data.length > 0) {\n // sort by signal strength\n data.sort(function (a, b) {\n const x = a.rssi;\n const y = b.rssi;\n // eslint-disable-next-line no-nested-ternary\n return x < y ? 1 : x > y ? -1 : 0;\n });\n apList = data;\n refreshAPHTML2(apList);\n\n }\n });\n });\n}\nfunction formatAP(ssid, rssi, auth) {\n const rssi_icon = rssiToIcon(rssi);\n const auth_icon = { label: auth == 0 ? '🔓' : '🔒', icon: auth == 0 ? 'no_encryption' : 'lock' };\n\n return `${ssid}\n ${getIcon(rssi_icon)}\n \t\n ${getIcon(auth_icon)}\n `;\n}\nfunction refreshAPHTML2(data) {\n let h = '';\n $('#wifiTable tr td:first-of-type').text('');\n $('#wifiTable tr').removeClass('table-success table-warning');\n if (data) {\n data.forEach(function (e) {\n h += formatAP(e.ssid, e.rssi, e.auth);\n });\n $('#wifiTable').html(h);\n }\n if ($('.manual_add').length == 0) {\n $('#wifiTable').append(formatAP('Manual add', 0, 0));\n $('#wifiTable tr:last').addClass('table-light text-dark').addClass('manual_add');\n }\n if (ConnectedTo.ssid && (ConnectedTo.urc === connectReturnCode.OK || ConnectedTo.urc === connectReturnCode.RESTORE)) {\n const wifiSelector = `#wifiTable td:contains(\"${ConnectedTo.ssid}\")`;\n if ($(wifiSelector).filter(function () { return $(this).text() === ConnectedTo.ssid; }).length == 0) {\n $('#wifiTable').prepend(`${formatAP(ConnectedTo.ssid, ConnectedTo.rssi ?? 0, 0)}`);\n }\n $(wifiSelector).filter(function () { return $(this).text() === ConnectedTo.ssid; }).siblings().first().html('✓').parent().addClass((ConnectedTo.urc === connectReturnCode.OK ? 'table-success' : 'table-warning'));\n $('span#foot-if').html(`SSID: ${ConnectedTo.ssid}, IP: ${ConnectedTo.ip}`);\n $('#wifiStsIcon').html(rssiToIcon(ConnectedTo.rssi));\n\n }\n else if (ConnectedTo?.urc !== connectReturnCode.ETH) {\n $('span#foot-if').html('');\n }\n\n}\nfunction refreshETH() {\n\n if (ConnectedTo.urc === connectReturnCode.ETH) {\n $('span#foot-if').html(`Network: Ethernet, IP: ${ConnectedTo.ip}`);\n }\n}\nfunction showTask(task) {\n console.debug(\n this.toLocaleString() +\n '\\t' +\n task.nme +\n '\\t' +\n task.cpu +\n '\\t' +\n taskStates[task.st] +\n '\\t' +\n task.minstk +\n '\\t' +\n task.bprio +\n '\\t' +\n task.cprio +\n '\\t' +\n task.num\n );\n $('tbody#tasks').append(\n '' +\n task.num +\n '' +\n task.nme +\n '' +\n task.cpu +\n '' +\n taskStates[task.st] +\n '' +\n task.minstk +\n '' +\n task.bprio +\n '' +\n task.cprio +\n ''\n );\n}\nfunction btExists(name) {\n return getBTSinkOpt(name).length > 0;\n}\nfunction getBTSinkOpt(name) {\n return $(`${btSinkNamesOptSel} option:contains('${name}')`);\n}\nfunction getMessages() {\n $.ajaxSetup({\n timeout: messageInterval //Time in milliseconds\n });\n $.getJSON('/messages.json', async function (data) {\n for (const msg of data) {\n const msgAge = msg.current_time - msg.sent_time;\n var msgTime = new Date();\n msgTime.setTime(msgTime.getTime() - msgAge);\n switch (msg.class) {\n case 'MESSAGING_CLASS_OTA':\n flashState.EventOTAMessageClass(msg.message);\n break;\n case 'MESSAGING_CLASS_STATS':\n // for task states, check structure : task_state_t\n var statsData = JSON.parse(msg.message);\n console.debug(\n msgTime.toLocalShort() +\n ' - Number of running tasks: ' +\n statsData.ntasks\n );\n console.debug(\n msgTime.toLocalShort() +\n '\\tname' +\n '\\tcpu' +\n '\\tstate' +\n '\\tminstk' +\n '\\tbprio' +\n '\\tcprio' +\n '\\tnum'\n );\n if (statsData.tasks) {\n if ($('#tasks_sect').css('visibility') === 'collapse') {\n $('#tasks_sect').css('visibility', 'visible');\n }\n $('tbody#tasks').html('');\n statsData.tasks\n .sort(function (a, b) {\n return b.cpu - a.cpu;\n })\n .forEach(showTask, msgTime);\n } else if ($('#tasks_sect').css('visibility') === 'visible') {\n $('tbody#tasks').empty();\n $('#tasks_sect').css('visibility', 'collapse');\n }\n break;\n case 'MESSAGING_CLASS_SYSTEM':\n showMessage(msg, msgTime);\n break;\n case 'MESSAGING_CLASS_CFGCMD':\n var msgparts = msg.message.split(/([^\\n]*)\\n(.*)/gs);\n showCmdMessage(msgparts[1], msg.type, msgparts[2], true);\n break;\n case 'MESSAGING_CLASS_BT':\n if ($(\"#cfg-audio-bt_source-sink_name\").is('input')) {\n var attr = $(\"#cfg-audio-bt_source-sink_name\")[0].attributes;\n var attrs = '';\n for (var j = 0; j < attr.length; j++) {\n if (attr.item(j).name != \"type\") {\n attrs += `${attr.item(j).name} = \"${attr.item(j).value}\" `;\n }\n }\n var curOpt = $(\"#cfg-audio-bt_source-sink_name\")[0].value;\n $(\"#cfg-audio-bt_source-sink_name\").replaceWith(` `);\n }\n JSON.parse(msg.message).forEach(function (btEntry) {\n //\n // \n if (!btExists(btEntry.name)) {\n $(\"#cfg-audio-bt_source-sink_name\").append(``);\n showMessage({ type: msg.type, message: `BT Audio device found: ${btEntry.name} RSSI: ${btEntry.rssi} ` }, msgTime);\n }\n getBTSinkOpt(btEntry.name).attr('data-bs-description', `${btEntry.name} (${btEntry.rssi}dB)`)\n .attr('rssi', btEntry.rssi)\n .attr('value', btEntry.name)\n .text(`${btEntry.name} [${btEntry.rssi}dB]`).trigger('change');\n\n });\n $(btSinkNamesOptSel).append($(`${btSinkNamesOptSel} option`).remove().sort(function (a, b) {\n console.log(`${parseInt($(a).attr('rssi'))} < ${parseInt($(b).attr('rssi'))} ? `);\n return parseInt($(a).attr('rssi')) < parseInt($(b).attr('rssi')) ? 1 : -1;\n }));\n break;\n default:\n break;\n }\n }\n setTimeout(getMessages, messageInterval);\n }).fail(function (xhr, ajaxOptions, thrownError) {\n\n if (xhr.status == 404) {\n $('.orec').hide(); // system commands won't be available either\n messagesHeld = true;\n }\n else {\n handleExceptionResponse(xhr, ajaxOptions, thrownError);\n }\n if (xhr.status == 0 && xhr.readyState == 0) {\n // probably a timeout. Target is rebooting? \n setTimeout(getMessages, messageInterval * 2); // increase duration if a failure happens\n }\n else if (!messagesHeld) {\n // 404 here means we rebooted to an old recovery\n setTimeout(getMessages, messageInterval); // increase duration if a failure happens\n }\n\n }\n );\n\n /*\n Minstk is minimum stack space left\nBprio is base priority\ncprio is current priority\nnme is name\nst is task state. I provided a \"typedef\" that you can use to convert to text\ncpu is cpu percent used\n*/\n}\nfunction handleRecoveryMode(data) {\n const locRecovery = data.recovery ?? 0;\n if (locRecovery === 1) {\n recovery = true;\n $('.recovery_element').show();\n $('.ota_element').hide();\n $('#boot-button').html('Reboot');\n $('#boot-form').attr('action', '/reboot_ota.json');\n } else {\n if (!recovery && messagesHeld) {\n messagesHeld = false;\n setTimeout(getMessages, messageInterval); // increase duration if a failure happens\n }\n recovery = false;\n\n $('.recovery_element').hide();\n $('.ota_element').show();\n $('#boot-button').html('Recovery');\n $('#boot-form').attr('action', '/recovery.json');\n }\n\n}\n\nfunction hasConnectionChanged(data) {\n // gw: \"192.168.10.1\"\n // ip: \"192.168.10.225\"\n // netmask: \"255.255.255.0\"\n // ssid: \"MyTestSSID\"\n\n return (data.urc !== ConnectedTo.urc ||\n data.ssid !== ConnectedTo.ssid ||\n data.gw !== ConnectedTo.gw ||\n data.netmask !== ConnectedTo.netmask ||\n data.ip !== ConnectedTo.ip || data.rssi !== ConnectedTo.rssi)\n}\nfunction handleWifiDialog(data) {\n if ($('#WifiConnectDialog').is(':visible')) {\n if (ConnectedTo.ip) {\n $('#ipAddress').text(ConnectedTo.ip);\n }\n if (ConnectedTo.ssid) {\n $('#connectedToSSID').text(ConnectedTo.ssid);\n }\n if (ConnectedTo.gw) {\n $('#gateway').text(ConnectedTo.gw);\n }\n if (ConnectedTo.netmask) {\n $('#netmask').text(ConnectedTo.netmask);\n }\n if (ConnectingToSSID.Action === undefined || (ConnectingToSSID.Action && ConnectingToSSID.Action == ConnectingToActions.STS)) {\n $(\"*[class*='connecting']\").hide();\n $('.connecting-status').show();\n }\n if (SystemConfig.ap_ssid) {\n $('#apName').text(SystemConfig.ap_ssid.value);\n }\n if (SystemConfig.ap_pwd) {\n $('#apPass').text(SystemConfig.ap_pwd.value);\n }\n if (!data) {\n return;\n }\n else {\n switch (data.urc) {\n case connectReturnCode.OK:\n if (data.ssid && data.ssid === ConnectingToSSID.ssid) {\n $(\"*[class*='connecting']\").hide();\n $('.connecting-success').show();\n ConnectingToSSID.Action = ConnectingToActions.STS;\n }\n break;\n case connectReturnCode.FAIL:\n // \n if (ConnectingToSSID.Action != ConnectingToActions.STS && ConnectingToSSID.ssid == data.ssid) {\n $(\"*[class*='connecting']\").hide();\n $('.connecting-fail').show();\n }\n break;\n case connectReturnCode.LOST:\n\n break;\n case connectReturnCode.RESTORE:\n if (ConnectingToSSID.Action != ConnectingToActions.STS && ConnectingToSSID.ssid != data.ssid) {\n $(\"*[class*='connecting']\").hide();\n $('.connecting-fail').show();\n }\n break;\n case connectReturnCode.DISC:\n // that's a manual disconnect\n // if ($('#wifi-status').is(':visible')) {\n // $('#wifi-status').slideUp('fast', function() {});\n // $('span#foot-wifi').html('');\n\n // } \n break;\n default:\n break;\n }\n }\n\n }\n}\nfunction setIcons(offline) {\n $('.material-icons').each(function (_index, entry) {\n entry.textContent = entry.attributes[offline ? 'aria-label' : 'icon'].value;\n });\n}\nfunction handleNetworkStatus(data) {\n setIcons(!isConnected());\n if (hasConnectionChanged(data) || !data.urc) {\n ConnectedTo = data;\n $(\".if_eth\").hide();\n $('.if_wifi').hide();\n if (!data.urc || ConnectedTo.urc != connectReturnCode.ETH) {\n $('.if_wifi').show();\n refreshAPHTML2();\n }\n else {\n $(\".if_eth\").show();\n refreshETH();\n }\n\n }\n handleWifiDialog(data);\n}\n\n\n\nfunction batteryToIcon(voltage) {\n /* Assuming Li-ion 18650s as a power source, 3.9V per cell, or above is treated\n as full charge (>75% of capacity). 3.4V is empty. The gauge is loosely\n following the graph here:\n https://learn.adafruit.com/li-ion-and-lipoly-batteries/voltages\n using the 0.2C discharge profile for the rest of the values.\n*/\n\n for (const iconEntry of batIcons) {\n for (const entryRanges of iconEntry.ranges) {\n if (inRange(voltage, entryRanges.f, entryRanges.t)) {\n return { label: iconEntry.label, icon: iconEntry.icon };\n }\n }\n }\n\n\n return { label: 'â–Ēâ–Ēâ–Ēâ–Ē', icon: \"battery_full\" };\n}\nfunction checkStatus() {\n $.ajaxSetup({\n timeout: statusInterval //Time in milliseconds\n });\n $.getJSON('/status.json', function (data) {\n handleRecoveryMode(data);\n handleNVSVisible();\n handleNetworkStatus(data);\n handlebtstate(data);\n flashState.EventTargetStatus(data);\n if(data.depth) {\n depth = data.depth;\n if(depth==16){\n $('#cmd_opt_R').show();\n }\n else{\n $('#cmd_opt_R').hide();\n }\n }\n\n\n if (data.project_name && data.project_name !== '') {\n project_name = data.project_name;\n }\n if (data.platform_name && data.platform_name !== '') {\n platform_name = data.platform_name;\n }\n if (board_model === '') board_model = project_name;\n if (board_model === '') board_model = 'Squeezelite-ESP32';\n if (data.version && data.version !== '') {\n versionName = data.version;\n $(\"#navtitle\").html(`${board_model}${recovery ? '
[recovery]' : ''}`);\n $('span#foot-fw').html(`fw: ${versionName}, mode: ${recovery ? \"Recovery\" : project_name}`);\n } else {\n $('span#flash-status').html('');\n }\n if (data.Voltage) {\n const bat_icon = batteryToIcon(data.Voltage);\n $('#battery').html(`${getIcon(bat_icon)}`);\n $('#battery').attr(\"aria-label\", bat_icon.label);\n $('#battery').attr(\"icon\", bat_icon.icon);\n $('#battery').show();\n } else {\n $('#battery').hide();\n }\n if ((data.message ?? '') != '' && prevmessage != data.message) {\n // supporting older recovery firmwares - messages will come from the status.json structure\n prevmessage = data.message;\n showLocalMessage(data.message, 'MESSAGING_INFO')\n }\n is_i2c_locked = data.is_i2c_locked;\n if (is_i2c_locked) {\n $('flds-cfg-hw-preset').hide();\n }\n else {\n $('flds-cfg-hw-preset').show();\n }\n $(\"button[onclick*='handleReboot']\").removeClass('rebooting');\n\n if (typeof lmsBaseUrl == \"undefined\" || data.lms_ip != prevLMSIP && data.lms_ip && data.lms_port) {\n const baseUrl = 'http://' + data.lms_ip + ':' + data.lms_port;\n prevLMSIP = data.lms_ip;\n $.ajax({\n url: baseUrl + '/plugins/SqueezeESP32/firmware/-check.bin',\n type: 'HEAD',\n dataType: 'text',\n cache: false,\n error: function () {\n // define the value, so we don't check it any more.\n lmsBaseUrl = '';\n },\n success: function () {\n lmsBaseUrl = baseUrl;\n }\n });\n }\n $('#o_jack').css({ display: Number(data.Jack) ? 'inline' : 'none' });\n setTimeout(checkStatus, statusInterval);\n }).fail(function (xhr, ajaxOptions, thrownError) {\n handleExceptionResponse(xhr, ajaxOptions, thrownError);\n if (xhr.status == 0 && xhr.readyState == 0) {\n // probably a timeout. Target is rebooting? \n setTimeout(checkStatus, messageInterval * 2); // increase duration if a failure happens\n }\n else {\n setTimeout(checkStatus, messageInterval); // increase duration if a failure happens\n }\n });\n}\n// eslint-disable-next-line no-unused-vars\nwindow.runCommand = function (button, reboot) {\n let cmdstring = button.attributes.cmdname.value;\n showCmdMessage(\n button.attributes.cmdname.value,\n 'MESSAGING_INFO',\n 'Executing.',\n false\n );\n const fields = document.getElementById('flds-' + cmdstring);\n const allfields = fields?.querySelectorAll('select,input');\n if (cmdstring === 'cfg-hw-preset') return handleHWPreset(allfields, reboot);\n cmdstring += ' ';\n if (fields) {\n\n for (const field of allfields) {\n let qts = '';\n let opt = '';\n let attr = field.attributes;\n let isSelect = $(field).is('select');\n const hasValue = attr?.hasvalue?.value === 'true';\n const validVal = (isSelect && field.value !== '--') || (!isSelect && field.value !== '');\n\n if (!hasValue || hasValue && validVal) {\n if (attr?.longopts?.value !== 'undefined') {\n opt += '--' + attr?.longopts?.value;\n } else if (attr?.shortopts?.value !== 'undefined') {\n opt = '-' + attr.shortopts.value;\n }\n\n if (attr?.hasvalue?.value === 'true') {\n if (attr?.value !== '') {\n qts = /\\s/.test(field.value) ? '\"' : '';\n cmdstring += opt + ' ' + qts + field.value + qts + ' ';\n }\n } else {\n // this is a checkbox\n if (field?.checked) {\n cmdstring += opt + ' ';\n }\n }\n }\n }\n }\n\n console.log(cmdstring);\n\n const data = {\n timestamp: Date.now(),\n };\n data.command = cmdstring;\n\n $.ajax({\n url: '/commands.json',\n dataType: 'text',\n method: 'POST',\n cache: false,\n contentType: 'application/json; charset=utf-8',\n data: JSON.stringify(data),\n error: function (xhr, _ajaxOptions, thrownError) {\n var cmd = JSON.parse(this.data).command;\n if (xhr.status == 404) {\n showCmdMessage(\n cmd.substr(0, cmd.indexOf(' ')),\n 'MESSAGING_ERROR',\n `${recovery ? 'Limited recovery mode active. Unsupported action ' : 'Unexpected error while processing command'}`,\n true\n );\n }\n else {\n handleExceptionResponse(xhr, _ajaxOptions, thrownError);\n showCmdMessage(\n cmd.substr(0, cmd.indexOf(' ') - 1),\n 'MESSAGING_ERROR',\n `Unexpected error ${(thrownError !== '') ? thrownError : 'with return status = ' + xhr.status}`,\n true\n );\n }\n },\n success: function (response) {\n $('.orec').show();\n console.log(response);\n if (\n JSON.parse(response).Result === 'Success' &&\n reboot\n ) {\n delayReboot(2500, button.attributes.cmdname.value);\n }\n },\n });\n}\nfunction getLongOps(data, name, longopts) {\n return data.values[name] !== undefined ? data.values[name][longopts] : \"\";\n}\nfunction getCommands() {\n $.ajaxSetup({\n timeout: 7000 //Time in milliseconds\n });\n $.getJSON('/commands.json', function (data) {\n console.log(data);\n $('.orec').show();\n data.commands.forEach(function (command) {\n if ($('#flds-' + command.name).length === 0) {\n const cmdParts = command.name.split('-');\n const isConfig = cmdParts[0] === 'cfg';\n const targetDiv = '#tab-' + cmdParts[0] + '-' + cmdParts[1];\n let innerhtml = '';\n innerhtml += `
${command.help.encodeHTML().replace(/\\n/g, '
')}
`;\n if (command.argtable) {\n command.argtable.forEach(function (arg) {\n let placeholder = arg.datatype || '';\n const ctrlname = command.name + '-' + arg.longopts;\n const curvalue = getLongOps(data, command.name, arg.longopts);\n\n let attributes = 'hasvalue=' + arg.hasvalue + ' ';\n attributes += 'longopts=\"' + arg.longopts + '\" ';\n attributes += 'shortopts=\"' + arg.shortopts + '\" ';\n attributes += 'checkbox=' + arg.checkbox + ' ';\n attributes += 'cmdname=\"' + command.name + '\" ';\n attributes +=\n 'id=\"' +\n ctrlname +\n '\" name=\"' +\n ctrlname +\n '\" hasvalue=\"' +\n arg.hasvalue +\n '\" ';\n let extraclass = arg.mincount > 0 ? 'bg-success' : '';\n if (arg.glossary === 'hidden') {\n attributes += ' style=\"visibility: hidden;\"';\n }\n if (arg.checkbox) {\n innerhtml += `
`;\n } else {\n innerhtml += `
`;\n if (placeholder.includes('|')) {\n extraclass = placeholder.startsWith('+') ? ' multiple ' : '';\n placeholder = placeholder\n .replace('<', '')\n .replace('=', '')\n .replace('>', '');\n innerhtml += `';\n } else {\n innerhtml += ``;\n }\n }\n\n innerhtml += `${arg.checkbox ? '
' : ''}Previous value: ${arg.checkbox ? (curvalue ? 'Checked' : 'Unchecked') : (curvalue || '')}${arg.checkbox ? '' : '
'}`;\n });\n }\n innerhtml += `
\n
\n
\n Result\n
\n
\n
`;\n if (isConfig) {\n innerhtml +=\n `\n`;\n } else {\n innerhtml += ``;\n }\n innerhtml += '
';\n if (isConfig) {\n $(targetDiv).append(innerhtml);\n } else {\n $('#commands-list').append(innerhtml);\n }\n }\n });\n $(\".sclk\").off('click').on('click', function () { runCommand(this, false); });\n $(\".cclk\").off('click').on('click', function () { runCommand(this, true); });\n data.commands.forEach(function (command) {\n $('[cmdname=' + command.name + ']:input').val('');\n $('[cmdname=' + command.name + ']:checkbox').prop('checked', false);\n if (command.argtable) {\n command.argtable.forEach(function (arg) {\n const ctrlselector = '#' + command.name + '-' + arg.longopts;\n const ctrlValue = getLongOps(data, command.name, arg.longopts);\n if (arg.checkbox) {\n $(ctrlselector)[0].checked = ctrlValue;\n } else {\n if (ctrlValue !== undefined) {\n $(ctrlselector)\n .val(ctrlValue)\n .trigger('change');\n }\n if (\n $(ctrlselector)[0].value.length === 0 &&\n (arg.datatype || '').includes('|')\n ) {\n $(ctrlselector)[0].value = '--';\n }\n }\n });\n }\n });\n loadPresets();\n }).fail(function (xhr, ajaxOptions, thrownError) {\n if (xhr.status == 404) {\n $('.orec').hide();\n }\n else {\n handleExceptionResponse(xhr, ajaxOptions, thrownError);\n }\n $('#commands-list').empty();\n\n });\n}\n\nfunction getConfig() {\n $.ajaxSetup({\n timeout: 7000 //Time in milliseconds\n });\n $.getJSON('/config.json', function (entries) {\n $('#nvsTable tr').remove();\n const data = (entries.config ? entries.config : entries);\n SystemConfig = data;\n commandBTSinkName = '';\n Object.keys(data)\n .sort()\n .forEach(function (key) {\n let val = data[key].value;\n if (key === 'autoexec') {\n if (data.autoexec.value === '0') {\n $('#disable-squeezelite')[0].checked = true;\n } else {\n $('#disable-squeezelite')[0].checked = false;\n }\n } else if (key === 'autoexec1') {\n /* call new function to parse the squeezelite options */\n processSqueezeliteCommandLine(val);\n } else if (key === 'host_name') {\n val = val.replaceAll('\"', '');\n $('input#dhcp-name1').val(val);\n $('input#dhcp-name2').val(val);\n if ($('#cmd_opt_n').length == 0) {\n $('#cmd_opt_n').val(val);\n }\n document.title = val;\n hostName = val;\n } else if (key === 'rel_api') {\n releaseURL = val;\n }\n else if (key === 'enable_airplay') {\n $(\"#s_airplay\").css({ display: isEnabled(val) ? 'inline' : 'none' })\n }\n else if (key === 'enable_cspot') {\n $(\"#s_cspot\").css({ display: isEnabled(val) ? 'inline' : 'none' })\n }\n else if (key == 'preset_name') {\n preset_name = val;\n }\n else if (key == 'board_model') {\n board_model = val;\n }\n\n $('tbody#nvsTable').append(\n '' +\n '' +\n key +\n '' +\n \"\" +\n \"' +\n '' +\n ''\n );\n $('input#' + key).val(data[key].value);\n });\n if(commandBTSinkName.length > 0) {\n // persist the sink name found in the autoexec1 command line\n $('#cfg-audio-bt_source-sink_name').val(commandBTSinkName);\n }\n $('tbody#nvsTable').append(\n \"\"\n );\n if (entries.gpio) {\n $('#pins').show();\n $('tbody#gpiotable tr').remove();\n entries.gpio.forEach(function (gpioEntry) {\n $('tbody#gpiotable').append(\n '' +\n gpioEntry.group +\n '' +\n gpioEntry.name +\n '' +\n gpioEntry.gpio +\n '' +\n (gpioEntry.fixed ? 'Fixed' : 'Configuration') +\n ''\n );\n });\n }\n else {\n $('#pins').hide();\n }\n }).fail(function (xhr, ajaxOptions, thrownError) {\n handleExceptionResponse(xhr, ajaxOptions, thrownError);\n });\n}\n\nfunction processSqueezeliteCommandLine(val) {\n const parsed = parseSqueezeliteCommandLine(val);\n if (parsed.output.toUpperCase().startsWith('I2S')) {\n handleTemplateTypeRadio('i2s');\n } else if (parsed.output.toUpperCase().startsWith('SPDIF')) {\n handleTemplateTypeRadio('spdif');\n } else if (parsed.output.toUpperCase().startsWith('BT')) {\n if(parsed.otherOptions.btname){ \n commandBTSinkName= parsed.otherOptions.btname;\n }\n handleTemplateTypeRadio('bt');\n }\n Object.keys(parsed.options).forEach(function (key) {\n const option = parsed.options[key];\n if (!$(`#cmd_opt_${key}`).hasOwnProperty('checked')) {\n $(`#cmd_opt_${key}`).val(option);\n } else {\n $(`#cmd_opt_${key}`)[0].checked = option;\n }\n });\n if (parsed.options.hasOwnProperty('u')) {\n // parse -u v[:i] and check the appropriate radio button with id #resample_v\n const [resampleValue, resampleInterpolation] = parsed.options.u.split(':');\n $(`#resample_${resampleValue}`).prop('checked', true);\n // if resampleinterpolation is set, check resample_i checkbox\n if (resampleInterpolation) {\n $('#resample_i').prop('checked', true);\n }\n }\n\n\n}\n\nfunction showLocalMessage(message, severity) {\n const msg = {\n message: message,\n type: severity,\n };\n showMessage(msg, new Date());\n}\n\nfunction showMessage(msg, msgTime) {\n let color = 'table-success';\n\n if (msg.type === 'MESSAGING_WARNING') {\n color = 'table-warning';\n if (messageseverity === 'MESSAGING_INFO') {\n messageseverity = 'MESSAGING_WARNING';\n }\n } else if (msg.type === 'MESSAGING_ERROR') {\n if (\n messageseverity === 'MESSAGING_INFO' ||\n messageseverity === 'MESSAGING_WARNING'\n ) {\n messageseverity = 'MESSAGING_ERROR';\n }\n color = 'table-danger';\n }\n if (++messagecount > 0) {\n $('#msgcnt').removeClass('badge-success');\n $('#msgcnt').removeClass('badge-warning');\n $('#msgcnt').removeClass('badge-danger');\n $('#msgcnt').addClass(pillcolors[messageseverity]);\n $('#msgcnt').text(messagecount);\n }\n\n $('#syslogTable').append(\n \"\" +\n '' +\n msgTime.toLocalShort() +\n '' +\n '' +\n msg.message.encodeHTML() +\n '' +\n ''\n );\n}\n\nfunction inRange(x, min, max) {\n return (x - min) * (x - max) <= 0;\n}\n\nfunction sleep(ms) {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nrequire(\"bootstrap\");\nrequire(\"./sass/main.scss\");\nrequire(\"./assets/images/favicon-32x32.png\");\nrequire(\"./js/custom.js\");\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\tid: moduleId,\n\t\tloaded: false,\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Flag the module as loaded\n\tmodule.loaded = true;\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","__webpack_require__.nmd = (module) => {\n\tmodule.paths = [];\n\tif (!module.children) module.children = [];\n\treturn module;\n};","// no baseURI\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t826: 0\n};\n\n// no chunk on demand loading\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n__webpack_require__.O.j = (chunkId) => (installedChunks[chunkId] === 0);\n\n// install a JSONP callback for chunk loading\nvar webpackJsonpCallback = (parentChunkLoadingFunction, data) => {\n\tvar [chunkIds, moreModules, runtime] = data;\n\t// add \"moreModules\" to the modules object,\n\t// then flag all \"chunkIds\" as loaded and fire callback\n\tvar moduleId, chunkId, i = 0;\n\tif(chunkIds.some((id) => (installedChunks[id] !== 0))) {\n\t\tfor(moduleId in moreModules) {\n\t\t\tif(__webpack_require__.o(moreModules, moduleId)) {\n\t\t\t\t__webpack_require__.m[moduleId] = moreModules[moduleId];\n\t\t\t}\n\t\t}\n\t\tif(runtime) var result = runtime(__webpack_require__);\n\t}\n\tif(parentChunkLoadingFunction) parentChunkLoadingFunction(data);\n\tfor(;i < chunkIds.length; i++) {\n\t\tchunkId = chunkIds[i];\n\t\tif(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {\n\t\t\tinstalledChunks[chunkId][0]();\n\t\t}\n\t\tinstalledChunks[chunkId] = 0;\n\t}\n\treturn __webpack_require__.O(result);\n}\n\nvar chunkLoadingGlobal = self[\"webpackChunksqueezelite_esp32\"] = self[\"webpackChunksqueezelite_esp32\"] || [];\nchunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));\nchunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));","// startup\n// Load entry module and return exports\n// This entry module depends on other loaded chunks and execution need to be delayed\nvar __webpack_exports__ = __webpack_require__.O(undefined, [987], () => (__webpack_require__(607)))\n__webpack_exports__ = __webpack_require__.O(__webpack_exports__);\n"],"names":["deferred","he","require","Promise","get_control_option_value","obj","ctrl","id","val","opt","$","concat","attr","checked","replace","includes","handleNVSVisible","nvs_previous_checked","isEnabled","Cookies","recovery","show","hide","undefined","match","window","bootstrap","String","prototype","format","Object","assign","args","arguments","this","number","encodeHTML","encode","Date","toLocalShort","toLocaleString","dateStyle","timeStyle","nvsTypes","btIcons","bt_playing","bt_disconnected","bt_neutral","bt_connecting","bt_connected","bt_disabled","play_arrow","pause","stop","batIcons","icon","label","ranges","f","t","btStateIcons","desc","sub","pillcolors","MESSAGING_INFO","MESSAGING_WARNING","MESSAGING_ERROR","connectReturnCode","OK","FAIL","DISC","LOST","RESTORE","ETH","taskStates","flashState","NONE","REBOOT_TO_RECOVERY","SET_FWURL","FLASHING","DONE","UPLOADING","ERROR","UPLOADCOMPLETE","_state","olderRecovery","statusText","flashURL","flashFileName","statusPercent","Completed","prevRecovery","updateModal","Modal","document","getElementById","reset","UpdateProgress","removeClass","prop","value","isStateError","html","parent","isStateUploadComplete","isStateNone","isStateRebootRecovery","isStateSetUrl","isStateFlashing","isStateDone","isStateUploading","init","SetStateError","addClass","SetStateNone","SetStateRebootRecovery","SetStatusText","ajax","url","context","dataType","method","cache","contentType","data","JSON","stringify","timestamp","now","error","xhr","_ajaxOptions","thrownError","_xhr$status","setOTAError","status","complete","response","SetStateSetUrl","post_config","fwurl","type","SetStateFlashing","SetStateDone","SetStateUploading","SetStateUploadComplete","isFlashExecuting","toString","_this","keys","find","x","setOTATargets","fileInput","files","length","message","SetStatusPercent","ShowDialog","pct","pctChanged","txt","changed","css","text","StartOTA","logEvent","name","TargetReadyStartOTA","UploadLocalFile","xhttp","XMLHttpRequest","boundHandleUploadProgressEvent","HandleUploadProgressEvent","bind","boundsetOTAError","upload","addEventListener","onreadystatechange","readyState","open","send","console","Math","round","loaded","total","EventTargetStatus","_data$ota_pct","_data$ota_dsc","ota_pct","ota_dsc","EventOTAMessageClass","otaData","parse","fun","log","hideSurrounding","presetsloaded","messageInterval","confPayload","config","handleExceptionResponse","parseSqueezeliteCommandLine","commandLine","output","options","otherValues","i","arg","startsWith","option","slice","join","trim","o","indexOf","substring","getOutput","n","getName","otherOptions","btname","toUpperCase","temp","isConnected","ConnectedTo","hasOwnProperty","ip","getIcon","icons","handleTemplateTypeRadio","outtype","children","display","commandDefaults","forEach","key","showLocalMessage","showCmdMessage","cmdname","msgtype","msgtext","append","color","escapedtext","hFlash","handleReboot","link","delayReboot","lmsBaseUrl","releaseURL","messagesHeld","commandBTSinkName","i2s","b","C","W","Z","spdif","bt","validOptions","codecs","messagecount","messageseverity","SystemConfig","LastCommandsState","hostName","versionName","prevmessage","project_name","board_model","platform_name","preset_name","btSinkNamesOptSel","ConnectingToSSID","prevLMSIP","ConnectingToActions","getConfigJson","slimMode","each","_index","entry","nvsType","parseInt","attributes","nvs_type","duration","empty","resolve","delay","then","rdata","HideCmdMessage","getCommands","getConfig","setPlatformFilter","filter","trigger","renderError","fieldname","errorFieldName","errorField","field","after","rssiToIcon","rssi","refreshAP","_ConnectedTo","urc","ajaxSetup","timeout","getJSON","_asyncToGenerator","_regeneratorRuntime","_callee","_context","prev","next","sleep","sort","a","y","refreshAPHTML2","formatAP","ssid","auth","rssi_icon","auth_icon","_ConnectedTo2","h","e","_ConnectedTo$rssi","wifiSelector","prepend","siblings","first","showTask","task","debug","nme","cpu","st","minstk","bprio","cprio","num","getBTSinkOpt","getMessages","_ref2","_callee2","_iterator","_step","_loop","msgTime","statsData","msgparts","attrs","j","curOpt","_context3","_createForOfIteratorHelper","msg","msgAge","_context2","current_time","sent_time","setTime","getTime","t0","abrupt","ntasks","tasks","showMessage","split","is","item","replaceWith","btEntry","remove","s","done","delegateYield","t1","finish","setTimeout","_x","apply","fail","ajaxOptions","handleWifiDialog","gw","netmask","Action","STS","ap_ssid","ap_pwd","setIcons","offline","textContent","handleNetworkStatus","hasConnectionChanged","checkStatus","_data$message","_data$recovery","handleRecoveryMode","tt","bt_status","bt_sub_status","iconindex","handlebtstate","depth","version","Voltage","bat_icon","voltage","_i3","_batIcons","_step2","iconEntry","_iterator2","entryRanges","err","batteryToIcon","is_i2c_locked","lms_ip","lms_port","baseUrl","success","Number","Jack","getLongOps","longopts","values","commands","command","cmdParts","isConfig","targetDiv","innerhtml","help","argtable","placeholder","datatype","ctrlname","curvalue","hasvalue","shortopts","checkbox","extraclass","mincount","glossary","choice","off","on","runCommand","ctrlselector","ctrlValue","_","jqxhr","textStatus","entries","autoexec","parsed","_parsed$options$u$spl","u","_parsed$options$u$spl2","_slicedToArray","resampleValue","resampleInterpolation","processSqueezeliteCommandLine","replaceAll","title","gpio","gpioEntry","fixed","group","severity","ms","reason","_resolve","reject","saveAutoexec1","_get_control_option_v","optStr","resample","_i","_Object$entries","_Object$entries$_i","concatenateOptions","autoexec1","responseText","result","Result","handleDisconnect","handleConnect","pwd","dhcpname","ready","index","not","_get_control_option_v2","invalid","map","test","event","relatedTarget","CONN","MAN","FileReader","file","fr","onload","target","ex","alert","readAsText","slideUp","slideDown","preventDefault","createElement","href","URL","createObjectURL","Blob","setAttribute","body","appendChild","click","removeChild","branches","release","branch","push","fwb","assets","asset","browser_download_url","namecomponents","ver","cfg","bits","substr","lastIndexOf","created_at","idf","trclass","setURL","button","dataset","reboot","cmdstring","fields","allfields","querySelectorAll","selJson","cmd","model_config","_i2","_Object$entries2","_Object$entries2$_i","storedval","handleHWPreset","_step3","_iterator3","_attr$hasvalue","qts","isSelect","hasValue","validVal","_attr$longopts","_attr$shortopts","_attr$hasvalue2","_attr$longopts2","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","exports","module","__webpack_modules__","call","m","O","chunkIds","fn","priority","notFulfilled","Infinity","fulfilled","every","splice","r","getter","__esModule","d","definition","defineProperty","enumerable","get","g","globalThis","Function","Symbol","toStringTag","nmd","paths","installedChunks","chunkId","webpackJsonpCallback","parentChunkLoadingFunction","moreModules","runtime","some","chunkLoadingGlobal","self","__webpack_exports__"],"sourceRoot":""} \ No newline at end of file diff --git a/components/wifi-manager/webapp/dist/js/node_vendors.997af2.bundle.js b/components/wifi-manager/webapp/dist/js/node_vendors.1e1c60.bundle.js similarity index 57% rename from components/wifi-manager/webapp/dist/js/node_vendors.997af2.bundle.js rename to components/wifi-manager/webapp/dist/js/node_vendors.1e1c60.bundle.js index bb83ec85..e322a846 100644 --- a/components/wifi-manager/webapp/dist/js/node_vendors.997af2.bundle.js +++ b/components/wifi-manager/webapp/dist/js/node_vendors.1e1c60.bundle.js @@ -1,2 +1,2 @@ -(self.webpackChunksqueezelite_esp32=self.webpackChunksqueezelite_esp32||[]).push([[987],{138:(e,t,r)=>{"use strict";r.r(t),r.d(t,{Alert:()=>qt,Button:()=>Lt,Carousel:()=>cr,Collapse:()=>Ar,Dropdown:()=>Yr,Modal:()=>Ln,Offcanvas:()=>Yn,Popover:()=>vi,ScrollSpy:()=>qi,Tab:()=>Yi,Toast:()=>co,Tooltip:()=>hi});var n={};r.r(n),r.d(n,{afterMain:()=>A,afterRead:()=>w,afterWrite:()=>T,applyStyles:()=>O,arrow:()=>Z,auto:()=>l,basePlacements:()=>c,beforeMain:()=>_,beforeRead:()=>b,beforeWrite:()=>E,bottom:()=>o,clippingParents:()=>f,computeStyles:()=>ne,createPopper:()=>Oe,createPopperBase:()=>Ne,createPopperLite:()=>je,detectOverflow:()=>ye,end:()=>p,eventListeners:()=>oe,flip:()=>we,hide:()=>Ae,left:()=>a,main:()=>x,modifierPhases:()=>C,offset:()=>Ee,placements:()=>v,popper:()=>h,popperGenerator:()=>Se,popperOffsets:()=>De,preventOverflow:()=>Te,read:()=>y,reference:()=>g,right:()=>s,start:()=>u,top:()=>i,variationPlacements:()=>m,viewport:()=>d,write:()=>D});var i="top",o="bottom",s="right",a="left",l="auto",c=[i,o,s,a],u="start",p="end",f="clippingParents",d="viewport",h="popper",g="reference",m=c.reduce((function(e,t){return e.concat([t+"-"+u,t+"-"+p])}),[]),v=[].concat(c,[l]).reduce((function(e,t){return e.concat([t,t+"-"+u,t+"-"+p])}),[]),b="beforeRead",y="read",w="afterRead",_="beforeMain",x="main",A="afterMain",E="beforeWrite",D="write",T="afterWrite",C=[b,y,w,_,x,A,E,D,T];function q(e){return e?(e.nodeName||"").toLowerCase():null}function k(e){if(null==e)return window;if("[object Window]"!==e.toString()){var t=e.ownerDocument;return t&&t.defaultView||window}return e}function L(e){return e instanceof k(e).Element||e instanceof Element}function S(e){return e instanceof k(e).HTMLElement||e instanceof HTMLElement}function N(e){return"undefined"!=typeof ShadowRoot&&(e instanceof k(e).ShadowRoot||e instanceof ShadowRoot)}const O={name:"applyStyles",enabled:!0,phase:"write",fn:function(e){var t=e.state;Object.keys(t.elements).forEach((function(e){var r=t.styles[e]||{},n=t.attributes[e]||{},i=t.elements[e];S(i)&&q(i)&&(Object.assign(i.style,r),Object.keys(n).forEach((function(e){var t=n[e];!1===t?i.removeAttribute(e):i.setAttribute(e,!0===t?"":t)})))}))},effect:function(e){var t=e.state,r={popper:{position:t.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(t.elements.popper.style,r.popper),t.styles=r,t.elements.arrow&&Object.assign(t.elements.arrow.style,r.arrow),function(){Object.keys(t.elements).forEach((function(e){var n=t.elements[e],i=t.attributes[e]||{},o=Object.keys(t.styles.hasOwnProperty(e)?t.styles[e]:r[e]).reduce((function(e,t){return e[t]="",e}),{});S(n)&&q(n)&&(Object.assign(n.style,o),Object.keys(i).forEach((function(e){n.removeAttribute(e)})))}))}},requires:["computeStyles"]};function j(e){return e.split("-")[0]}var R=Math.max,B=Math.min,I=Math.round;function P(){var e=navigator.userAgentData;return null!=e&&e.brands&&Array.isArray(e.brands)?e.brands.map((function(e){return e.brand+"/"+e.version})).join(" "):navigator.userAgent}function F(){return!/^((?!chrome|android).)*safari/i.test(P())}function H(e,t,r){void 0===t&&(t=!1),void 0===r&&(r=!1);var n=e.getBoundingClientRect(),i=1,o=1;t&&S(e)&&(i=e.offsetWidth>0&&I(n.width)/e.offsetWidth||1,o=e.offsetHeight>0&&I(n.height)/e.offsetHeight||1);var s=(L(e)?k(e):window).visualViewport,a=!F()&&r,l=(n.left+(a&&s?s.offsetLeft:0))/i,c=(n.top+(a&&s?s.offsetTop:0))/o,u=n.width/i,p=n.height/o;return{width:u,height:p,top:c,right:l+u,bottom:c+p,left:l,x:l,y:c}}function M(e){var t=H(e),r=e.offsetWidth,n=e.offsetHeight;return Math.abs(t.width-r)<=1&&(r=t.width),Math.abs(t.height-n)<=1&&(n=t.height),{x:e.offsetLeft,y:e.offsetTop,width:r,height:n}}function U(e,t){var r=t.getRootNode&&t.getRootNode();if(e.contains(t))return!0;if(r&&N(r)){var n=t;do{if(n&&e.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function V(e){return k(e).getComputedStyle(e)}function $(e){return["table","td","th"].indexOf(q(e))>=0}function z(e){return((L(e)?e.ownerDocument:e.document)||window.document).documentElement}function G(e){return"html"===q(e)?e:e.assignedSlot||e.parentNode||(N(e)?e.host:null)||z(e)}function W(e){return S(e)&&"fixed"!==V(e).position?e.offsetParent:null}function Y(e){for(var t=k(e),r=W(e);r&&$(r)&&"static"===V(r).position;)r=W(r);return r&&("html"===q(r)||"body"===q(r)&&"static"===V(r).position)?t:r||function(e){var t=/firefox/i.test(P());if(/Trident/i.test(P())&&S(e)&&"fixed"===V(e).position)return null;var r=G(e);for(N(r)&&(r=r.host);S(r)&&["html","body"].indexOf(q(r))<0;){var n=V(r);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||t&&"filter"===n.willChange||t&&n.filter&&"none"!==n.filter)return r;r=r.parentNode}return null}(e)||t}function X(e){return["top","bottom"].indexOf(e)>=0?"x":"y"}function K(e,t,r){return R(e,B(t,r))}function J(e){return Object.assign({},{top:0,right:0,bottom:0,left:0},e)}function Q(e,t){return t.reduce((function(t,r){return t[r]=e,t}),{})}const Z={name:"arrow",enabled:!0,phase:"main",fn:function(e){var t,r=e.state,n=e.name,l=e.options,u=r.elements.arrow,p=r.modifiersData.popperOffsets,f=j(r.placement),d=X(f),h=[a,s].indexOf(f)>=0?"height":"width";if(u&&p){var g=function(e,t){return J("number"!=typeof(e="function"==typeof e?e(Object.assign({},t.rects,{placement:t.placement})):e)?e:Q(e,c))}(l.padding,r),m=M(u),v="y"===d?i:a,b="y"===d?o:s,y=r.rects.reference[h]+r.rects.reference[d]-p[d]-r.rects.popper[h],w=p[d]-r.rects.reference[d],_=Y(u),x=_?"y"===d?_.clientHeight||0:_.clientWidth||0:0,A=y/2-w/2,E=g[v],D=x-m[h]-g[b],T=x/2-m[h]/2+A,C=K(E,T,D),q=d;r.modifiersData[n]=((t={})[q]=C,t.centerOffset=C-T,t)}},effect:function(e){var t=e.state,r=e.options.element,n=void 0===r?"[data-popper-arrow]":r;null!=n&&("string"!=typeof n||(n=t.elements.popper.querySelector(n)))&&U(t.elements.popper,n)&&(t.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function ee(e){return e.split("-")[1]}var te={top:"auto",right:"auto",bottom:"auto",left:"auto"};function re(e){var t,r=e.popper,n=e.popperRect,l=e.placement,c=e.variation,u=e.offsets,f=e.position,d=e.gpuAcceleration,h=e.adaptive,g=e.roundOffsets,m=e.isFixed,v=u.x,b=void 0===v?0:v,y=u.y,w=void 0===y?0:y,_="function"==typeof g?g({x:b,y:w}):{x:b,y:w};b=_.x,w=_.y;var x=u.hasOwnProperty("x"),A=u.hasOwnProperty("y"),E=a,D=i,T=window;if(h){var C=Y(r),q="clientHeight",L="clientWidth";if(C===k(r)&&"static"!==V(C=z(r)).position&&"absolute"===f&&(q="scrollHeight",L="scrollWidth"),l===i||(l===a||l===s)&&c===p)D=o,w-=(m&&C===T&&T.visualViewport?T.visualViewport.height:C[q])-n.height,w*=d?1:-1;if(l===a||(l===i||l===o)&&c===p)E=s,b-=(m&&C===T&&T.visualViewport?T.visualViewport.width:C[L])-n.width,b*=d?1:-1}var S,N=Object.assign({position:f},h&&te),O=!0===g?function(e,t){var r=e.x,n=e.y,i=t.devicePixelRatio||1;return{x:I(r*i)/i||0,y:I(n*i)/i||0}}({x:b,y:w},k(r)):{x:b,y:w};return b=O.x,w=O.y,d?Object.assign({},N,((S={})[D]=A?"0":"",S[E]=x?"0":"",S.transform=(T.devicePixelRatio||1)<=1?"translate("+b+"px, "+w+"px)":"translate3d("+b+"px, "+w+"px, 0)",S)):Object.assign({},N,((t={})[D]=A?w+"px":"",t[E]=x?b+"px":"",t.transform="",t))}const ne={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(e){var t=e.state,r=e.options,n=r.gpuAcceleration,i=void 0===n||n,o=r.adaptive,s=void 0===o||o,a=r.roundOffsets,l=void 0===a||a,c={placement:j(t.placement),variation:ee(t.placement),popper:t.elements.popper,popperRect:t.rects.popper,gpuAcceleration:i,isFixed:"fixed"===t.options.strategy};null!=t.modifiersData.popperOffsets&&(t.styles.popper=Object.assign({},t.styles.popper,re(Object.assign({},c,{offsets:t.modifiersData.popperOffsets,position:t.options.strategy,adaptive:s,roundOffsets:l})))),null!=t.modifiersData.arrow&&(t.styles.arrow=Object.assign({},t.styles.arrow,re(Object.assign({},c,{offsets:t.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-placement":t.placement})},data:{}};var ie={passive:!0};const oe={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(e){var t=e.state,r=e.instance,n=e.options,i=n.scroll,o=void 0===i||i,s=n.resize,a=void 0===s||s,l=k(t.elements.popper),c=[].concat(t.scrollParents.reference,t.scrollParents.popper);return o&&c.forEach((function(e){e.addEventListener("scroll",r.update,ie)})),a&&l.addEventListener("resize",r.update,ie),function(){o&&c.forEach((function(e){e.removeEventListener("scroll",r.update,ie)})),a&&l.removeEventListener("resize",r.update,ie)}},data:{}};var se={left:"right",right:"left",bottom:"top",top:"bottom"};function ae(e){return e.replace(/left|right|bottom|top/g,(function(e){return se[e]}))}var le={start:"end",end:"start"};function ce(e){return e.replace(/start|end/g,(function(e){return le[e]}))}function ue(e){var t=k(e);return{scrollLeft:t.pageXOffset,scrollTop:t.pageYOffset}}function pe(e){return H(z(e)).left+ue(e).scrollLeft}function fe(e){var t=V(e),r=t.overflow,n=t.overflowX,i=t.overflowY;return/auto|scroll|overlay|hidden/.test(r+i+n)}function de(e){return["html","body","#document"].indexOf(q(e))>=0?e.ownerDocument.body:S(e)&&fe(e)?e:de(G(e))}function he(e,t){var r;void 0===t&&(t=[]);var n=de(e),i=n===(null==(r=e.ownerDocument)?void 0:r.body),o=k(n),s=i?[o].concat(o.visualViewport||[],fe(n)?n:[]):n,a=t.concat(s);return i?a:a.concat(he(G(s)))}function ge(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function me(e,t,r){return t===d?ge(function(e,t){var r=k(e),n=z(e),i=r.visualViewport,o=n.clientWidth,s=n.clientHeight,a=0,l=0;if(i){o=i.width,s=i.height;var c=F();(c||!c&&"fixed"===t)&&(a=i.offsetLeft,l=i.offsetTop)}return{width:o,height:s,x:a+pe(e),y:l}}(e,r)):L(t)?function(e,t){var r=H(e,!1,"fixed"===t);return r.top=r.top+e.clientTop,r.left=r.left+e.clientLeft,r.bottom=r.top+e.clientHeight,r.right=r.left+e.clientWidth,r.width=e.clientWidth,r.height=e.clientHeight,r.x=r.left,r.y=r.top,r}(t,r):ge(function(e){var t,r=z(e),n=ue(e),i=null==(t=e.ownerDocument)?void 0:t.body,o=R(r.scrollWidth,r.clientWidth,i?i.scrollWidth:0,i?i.clientWidth:0),s=R(r.scrollHeight,r.clientHeight,i?i.scrollHeight:0,i?i.clientHeight:0),a=-n.scrollLeft+pe(e),l=-n.scrollTop;return"rtl"===V(i||r).direction&&(a+=R(r.clientWidth,i?i.clientWidth:0)-o),{width:o,height:s,x:a,y:l}}(z(e)))}function ve(e,t,r,n){var i="clippingParents"===t?function(e){var t=he(G(e)),r=["absolute","fixed"].indexOf(V(e).position)>=0&&S(e)?Y(e):e;return L(r)?t.filter((function(e){return L(e)&&U(e,r)&&"body"!==q(e)})):[]}(e):[].concat(t),o=[].concat(i,[r]),s=o[0],a=o.reduce((function(t,r){var i=me(e,r,n);return t.top=R(i.top,t.top),t.right=B(i.right,t.right),t.bottom=B(i.bottom,t.bottom),t.left=R(i.left,t.left),t}),me(e,s,n));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}function be(e){var t,r=e.reference,n=e.element,l=e.placement,c=l?j(l):null,f=l?ee(l):null,d=r.x+r.width/2-n.width/2,h=r.y+r.height/2-n.height/2;switch(c){case i:t={x:d,y:r.y-n.height};break;case o:t={x:d,y:r.y+r.height};break;case s:t={x:r.x+r.width,y:h};break;case a:t={x:r.x-n.width,y:h};break;default:t={x:r.x,y:r.y}}var g=c?X(c):null;if(null!=g){var m="y"===g?"height":"width";switch(f){case u:t[g]=t[g]-(r[m]/2-n[m]/2);break;case p:t[g]=t[g]+(r[m]/2-n[m]/2)}}return t}function ye(e,t){void 0===t&&(t={});var r=t,n=r.placement,a=void 0===n?e.placement:n,l=r.strategy,u=void 0===l?e.strategy:l,p=r.boundary,m=void 0===p?f:p,v=r.rootBoundary,b=void 0===v?d:v,y=r.elementContext,w=void 0===y?h:y,_=r.altBoundary,x=void 0!==_&&_,A=r.padding,E=void 0===A?0:A,D=J("number"!=typeof E?E:Q(E,c)),T=w===h?g:h,C=e.rects.popper,q=e.elements[x?T:w],k=ve(L(q)?q:q.contextElement||z(e.elements.popper),m,b,u),S=H(e.elements.reference),N=be({reference:S,element:C,strategy:"absolute",placement:a}),O=ge(Object.assign({},C,N)),j=w===h?O:S,R={top:k.top-j.top+D.top,bottom:j.bottom-k.bottom+D.bottom,left:k.left-j.left+D.left,right:j.right-k.right+D.right},B=e.modifiersData.offset;if(w===h&&B){var I=B[a];Object.keys(R).forEach((function(e){var t=[s,o].indexOf(e)>=0?1:-1,r=[i,o].indexOf(e)>=0?"y":"x";R[e]+=I[r]*t}))}return R}const we={name:"flip",enabled:!0,phase:"main",fn:function(e){var t=e.state,r=e.options,n=e.name;if(!t.modifiersData[n]._skip){for(var p=r.mainAxis,f=void 0===p||p,d=r.altAxis,h=void 0===d||d,g=r.fallbackPlacements,b=r.padding,y=r.boundary,w=r.rootBoundary,_=r.altBoundary,x=r.flipVariations,A=void 0===x||x,E=r.allowedAutoPlacements,D=t.options.placement,T=j(D),C=g||(T===D||!A?[ae(D)]:function(e){if(j(e)===l)return[];var t=ae(e);return[ce(e),t,ce(t)]}(D)),q=[D].concat(C).reduce((function(e,r){return e.concat(j(r)===l?function(e,t){void 0===t&&(t={});var r=t,n=r.placement,i=r.boundary,o=r.rootBoundary,s=r.padding,a=r.flipVariations,l=r.allowedAutoPlacements,u=void 0===l?v:l,p=ee(n),f=p?a?m:m.filter((function(e){return ee(e)===p})):c,d=f.filter((function(e){return u.indexOf(e)>=0}));0===d.length&&(d=f);var h=d.reduce((function(t,r){return t[r]=ye(e,{placement:r,boundary:i,rootBoundary:o,padding:s})[j(r)],t}),{});return Object.keys(h).sort((function(e,t){return h[e]-h[t]}))}(t,{placement:r,boundary:y,rootBoundary:w,padding:b,flipVariations:A,allowedAutoPlacements:E}):r)}),[]),k=t.rects.reference,L=t.rects.popper,S=new Map,N=!0,O=q[0],R=0;R=0,H=F?"width":"height",M=ye(t,{placement:B,boundary:y,rootBoundary:w,altBoundary:_,padding:b}),U=F?P?s:a:P?o:i;k[H]>L[H]&&(U=ae(U));var V=ae(U),$=[];if(f&&$.push(M[I]<=0),h&&$.push(M[U]<=0,M[V]<=0),$.every((function(e){return e}))){O=B,N=!1;break}S.set(B,$)}if(N)for(var z=function(e){var t=q.find((function(t){var r=S.get(t);if(r)return r.slice(0,e).every((function(e){return e}))}));if(t)return O=t,"break"},G=A?3:1;G>0;G--){if("break"===z(G))break}t.placement!==O&&(t.modifiersData[n]._skip=!0,t.placement=O,t.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function _e(e,t,r){return void 0===r&&(r={x:0,y:0}),{top:e.top-t.height-r.y,right:e.right-t.width+r.x,bottom:e.bottom-t.height+r.y,left:e.left-t.width-r.x}}function xe(e){return[i,s,o,a].some((function(t){return e[t]>=0}))}const Ae={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(e){var t=e.state,r=e.name,n=t.rects.reference,i=t.rects.popper,o=t.modifiersData.preventOverflow,s=ye(t,{elementContext:"reference"}),a=ye(t,{altBoundary:!0}),l=_e(s,n),c=_e(a,i,o),u=xe(l),p=xe(c);t.modifiersData[r]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:u,hasPopperEscaped:p},t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-reference-hidden":u,"data-popper-escaped":p})}};const Ee={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(e){var t=e.state,r=e.options,n=e.name,o=r.offset,l=void 0===o?[0,0]:o,c=v.reduce((function(e,r){return e[r]=function(e,t,r){var n=j(e),o=[a,i].indexOf(n)>=0?-1:1,l="function"==typeof r?r(Object.assign({},t,{placement:e})):r,c=l[0],u=l[1];return c=c||0,u=(u||0)*o,[a,s].indexOf(n)>=0?{x:u,y:c}:{x:c,y:u}}(r,t.rects,l),e}),{}),u=c[t.placement],p=u.x,f=u.y;null!=t.modifiersData.popperOffsets&&(t.modifiersData.popperOffsets.x+=p,t.modifiersData.popperOffsets.y+=f),t.modifiersData[n]=c}};const De={name:"popperOffsets",enabled:!0,phase:"read",fn:function(e){var t=e.state,r=e.name;t.modifiersData[r]=be({reference:t.rects.reference,element:t.rects.popper,strategy:"absolute",placement:t.placement})},data:{}};const Te={name:"preventOverflow",enabled:!0,phase:"main",fn:function(e){var t=e.state,r=e.options,n=e.name,l=r.mainAxis,c=void 0===l||l,p=r.altAxis,f=void 0!==p&&p,d=r.boundary,h=r.rootBoundary,g=r.altBoundary,m=r.padding,v=r.tether,b=void 0===v||v,y=r.tetherOffset,w=void 0===y?0:y,_=ye(t,{boundary:d,rootBoundary:h,padding:m,altBoundary:g}),x=j(t.placement),A=ee(t.placement),E=!A,D=X(x),T="x"===D?"y":"x",C=t.modifiersData.popperOffsets,q=t.rects.reference,k=t.rects.popper,L="function"==typeof w?w(Object.assign({},t.rects,{placement:t.placement})):w,S="number"==typeof L?{mainAxis:L,altAxis:L}:Object.assign({mainAxis:0,altAxis:0},L),N=t.modifiersData.offset?t.modifiersData.offset[t.placement]:null,O={x:0,y:0};if(C){if(c){var I,P="y"===D?i:a,F="y"===D?o:s,H="y"===D?"height":"width",U=C[D],V=U+_[P],$=U-_[F],z=b?-k[H]/2:0,G=A===u?q[H]:k[H],W=A===u?-k[H]:-q[H],J=t.elements.arrow,Q=b&&J?M(J):{width:0,height:0},Z=t.modifiersData["arrow#persistent"]?t.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},te=Z[P],re=Z[F],ne=K(0,q[H],Q[H]),ie=E?q[H]/2-z-ne-te-S.mainAxis:G-ne-te-S.mainAxis,oe=E?-q[H]/2+z+ne+re+S.mainAxis:W+ne+re+S.mainAxis,se=t.elements.arrow&&Y(t.elements.arrow),ae=se?"y"===D?se.clientTop||0:se.clientLeft||0:0,le=null!=(I=null==N?void 0:N[D])?I:0,ce=U+oe-le,ue=K(b?B(V,U+ie-le-ae):V,U,b?R($,ce):$);C[D]=ue,O[D]=ue-U}if(f){var pe,fe="x"===D?i:a,de="x"===D?o:s,he=C[T],ge="y"===T?"height":"width",me=he+_[fe],ve=he-_[de],be=-1!==[i,a].indexOf(x),we=null!=(pe=null==N?void 0:N[T])?pe:0,_e=be?me:he-q[ge]-k[ge]-we+S.altAxis,xe=be?he+q[ge]+k[ge]-we-S.altAxis:ve,Ae=b&&be?function(e,t,r){var n=K(e,t,r);return n>r?r:n}(_e,he,xe):K(b?_e:me,he,b?xe:ve);C[T]=Ae,O[T]=Ae-he}t.modifiersData[n]=O}},requiresIfExists:["offset"]};function Ce(e,t,r){void 0===r&&(r=!1);var n,i,o=S(t),s=S(t)&&function(e){var t=e.getBoundingClientRect(),r=I(t.width)/e.offsetWidth||1,n=I(t.height)/e.offsetHeight||1;return 1!==r||1!==n}(t),a=z(t),l=H(e,s,r),c={scrollLeft:0,scrollTop:0},u={x:0,y:0};return(o||!o&&!r)&&(("body"!==q(t)||fe(a))&&(c=(n=t)!==k(n)&&S(n)?{scrollLeft:(i=n).scrollLeft,scrollTop:i.scrollTop}:ue(n)),S(t)?((u=H(t,!0)).x+=t.clientLeft,u.y+=t.clientTop):a&&(u.x=pe(a))),{x:l.left+c.scrollLeft-u.x,y:l.top+c.scrollTop-u.y,width:l.width,height:l.height}}function qe(e){var t=new Map,r=new Set,n=[];function i(e){r.add(e.name),[].concat(e.requires||[],e.requiresIfExists||[]).forEach((function(e){if(!r.has(e)){var n=t.get(e);n&&i(n)}})),n.push(e)}return e.forEach((function(e){t.set(e.name,e)})),e.forEach((function(e){r.has(e.name)||i(e)})),n}var ke={placement:"bottom",modifiers:[],strategy:"absolute"};function Le(){for(var e=arguments.length,t=new Array(e),r=0;r{let t=e.getAttribute("data-bs-target");if(!t||"#"===t){let r=e.getAttribute("href");if(!r||!r.includes("#")&&!r.startsWith("."))return null;r.includes("#")&&!r.startsWith("#")&&(r=`#${r.split("#")[1]}`),t=r&&"#"!==r?r.trim():null}return t},Ie=e=>{const t=Be(e);return t&&document.querySelector(t)?t:null},Pe=e=>{const t=Be(e);return t?document.querySelector(t):null},Fe=e=>{e.dispatchEvent(new Event(Re))},He=e=>!(!e||"object"!=typeof e)&&(void 0!==e.jquery&&(e=e[0]),void 0!==e.nodeType),Me=e=>He(e)?e.jquery?e[0]:e:"string"==typeof e&&e.length>0?document.querySelector(e):null,Ue=e=>{if(!He(e)||0===e.getClientRects().length)return!1;const t="visible"===getComputedStyle(e).getPropertyValue("visibility"),r=e.closest("details:not([open])");if(!r)return t;if(r!==e){const t=e.closest("summary");if(t&&t.parentNode!==r)return!1;if(null===t)return!1}return t},Ve=e=>!e||e.nodeType!==Node.ELEMENT_NODE||(!!e.classList.contains("disabled")||(void 0!==e.disabled?e.disabled:e.hasAttribute("disabled")&&"false"!==e.getAttribute("disabled"))),$e=e=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof e.getRootNode){const t=e.getRootNode();return t instanceof ShadowRoot?t:null}return e instanceof ShadowRoot?e:e.parentNode?$e(e.parentNode):null},ze=()=>{},Ge=e=>{e.offsetHeight},We=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,Ye=[],Xe=()=>"rtl"===document.documentElement.dir,Ke=e=>{var t;t=()=>{const t=We();if(t){const r=e.NAME,n=t.fn[r];t.fn[r]=e.jQueryInterface,t.fn[r].Constructor=e,t.fn[r].noConflict=()=>(t.fn[r]=n,e.jQueryInterface)}},"loading"===document.readyState?(Ye.length||document.addEventListener("DOMContentLoaded",(()=>{for(const e of Ye)e()})),Ye.push(t)):t()},Je=e=>{"function"==typeof e&&e()},Qe=(e,t,r=!0)=>{if(!r)return void Je(e);const n=(e=>{if(!e)return 0;let{transitionDuration:t,transitionDelay:r}=window.getComputedStyle(e);const n=Number.parseFloat(t),i=Number.parseFloat(r);return n||i?(t=t.split(",")[0],r=r.split(",")[0],1e3*(Number.parseFloat(t)+Number.parseFloat(r))):0})(t)+5;let i=!1;const o=({target:r})=>{r===t&&(i=!0,t.removeEventListener(Re,o),Je(e))};t.addEventListener(Re,o),setTimeout((()=>{i||Fe(t)}),n)},Ze=(e,t,r,n)=>{const i=e.length;let o=e.indexOf(t);return-1===o?!r&&n?e[i-1]:e[0]:(o+=r?1:-1,n&&(o=(o+i)%i),e[Math.max(0,Math.min(o,i-1))])},et=/[^.]*(?=\..*)\.|.*/,tt=/\..*/,rt=/::\d+$/,nt={};let it=1;const ot={mouseenter:"mouseover",mouseleave:"mouseout"},st=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function at(e,t){return t&&`${t}::${it++}`||e.uidEvent||it++}function lt(e){const t=at(e);return e.uidEvent=t,nt[t]=nt[t]||{},nt[t]}function ct(e,t,r=null){return Object.values(e).find((e=>e.callable===t&&e.delegationSelector===r))}function ut(e,t,r){const n="string"==typeof t,i=n?r:t||r;let o=ht(e);return st.has(o)||(o=e),[n,i,o]}function pt(e,t,r,n,i){if("string"!=typeof t||!e)return;let[o,s,a]=ut(t,r,n);if(t in ot){const e=e=>function(t){if(!t.relatedTarget||t.relatedTarget!==t.delegateTarget&&!t.delegateTarget.contains(t.relatedTarget))return e.call(this,t)};s=e(s)}const l=lt(e),c=l[a]||(l[a]={}),u=ct(c,s,o?r:null);if(u)return void(u.oneOff=u.oneOff&&i);const p=at(s,t.replace(et,"")),f=o?function(e,t,r){return function n(i){const o=e.querySelectorAll(t);for(let{target:s}=i;s&&s!==this;s=s.parentNode)for(const a of o)if(a===s)return mt(i,{delegateTarget:s}),n.oneOff&>.off(e,i.type,t,r),r.apply(s,[i])}}(e,r,s):function(e,t){return function r(n){return mt(n,{delegateTarget:e}),r.oneOff&>.off(e,n.type,t),t.apply(e,[n])}}(e,s);f.delegationSelector=o?r:null,f.callable=s,f.oneOff=i,f.uidEvent=p,c[p]=f,e.addEventListener(a,f,o)}function ft(e,t,r,n,i){const o=ct(t[r],n,i);o&&(e.removeEventListener(r,o,Boolean(i)),delete t[r][o.uidEvent])}function dt(e,t,r,n){const i=t[r]||{};for(const o of Object.keys(i))if(o.includes(n)){const n=i[o];ft(e,t,r,n.callable,n.delegationSelector)}}function ht(e){return e=e.replace(tt,""),ot[e]||e}const gt={on(e,t,r,n){pt(e,t,r,n,!1)},one(e,t,r,n){pt(e,t,r,n,!0)},off(e,t,r,n){if("string"!=typeof t||!e)return;const[i,o,s]=ut(t,r,n),a=s!==t,l=lt(e),c=l[s]||{},u=t.startsWith(".");if(void 0===o){if(u)for(const r of Object.keys(l))dt(e,l,r,t.slice(1));for(const r of Object.keys(c)){const n=r.replace(rt,"");if(!a||t.includes(n)){const t=c[r];ft(e,l,s,t.callable,t.delegationSelector)}}}else{if(!Object.keys(c).length)return;ft(e,l,s,o,i?r:null)}},trigger(e,t,r){if("string"!=typeof t||!e)return null;const n=We();let i=null,o=!0,s=!0,a=!1;t!==ht(t)&&n&&(i=n.Event(t,r),n(e).trigger(i),o=!i.isPropagationStopped(),s=!i.isImmediatePropagationStopped(),a=i.isDefaultPrevented());let l=new Event(t,{bubbles:o,cancelable:!0});return l=mt(l,r),a&&l.preventDefault(),s&&e.dispatchEvent(l),l.defaultPrevented&&i&&i.preventDefault(),l}};function mt(e,t){for(const[r,n]of Object.entries(t||{}))try{e[r]=n}catch(t){Object.defineProperty(e,r,{configurable:!0,get:()=>n})}return e}const vt=new Map,bt={set(e,t,r){vt.has(e)||vt.set(e,new Map);const n=vt.get(e);n.has(t)||0===n.size?n.set(t,r):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(n.keys())[0]}.`)},get:(e,t)=>vt.has(e)&&vt.get(e).get(t)||null,remove(e,t){if(!vt.has(e))return;const r=vt.get(e);r.delete(t),0===r.size&&vt.delete(e)}};function yt(e){if("true"===e)return!0;if("false"===e)return!1;if(e===Number(e).toString())return Number(e);if(""===e||"null"===e)return null;if("string"!=typeof e)return e;try{return JSON.parse(decodeURIComponent(e))}catch(t){return e}}function wt(e){return e.replace(/[A-Z]/g,(e=>`-${e.toLowerCase()}`))}const _t={setDataAttribute(e,t,r){e.setAttribute(`data-bs-${wt(t)}`,r)},removeDataAttribute(e,t){e.removeAttribute(`data-bs-${wt(t)}`)},getDataAttributes(e){if(!e)return{};const t={},r=Object.keys(e.dataset).filter((e=>e.startsWith("bs")&&!e.startsWith("bsConfig")));for(const n of r){let r=n.replace(/^bs/,"");r=r.charAt(0).toLowerCase()+r.slice(1,r.length),t[r]=yt(e.dataset[n])}return t},getDataAttribute:(e,t)=>yt(e.getAttribute(`data-bs-${wt(t)}`))};class xt{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(e){return e=this._mergeConfigObj(e),e=this._configAfterMerge(e),this._typeCheckConfig(e),e}_configAfterMerge(e){return e}_mergeConfigObj(e,t){const r=He(t)?_t.getDataAttribute(t,"config"):{};return{...this.constructor.Default,..."object"==typeof r?r:{},...He(t)?_t.getDataAttributes(t):{},..."object"==typeof e?e:{}}}_typeCheckConfig(e,t=this.constructor.DefaultType){for(const n of Object.keys(t)){const i=t[n],o=e[n],s=He(o)?"element":null==(r=o)?`${r}`:Object.prototype.toString.call(r).match(/\s([a-z]+)/i)[1].toLowerCase();if(!new RegExp(i).test(s))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${n}" provided type "${s}" but expected type "${i}".`)}var r}}class At extends xt{constructor(e,t){super(),(e=Me(e))&&(this._element=e,this._config=this._getConfig(t),bt.set(this._element,this.constructor.DATA_KEY,this))}dispose(){bt.remove(this._element,this.constructor.DATA_KEY),gt.off(this._element,this.constructor.EVENT_KEY);for(const e of Object.getOwnPropertyNames(this))this[e]=null}_queueCallback(e,t,r=!0){Qe(e,t,r)}_getConfig(e){return e=this._mergeConfigObj(e,this._element),e=this._configAfterMerge(e),this._typeCheckConfig(e),e}static getInstance(e){return bt.get(Me(e),this.DATA_KEY)}static getOrCreateInstance(e,t={}){return this.getInstance(e)||new this(e,"object"==typeof t?t:null)}static get VERSION(){return"5.2.3"}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(e){return`${e}${this.EVENT_KEY}`}}const Et=(e,t="hide")=>{const r=`click.dismiss${e.EVENT_KEY}`,n=e.NAME;gt.on(document,r,`[data-bs-dismiss="${n}"]`,(function(r){if(["A","AREA"].includes(this.tagName)&&r.preventDefault(),Ve(this))return;const i=Pe(this)||this.closest(`.${n}`);e.getOrCreateInstance(i)[t]()}))},Dt=".bs.alert",Tt=`close${Dt}`,Ct=`closed${Dt}`;class qt extends At{static get NAME(){return"alert"}close(){if(gt.trigger(this._element,Tt).defaultPrevented)return;this._element.classList.remove("show");const e=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,e)}_destroyElement(){this._element.remove(),gt.trigger(this._element,Ct),this.dispose()}static jQueryInterface(e){return this.each((function(){const t=qt.getOrCreateInstance(this);if("string"==typeof e){if(void 0===t[e]||e.startsWith("_")||"constructor"===e)throw new TypeError(`No method named "${e}"`);t[e](this)}}))}}Et(qt,"close"),Ke(qt);const kt='[data-bs-toggle="button"]';class Lt extends At{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(e){return this.each((function(){const t=Lt.getOrCreateInstance(this);"toggle"===e&&t[e]()}))}}gt.on(document,"click.bs.button.data-api",kt,(e=>{e.preventDefault();const t=e.target.closest(kt);Lt.getOrCreateInstance(t).toggle()})),Ke(Lt);const St={find:(e,t=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(t,e)),findOne:(e,t=document.documentElement)=>Element.prototype.querySelector.call(t,e),children:(e,t)=>[].concat(...e.children).filter((e=>e.matches(t))),parents(e,t){const r=[];let n=e.parentNode.closest(t);for(;n;)r.push(n),n=n.parentNode.closest(t);return r},prev(e,t){let r=e.previousElementSibling;for(;r;){if(r.matches(t))return[r];r=r.previousElementSibling}return[]},next(e,t){let r=e.nextElementSibling;for(;r;){if(r.matches(t))return[r];r=r.nextElementSibling}return[]},focusableChildren(e){const t=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((e=>`${e}:not([tabindex^="-"])`)).join(",");return this.find(t,e).filter((e=>!Ve(e)&&Ue(e)))}},Nt=".bs.swipe",Ot=`touchstart${Nt}`,jt=`touchmove${Nt}`,Rt=`touchend${Nt}`,Bt=`pointerdown${Nt}`,It=`pointerup${Nt}`,Pt={endCallback:null,leftCallback:null,rightCallback:null},Ft={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"};class Ht extends xt{constructor(e,t){super(),this._element=e,e&&Ht.isSupported()&&(this._config=this._getConfig(t),this._deltaX=0,this._supportPointerEvents=Boolean(window.PointerEvent),this._initEvents())}static get Default(){return Pt}static get DefaultType(){return Ft}static get NAME(){return"swipe"}dispose(){gt.off(this._element,Nt)}_start(e){this._supportPointerEvents?this._eventIsPointerPenTouch(e)&&(this._deltaX=e.clientX):this._deltaX=e.touches[0].clientX}_end(e){this._eventIsPointerPenTouch(e)&&(this._deltaX=e.clientX-this._deltaX),this._handleSwipe(),Je(this._config.endCallback)}_move(e){this._deltaX=e.touches&&e.touches.length>1?0:e.touches[0].clientX-this._deltaX}_handleSwipe(){const e=Math.abs(this._deltaX);if(e<=40)return;const t=e/this._deltaX;this._deltaX=0,t&&Je(t>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(gt.on(this._element,Bt,(e=>this._start(e))),gt.on(this._element,It,(e=>this._end(e))),this._element.classList.add("pointer-event")):(gt.on(this._element,Ot,(e=>this._start(e))),gt.on(this._element,jt,(e=>this._move(e))),gt.on(this._element,Rt,(e=>this._end(e))))}_eventIsPointerPenTouch(e){return this._supportPointerEvents&&("pen"===e.pointerType||"touch"===e.pointerType)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}}const Mt=".bs.carousel",Ut=".data-api",Vt="next",$t="prev",zt="left",Gt="right",Wt=`slide${Mt}`,Yt=`slid${Mt}`,Xt=`keydown${Mt}`,Kt=`mouseenter${Mt}`,Jt=`mouseleave${Mt}`,Qt=`dragstart${Mt}`,Zt=`load${Mt}${Ut}`,er=`click${Mt}${Ut}`,tr="carousel",rr="active",nr=".active",ir=".carousel-item",or=nr+ir,sr={ArrowLeft:Gt,ArrowRight:zt},ar={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},lr={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"};class cr extends At{constructor(e,t){super(e,t),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=St.findOne(".carousel-indicators",this._element),this._addEventListeners(),this._config.ride===tr&&this.cycle()}static get Default(){return ar}static get DefaultType(){return lr}static get NAME(){return"carousel"}next(){this._slide(Vt)}nextWhenVisible(){!document.hidden&&Ue(this._element)&&this.next()}prev(){this._slide($t)}pause(){this._isSliding&&Fe(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval((()=>this.nextWhenVisible()),this._config.interval)}_maybeEnableCycle(){this._config.ride&&(this._isSliding?gt.one(this._element,Yt,(()=>this.cycle())):this.cycle())}to(e){const t=this._getItems();if(e>t.length-1||e<0)return;if(this._isSliding)return void gt.one(this._element,Yt,(()=>this.to(e)));const r=this._getItemIndex(this._getActive());if(r===e)return;const n=e>r?Vt:$t;this._slide(n,t[e])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(e){return e.defaultInterval=e.interval,e}_addEventListeners(){this._config.keyboard&>.on(this._element,Xt,(e=>this._keydown(e))),"hover"===this._config.pause&&(gt.on(this._element,Kt,(()=>this.pause())),gt.on(this._element,Jt,(()=>this._maybeEnableCycle()))),this._config.touch&&Ht.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(const e of St.find(".carousel-item img",this._element))gt.on(e,Qt,(e=>e.preventDefault()));const e={leftCallback:()=>this._slide(this._directionToOrder(zt)),rightCallback:()=>this._slide(this._directionToOrder(Gt)),endCallback:()=>{"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((()=>this._maybeEnableCycle()),500+this._config.interval))}};this._swipeHelper=new Ht(this._element,e)}_keydown(e){if(/input|textarea/i.test(e.target.tagName))return;const t=sr[e.key];t&&(e.preventDefault(),this._slide(this._directionToOrder(t)))}_getItemIndex(e){return this._getItems().indexOf(e)}_setActiveIndicatorElement(e){if(!this._indicatorsElement)return;const t=St.findOne(nr,this._indicatorsElement);t.classList.remove(rr),t.removeAttribute("aria-current");const r=St.findOne(`[data-bs-slide-to="${e}"]`,this._indicatorsElement);r&&(r.classList.add(rr),r.setAttribute("aria-current","true"))}_updateInterval(){const e=this._activeElement||this._getActive();if(!e)return;const t=Number.parseInt(e.getAttribute("data-bs-interval"),10);this._config.interval=t||this._config.defaultInterval}_slide(e,t=null){if(this._isSliding)return;const r=this._getActive(),n=e===Vt,i=t||Ze(this._getItems(),r,n,this._config.wrap);if(i===r)return;const o=this._getItemIndex(i),s=t=>gt.trigger(this._element,t,{relatedTarget:i,direction:this._orderToDirection(e),from:this._getItemIndex(r),to:o});if(s(Wt).defaultPrevented)return;if(!r||!i)return;const a=Boolean(this._interval);this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(o),this._activeElement=i;const l=n?"carousel-item-start":"carousel-item-end",c=n?"carousel-item-next":"carousel-item-prev";i.classList.add(c),Ge(i),r.classList.add(l),i.classList.add(l);this._queueCallback((()=>{i.classList.remove(l,c),i.classList.add(rr),r.classList.remove(rr,c,l),this._isSliding=!1,s(Yt)}),r,this._isAnimated()),a&&this.cycle()}_isAnimated(){return this._element.classList.contains("slide")}_getActive(){return St.findOne(or,this._element)}_getItems(){return St.find(ir,this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(e){return Xe()?e===zt?$t:Vt:e===zt?Vt:$t}_orderToDirection(e){return Xe()?e===$t?zt:Gt:e===$t?Gt:zt}static jQueryInterface(e){return this.each((function(){const t=cr.getOrCreateInstance(this,e);if("number"!=typeof e){if("string"==typeof e){if(void 0===t[e]||e.startsWith("_")||"constructor"===e)throw new TypeError(`No method named "${e}"`);t[e]()}}else t.to(e)}))}}gt.on(document,er,"[data-bs-slide], [data-bs-slide-to]",(function(e){const t=Pe(this);if(!t||!t.classList.contains(tr))return;e.preventDefault();const r=cr.getOrCreateInstance(t),n=this.getAttribute("data-bs-slide-to");return n?(r.to(n),void r._maybeEnableCycle()):"next"===_t.getDataAttribute(this,"slide")?(r.next(),void r._maybeEnableCycle()):(r.prev(),void r._maybeEnableCycle())})),gt.on(window,Zt,(()=>{const e=St.find('[data-bs-ride="carousel"]');for(const t of e)cr.getOrCreateInstance(t)})),Ke(cr);const ur=".bs.collapse",pr=`show${ur}`,fr=`shown${ur}`,dr=`hide${ur}`,hr=`hidden${ur}`,gr=`click${ur}.data-api`,mr="show",vr="collapse",br="collapsing",yr=`:scope .${vr} .${vr}`,wr='[data-bs-toggle="collapse"]',_r={parent:null,toggle:!0},xr={parent:"(null|element)",toggle:"boolean"};class Ar extends At{constructor(e,t){super(e,t),this._isTransitioning=!1,this._triggerArray=[];const r=St.find(wr);for(const e of r){const t=Ie(e),r=St.find(t).filter((e=>e===this._element));null!==t&&r.length&&this._triggerArray.push(e)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return _r}static get DefaultType(){return xr}static get NAME(){return"collapse"}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let e=[];if(this._config.parent&&(e=this._getFirstLevelChildren(".collapse.show, .collapse.collapsing").filter((e=>e!==this._element)).map((e=>Ar.getOrCreateInstance(e,{toggle:!1})))),e.length&&e[0]._isTransitioning)return;if(gt.trigger(this._element,pr).defaultPrevented)return;for(const t of e)t.hide();const t=this._getDimension();this._element.classList.remove(vr),this._element.classList.add(br),this._element.style[t]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const r=`scroll${t[0].toUpperCase()+t.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(br),this._element.classList.add(vr,mr),this._element.style[t]="",gt.trigger(this._element,fr)}),this._element,!0),this._element.style[t]=`${this._element[r]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(gt.trigger(this._element,dr).defaultPrevented)return;const e=this._getDimension();this._element.style[e]=`${this._element.getBoundingClientRect()[e]}px`,Ge(this._element),this._element.classList.add(br),this._element.classList.remove(vr,mr);for(const e of this._triggerArray){const t=Pe(e);t&&!this._isShown(t)&&this._addAriaAndCollapsedClass([e],!1)}this._isTransitioning=!0;this._element.style[e]="",this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(br),this._element.classList.add(vr),gt.trigger(this._element,hr)}),this._element,!0)}_isShown(e=this._element){return e.classList.contains(mr)}_configAfterMerge(e){return e.toggle=Boolean(e.toggle),e.parent=Me(e.parent),e}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const e=this._getFirstLevelChildren(wr);for(const t of e){const e=Pe(t);e&&this._addAriaAndCollapsedClass([t],this._isShown(e))}}_getFirstLevelChildren(e){const t=St.find(yr,this._config.parent);return St.find(e,this._config.parent).filter((e=>!t.includes(e)))}_addAriaAndCollapsedClass(e,t){if(e.length)for(const r of e)r.classList.toggle("collapsed",!t),r.setAttribute("aria-expanded",t)}static jQueryInterface(e){const t={};return"string"==typeof e&&/show|hide/.test(e)&&(t.toggle=!1),this.each((function(){const r=Ar.getOrCreateInstance(this,t);if("string"==typeof e){if(void 0===r[e])throw new TypeError(`No method named "${e}"`);r[e]()}}))}}gt.on(document,gr,wr,(function(e){("A"===e.target.tagName||e.delegateTarget&&"A"===e.delegateTarget.tagName)&&e.preventDefault();const t=Ie(this),r=St.find(t);for(const e of r)Ar.getOrCreateInstance(e,{toggle:!1}).toggle()})),Ke(Ar);const Er="dropdown",Dr=".bs.dropdown",Tr=".data-api",Cr="ArrowUp",qr="ArrowDown",kr=`hide${Dr}`,Lr=`hidden${Dr}`,Sr=`show${Dr}`,Nr=`shown${Dr}`,Or=`click${Dr}${Tr}`,jr=`keydown${Dr}${Tr}`,Rr=`keyup${Dr}${Tr}`,Br="show",Ir='[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)',Pr=`${Ir}.${Br}`,Fr=".dropdown-menu",Hr=Xe()?"top-end":"top-start",Mr=Xe()?"top-start":"top-end",Ur=Xe()?"bottom-end":"bottom-start",Vr=Xe()?"bottom-start":"bottom-end",$r=Xe()?"left-start":"right-start",zr=Xe()?"right-start":"left-start",Gr={autoClose:!0,boundary:"clippingParents",display:"dynamic",offset:[0,2],popperConfig:null,reference:"toggle"},Wr={autoClose:"(boolean|string)",boundary:"(string|element)",display:"string",offset:"(array|string|function)",popperConfig:"(null|object|function)",reference:"(string|element|object)"};class Yr extends At{constructor(e,t){super(e,t),this._popper=null,this._parent=this._element.parentNode,this._menu=St.next(this._element,Fr)[0]||St.prev(this._element,Fr)[0]||St.findOne(Fr,this._parent),this._inNavbar=this._detectNavbar()}static get Default(){return Gr}static get DefaultType(){return Wr}static get NAME(){return Er}toggle(){return this._isShown()?this.hide():this.show()}show(){if(Ve(this._element)||this._isShown())return;const e={relatedTarget:this._element};if(!gt.trigger(this._element,Sr,e).defaultPrevented){if(this._createPopper(),"ontouchstart"in document.documentElement&&!this._parent.closest(".navbar-nav"))for(const e of[].concat(...document.body.children))gt.on(e,"mouseover",ze);this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(Br),this._element.classList.add(Br),gt.trigger(this._element,Nr,e)}}hide(){if(Ve(this._element)||!this._isShown())return;const e={relatedTarget:this._element};this._completeHide(e)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(e){if(!gt.trigger(this._element,kr,e).defaultPrevented){if("ontouchstart"in document.documentElement)for(const e of[].concat(...document.body.children))gt.off(e,"mouseover",ze);this._popper&&this._popper.destroy(),this._menu.classList.remove(Br),this._element.classList.remove(Br),this._element.setAttribute("aria-expanded","false"),_t.removeDataAttribute(this._menu,"popper"),gt.trigger(this._element,Lr,e)}}_getConfig(e){if("object"==typeof(e=super._getConfig(e)).reference&&!He(e.reference)&&"function"!=typeof e.reference.getBoundingClientRect)throw new TypeError(`${Er.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return e}_createPopper(){if(void 0===n)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let e=this._element;"parent"===this._config.reference?e=this._parent:He(this._config.reference)?e=Me(this._config.reference):"object"==typeof this._config.reference&&(e=this._config.reference);const t=this._getPopperConfig();this._popper=Oe(e,this._menu,t)}_isShown(){return this._menu.classList.contains(Br)}_getPlacement(){const e=this._parent;if(e.classList.contains("dropend"))return $r;if(e.classList.contains("dropstart"))return zr;if(e.classList.contains("dropup-center"))return"top";if(e.classList.contains("dropdown-center"))return"bottom";const t="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return e.classList.contains("dropup")?t?Mr:Hr:t?Vr:Ur}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:e}=this._config;return"string"==typeof e?e.split(",").map((e=>Number.parseInt(e,10))):"function"==typeof e?t=>e(t,this._element):e}_getPopperConfig(){const e={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||"static"===this._config.display)&&(_t.setDataAttribute(this._menu,"popper","static"),e.modifiers=[{name:"applyStyles",enabled:!1}]),{...e,..."function"==typeof this._config.popperConfig?this._config.popperConfig(e):this._config.popperConfig}}_selectMenuItem({key:e,target:t}){const r=St.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter((e=>Ue(e)));r.length&&Ze(r,t,e===qr,!r.includes(t)).focus()}static jQueryInterface(e){return this.each((function(){const t=Yr.getOrCreateInstance(this,e);if("string"==typeof e){if(void 0===t[e])throw new TypeError(`No method named "${e}"`);t[e]()}}))}static clearMenus(e){if(2===e.button||"keyup"===e.type&&"Tab"!==e.key)return;const t=St.find(Pr);for(const r of t){const t=Yr.getInstance(r);if(!t||!1===t._config.autoClose)continue;const n=e.composedPath(),i=n.includes(t._menu);if(n.includes(t._element)||"inside"===t._config.autoClose&&!i||"outside"===t._config.autoClose&&i)continue;if(t._menu.contains(e.target)&&("keyup"===e.type&&"Tab"===e.key||/input|select|option|textarea|form/i.test(e.target.tagName)))continue;const o={relatedTarget:t._element};"click"===e.type&&(o.clickEvent=e),t._completeHide(o)}}static dataApiKeydownHandler(e){const t=/input|textarea/i.test(e.target.tagName),r="Escape"===e.key,n=[Cr,qr].includes(e.key);if(!n&&!r)return;if(t&&!r)return;e.preventDefault();const i=this.matches(Ir)?this:St.prev(this,Ir)[0]||St.next(this,Ir)[0]||St.findOne(Ir,e.delegateTarget.parentNode),o=Yr.getOrCreateInstance(i);if(n)return e.stopPropagation(),o.show(),void o._selectMenuItem(e);o._isShown()&&(e.stopPropagation(),o.hide(),i.focus())}}gt.on(document,jr,Ir,Yr.dataApiKeydownHandler),gt.on(document,jr,Fr,Yr.dataApiKeydownHandler),gt.on(document,Or,Yr.clearMenus),gt.on(document,Rr,Yr.clearMenus),gt.on(document,Or,Ir,(function(e){e.preventDefault(),Yr.getOrCreateInstance(this).toggle()})),Ke(Yr);const Xr=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",Kr=".sticky-top",Jr="padding-right",Qr="margin-right";class Zr{constructor(){this._element=document.body}getWidth(){const e=document.documentElement.clientWidth;return Math.abs(window.innerWidth-e)}hide(){const e=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,Jr,(t=>t+e)),this._setElementAttributes(Xr,Jr,(t=>t+e)),this._setElementAttributes(Kr,Qr,(t=>t-e))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,Jr),this._resetElementAttributes(Xr,Jr),this._resetElementAttributes(Kr,Qr)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(e,t,r){const n=this.getWidth();this._applyManipulationCallback(e,(e=>{if(e!==this._element&&window.innerWidth>e.clientWidth+n)return;this._saveInitialAttribute(e,t);const i=window.getComputedStyle(e).getPropertyValue(t);e.style.setProperty(t,`${r(Number.parseFloat(i))}px`)}))}_saveInitialAttribute(e,t){const r=e.style.getPropertyValue(t);r&&_t.setDataAttribute(e,t,r)}_resetElementAttributes(e,t){this._applyManipulationCallback(e,(e=>{const r=_t.getDataAttribute(e,t);null!==r?(_t.removeDataAttribute(e,t),e.style.setProperty(t,r)):e.style.removeProperty(t)}))}_applyManipulationCallback(e,t){if(He(e))t(e);else for(const r of St.find(e,this._element))t(r)}}const en="backdrop",tn="show",rn=`mousedown.bs.${en}`,nn={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},on={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"};class sn extends xt{constructor(e){super(),this._config=this._getConfig(e),this._isAppended=!1,this._element=null}static get Default(){return nn}static get DefaultType(){return on}static get NAME(){return en}show(e){if(!this._config.isVisible)return void Je(e);this._append();const t=this._getElement();this._config.isAnimated&&Ge(t),t.classList.add(tn),this._emulateAnimation((()=>{Je(e)}))}hide(e){this._config.isVisible?(this._getElement().classList.remove(tn),this._emulateAnimation((()=>{this.dispose(),Je(e)}))):Je(e)}dispose(){this._isAppended&&(gt.off(this._element,rn),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){const e=document.createElement("div");e.className=this._config.className,this._config.isAnimated&&e.classList.add("fade"),this._element=e}return this._element}_configAfterMerge(e){return e.rootElement=Me(e.rootElement),e}_append(){if(this._isAppended)return;const e=this._getElement();this._config.rootElement.append(e),gt.on(e,rn,(()=>{Je(this._config.clickCallback)})),this._isAppended=!0}_emulateAnimation(e){Qe(e,this._getElement(),this._config.isAnimated)}}const an=".bs.focustrap",ln=`focusin${an}`,cn=`keydown.tab${an}`,un="backward",pn={autofocus:!0,trapElement:null},fn={autofocus:"boolean",trapElement:"element"};class dn extends xt{constructor(e){super(),this._config=this._getConfig(e),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return pn}static get DefaultType(){return fn}static get NAME(){return"focustrap"}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),gt.off(document,an),gt.on(document,ln,(e=>this._handleFocusin(e))),gt.on(document,cn,(e=>this._handleKeydown(e))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,gt.off(document,an))}_handleFocusin(e){const{trapElement:t}=this._config;if(e.target===document||e.target===t||t.contains(e.target))return;const r=St.focusableChildren(t);0===r.length?t.focus():this._lastTabNavDirection===un?r[r.length-1].focus():r[0].focus()}_handleKeydown(e){"Tab"===e.key&&(this._lastTabNavDirection=e.shiftKey?un:"forward")}}const hn=".bs.modal",gn=`hide${hn}`,mn=`hidePrevented${hn}`,vn=`hidden${hn}`,bn=`show${hn}`,yn=`shown${hn}`,wn=`resize${hn}`,_n=`click.dismiss${hn}`,xn=`mousedown.dismiss${hn}`,An=`keydown.dismiss${hn}`,En=`click${hn}.data-api`,Dn="modal-open",Tn="show",Cn="modal-static",qn={backdrop:!0,focus:!0,keyboard:!0},kn={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"};class Ln extends At{constructor(e,t){super(e,t),this._dialog=St.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new Zr,this._addEventListeners()}static get Default(){return qn}static get DefaultType(){return kn}static get NAME(){return"modal"}toggle(e){return this._isShown?this.hide():this.show(e)}show(e){if(this._isShown||this._isTransitioning)return;gt.trigger(this._element,bn,{relatedTarget:e}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(Dn),this._adjustDialog(),this._backdrop.show((()=>this._showElement(e))))}hide(){if(!this._isShown||this._isTransitioning)return;gt.trigger(this._element,gn).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(Tn),this._queueCallback((()=>this._hideModal()),this._element,this._isAnimated()))}dispose(){for(const e of[window,this._dialog])gt.off(e,hn);this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new sn({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new dn({trapElement:this._element})}_showElement(e){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0;const t=St.findOne(".modal-body",this._dialog);t&&(t.scrollTop=0),Ge(this._element),this._element.classList.add(Tn);this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,gt.trigger(this._element,yn,{relatedTarget:e})}),this._dialog,this._isAnimated())}_addEventListeners(){gt.on(this._element,An,(e=>{if("Escape"===e.key)return this._config.keyboard?(e.preventDefault(),void this.hide()):void this._triggerBackdropTransition()})),gt.on(window,wn,(()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()})),gt.on(this._element,xn,(e=>{gt.one(this._element,_n,(t=>{this._element===e.target&&this._element===t.target&&("static"!==this._config.backdrop?this._config.backdrop&&this.hide():this._triggerBackdropTransition())}))}))}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(Dn),this._resetAdjustments(),this._scrollBar.reset(),gt.trigger(this._element,vn)}))}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(gt.trigger(this._element,mn).defaultPrevented)return;const e=this._element.scrollHeight>document.documentElement.clientHeight,t=this._element.style.overflowY;"hidden"===t||this._element.classList.contains(Cn)||(e||(this._element.style.overflowY="hidden"),this._element.classList.add(Cn),this._queueCallback((()=>{this._element.classList.remove(Cn),this._queueCallback((()=>{this._element.style.overflowY=t}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const e=this._element.scrollHeight>document.documentElement.clientHeight,t=this._scrollBar.getWidth(),r=t>0;if(r&&!e){const e=Xe()?"paddingLeft":"paddingRight";this._element.style[e]=`${t}px`}if(!r&&e){const e=Xe()?"paddingRight":"paddingLeft";this._element.style[e]=`${t}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(e,t){return this.each((function(){const r=Ln.getOrCreateInstance(this,e);if("string"==typeof e){if(void 0===r[e])throw new TypeError(`No method named "${e}"`);r[e](t)}}))}}gt.on(document,En,'[data-bs-toggle="modal"]',(function(e){const t=Pe(this);["A","AREA"].includes(this.tagName)&&e.preventDefault(),gt.one(t,bn,(e=>{e.defaultPrevented||gt.one(t,vn,(()=>{Ue(this)&&this.focus()}))}));const r=St.findOne(".modal.show");r&&Ln.getInstance(r).hide();Ln.getOrCreateInstance(t).toggle(this)})),Et(Ln),Ke(Ln);const Sn=".bs.offcanvas",Nn=".data-api",On=`load${Sn}${Nn}`,jn="show",Rn="showing",Bn="hiding",In=".offcanvas.show",Pn=`show${Sn}`,Fn=`shown${Sn}`,Hn=`hide${Sn}`,Mn=`hidePrevented${Sn}`,Un=`hidden${Sn}`,Vn=`resize${Sn}`,$n=`click${Sn}${Nn}`,zn=`keydown.dismiss${Sn}`,Gn={backdrop:!0,keyboard:!0,scroll:!1},Wn={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"};class Yn extends At{constructor(e,t){super(e,t),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return Gn}static get DefaultType(){return Wn}static get NAME(){return"offcanvas"}toggle(e){return this._isShown?this.hide():this.show(e)}show(e){if(this._isShown)return;if(gt.trigger(this._element,Pn,{relatedTarget:e}).defaultPrevented)return;this._isShown=!0,this._backdrop.show(),this._config.scroll||(new Zr).hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(Rn);this._queueCallback((()=>{this._config.scroll&&!this._config.backdrop||this._focustrap.activate(),this._element.classList.add(jn),this._element.classList.remove(Rn),gt.trigger(this._element,Fn,{relatedTarget:e})}),this._element,!0)}hide(){if(!this._isShown)return;if(gt.trigger(this._element,Hn).defaultPrevented)return;this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add(Bn),this._backdrop.hide();this._queueCallback((()=>{this._element.classList.remove(jn,Bn),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||(new Zr).reset(),gt.trigger(this._element,Un)}),this._element,!0)}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){const e=Boolean(this._config.backdrop);return new sn({className:"offcanvas-backdrop",isVisible:e,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:e?()=>{"static"!==this._config.backdrop?this.hide():gt.trigger(this._element,Mn)}:null})}_initializeFocusTrap(){return new dn({trapElement:this._element})}_addEventListeners(){gt.on(this._element,zn,(e=>{"Escape"===e.key&&(this._config.keyboard?this.hide():gt.trigger(this._element,Mn))}))}static jQueryInterface(e){return this.each((function(){const t=Yn.getOrCreateInstance(this,e);if("string"==typeof e){if(void 0===t[e]||e.startsWith("_")||"constructor"===e)throw new TypeError(`No method named "${e}"`);t[e](this)}}))}}gt.on(document,$n,'[data-bs-toggle="offcanvas"]',(function(e){const t=Pe(this);if(["A","AREA"].includes(this.tagName)&&e.preventDefault(),Ve(this))return;gt.one(t,Un,(()=>{Ue(this)&&this.focus()}));const r=St.findOne(In);r&&r!==t&&Yn.getInstance(r).hide();Yn.getOrCreateInstance(t).toggle(this)})),gt.on(window,On,(()=>{for(const e of St.find(In))Yn.getOrCreateInstance(e).show()})),gt.on(window,Vn,(()=>{for(const e of St.find("[aria-modal][class*=show][class*=offcanvas-]"))"fixed"!==getComputedStyle(e).position&&Yn.getOrCreateInstance(e).hide()})),Et(Yn),Ke(Yn);const Xn=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Kn=/^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i,Jn=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i,Qn=(e,t)=>{const r=e.nodeName.toLowerCase();return t.includes(r)?!Xn.has(r)||Boolean(Kn.test(e.nodeValue)||Jn.test(e.nodeValue)):t.filter((e=>e instanceof RegExp)).some((e=>e.test(r)))},Zn={"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]};const ei={allowList:Zn,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:"
"},ti={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},ri={entry:"(string|element|function|null)",selector:"(string|element)"};class ni extends xt{constructor(e){super(),this._config=this._getConfig(e)}static get Default(){return ei}static get DefaultType(){return ti}static get NAME(){return"TemplateFactory"}getContent(){return Object.values(this._config.content).map((e=>this._resolvePossibleFunction(e))).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(e){return this._checkContent(e),this._config.content={...this._config.content,...e},this}toHtml(){const e=document.createElement("div");e.innerHTML=this._maybeSanitize(this._config.template);for(const[t,r]of Object.entries(this._config.content))this._setContent(e,r,t);const t=e.children[0],r=this._resolvePossibleFunction(this._config.extraClass);return r&&t.classList.add(...r.split(" ")),t}_typeCheckConfig(e){super._typeCheckConfig(e),this._checkContent(e.content)}_checkContent(e){for(const[t,r]of Object.entries(e))super._typeCheckConfig({selector:t,entry:r},ri)}_setContent(e,t,r){const n=St.findOne(r,e);n&&((t=this._resolvePossibleFunction(t))?He(t)?this._putElementInTemplate(Me(t),n):this._config.html?n.innerHTML=this._maybeSanitize(t):n.textContent=t:n.remove())}_maybeSanitize(e){return this._config.sanitize?function(e,t,r){if(!e.length)return e;if(r&&"function"==typeof r)return r(e);const n=(new window.DOMParser).parseFromString(e,"text/html"),i=[].concat(...n.body.querySelectorAll("*"));for(const e of i){const r=e.nodeName.toLowerCase();if(!Object.keys(t).includes(r)){e.remove();continue}const n=[].concat(...e.attributes),i=[].concat(t["*"]||[],t[r]||[]);for(const t of n)Qn(t,i)||e.removeAttribute(t.nodeName)}return n.body.innerHTML}(e,this._config.allowList,this._config.sanitizeFn):e}_resolvePossibleFunction(e){return"function"==typeof e?e(this):e}_putElementInTemplate(e,t){if(this._config.html)return t.innerHTML="",void t.append(e);t.textContent=e.textContent}}const ii=new Set(["sanitize","allowList","sanitizeFn"]),oi="fade",si="show",ai=".modal",li="hide.bs.modal",ci="hover",ui="focus",pi={AUTO:"auto",TOP:"top",RIGHT:Xe()?"left":"right",BOTTOM:"bottom",LEFT:Xe()?"right":"left"},fi={allowList:Zn,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,0],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'',title:"",trigger:"hover focus"},di={allowList:"object",animation:"boolean",boundary:"(string|element)",container:"(string|element|boolean)",customClass:"(string|function)",delay:"(number|object)",fallbackPlacements:"array",html:"boolean",offset:"(array|string|function)",placement:"(string|function)",popperConfig:"(null|object|function)",sanitize:"boolean",sanitizeFn:"(null|function)",selector:"(string|boolean)",template:"string",title:"(string|element|function)",trigger:"string"};class hi extends At{constructor(e,t){if(void 0===n)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(e,t),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return fi}static get DefaultType(){return di}static get NAME(){return"tooltip"}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){this._isEnabled&&(this._activeTrigger.click=!this._activeTrigger.click,this._isShown()?this._leave():this._enter())}dispose(){clearTimeout(this._timeout),gt.off(this._element.closest(ai),li,this._hideModalHandler),this._element.getAttribute("data-bs-original-title")&&this._element.setAttribute("title",this._element.getAttribute("data-bs-original-title")),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this._isWithContent()||!this._isEnabled)return;const e=gt.trigger(this._element,this.constructor.eventName("show")),t=($e(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(e.defaultPrevented||!t)return;this._disposePopper();const r=this._getTipElement();this._element.setAttribute("aria-describedby",r.getAttribute("id"));const{container:n}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(n.append(r),gt.trigger(this._element,this.constructor.eventName("inserted"))),this._popper=this._createPopper(r),r.classList.add(si),"ontouchstart"in document.documentElement)for(const e of[].concat(...document.body.children))gt.on(e,"mouseover",ze);this._queueCallback((()=>{gt.trigger(this._element,this.constructor.eventName("shown")),!1===this._isHovered&&this._leave(),this._isHovered=!1}),this.tip,this._isAnimated())}hide(){if(!this._isShown())return;if(gt.trigger(this._element,this.constructor.eventName("hide")).defaultPrevented)return;if(this._getTipElement().classList.remove(si),"ontouchstart"in document.documentElement)for(const e of[].concat(...document.body.children))gt.off(e,"mouseover",ze);this._activeTrigger.click=!1,this._activeTrigger[ui]=!1,this._activeTrigger[ci]=!1,this._isHovered=null;this._queueCallback((()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute("aria-describedby"),gt.trigger(this._element,this.constructor.eventName("hidden")))}),this.tip,this._isAnimated())}update(){this._popper&&this._popper.update()}_isWithContent(){return Boolean(this._getTitle())}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(e){const t=this._getTemplateFactory(e).toHtml();if(!t)return null;t.classList.remove(oi,si),t.classList.add(`bs-${this.constructor.NAME}-auto`);const r=(e=>{do{e+=Math.floor(1e6*Math.random())}while(document.getElementById(e));return e})(this.constructor.NAME).toString();return t.setAttribute("id",r),this._isAnimated()&&t.classList.add(oi),t}setContent(e){this._newContent=e,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(e){return this._templateFactory?this._templateFactory.changeContent(e):this._templateFactory=new ni({...this._config,content:e,extraClass:this._resolvePossibleFunction(this._config.customClass)}),this._templateFactory}_getContentForTemplate(){return{".tooltip-inner":this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(e){return this.constructor.getOrCreateInstance(e.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(oi)}_isShown(){return this.tip&&this.tip.classList.contains(si)}_createPopper(e){const t="function"==typeof this._config.placement?this._config.placement.call(this,e,this._element):this._config.placement,r=pi[t.toUpperCase()];return Oe(this._element,e,this._getPopperConfig(r))}_getOffset(){const{offset:e}=this._config;return"string"==typeof e?e.split(",").map((e=>Number.parseInt(e,10))):"function"==typeof e?t=>e(t,this._element):e}_resolvePossibleFunction(e){return"function"==typeof e?e.call(this._element):e}_getPopperConfig(e){const t={placement:e,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:e=>{this._getTipElement().setAttribute("data-popper-placement",e.state.placement)}}]};return{...t,..."function"==typeof this._config.popperConfig?this._config.popperConfig(t):this._config.popperConfig}}_setListeners(){const e=this._config.trigger.split(" ");for(const t of e)if("click"===t)gt.on(this._element,this.constructor.eventName("click"),this._config.selector,(e=>{this._initializeOnDelegatedTarget(e).toggle()}));else if("manual"!==t){const e=t===ci?this.constructor.eventName("mouseenter"):this.constructor.eventName("focusin"),r=t===ci?this.constructor.eventName("mouseleave"):this.constructor.eventName("focusout");gt.on(this._element,e,this._config.selector,(e=>{const t=this._initializeOnDelegatedTarget(e);t._activeTrigger["focusin"===e.type?ui:ci]=!0,t._enter()})),gt.on(this._element,r,this._config.selector,(e=>{const t=this._initializeOnDelegatedTarget(e);t._activeTrigger["focusout"===e.type?ui:ci]=t._element.contains(e.relatedTarget),t._leave()}))}this._hideModalHandler=()=>{this._element&&this.hide()},gt.on(this._element.closest(ai),li,this._hideModalHandler)}_fixTitle(){const e=this._element.getAttribute("title");e&&(this._element.getAttribute("aria-label")||this._element.textContent.trim()||this._element.setAttribute("aria-label",e),this._element.setAttribute("data-bs-original-title",e),this._element.removeAttribute("title"))}_enter(){this._isShown()||this._isHovered?this._isHovered=!0:(this._isHovered=!0,this._setTimeout((()=>{this._isHovered&&this.show()}),this._config.delay.show))}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout((()=>{this._isHovered||this.hide()}),this._config.delay.hide))}_setTimeout(e,t){clearTimeout(this._timeout),this._timeout=setTimeout(e,t)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(e){const t=_t.getDataAttributes(this._element);for(const e of Object.keys(t))ii.has(e)&&delete t[e];return e={...t,..."object"==typeof e&&e?e:{}},e=this._mergeConfigObj(e),e=this._configAfterMerge(e),this._typeCheckConfig(e),e}_configAfterMerge(e){return e.container=!1===e.container?document.body:Me(e.container),"number"==typeof e.delay&&(e.delay={show:e.delay,hide:e.delay}),"number"==typeof e.title&&(e.title=e.title.toString()),"number"==typeof e.content&&(e.content=e.content.toString()),e}_getDelegateConfig(){const e={};for(const t in this._config)this.constructor.Default[t]!==this._config[t]&&(e[t]=this._config[t]);return e.selector=!1,e.trigger="manual",e}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null),this.tip&&(this.tip.remove(),this.tip=null)}static jQueryInterface(e){return this.each((function(){const t=hi.getOrCreateInstance(this,e);if("string"==typeof e){if(void 0===t[e])throw new TypeError(`No method named "${e}"`);t[e]()}}))}}Ke(hi);const gi={...hi.Default,content:"",offset:[0,8],placement:"right",template:'',trigger:"click"},mi={...hi.DefaultType,content:"(null|string|element|function)"};class vi extends hi{static get Default(){return gi}static get DefaultType(){return mi}static get NAME(){return"popover"}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{".popover-header":this._getTitle(),".popover-body":this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(e){return this.each((function(){const t=vi.getOrCreateInstance(this,e);if("string"==typeof e){if(void 0===t[e])throw new TypeError(`No method named "${e}"`);t[e]()}}))}}Ke(vi);const bi=".bs.scrollspy",yi=`activate${bi}`,wi=`click${bi}`,_i=`load${bi}.data-api`,xi="active",Ai="[href]",Ei=".nav-link",Di=`${Ei}, .nav-item > ${Ei}, .list-group-item`,Ti={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},Ci={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"};class qi extends At{constructor(e,t){super(e,t),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement="visible"===getComputedStyle(this._element).overflowY?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return Ti}static get DefaultType(){return Ci}static get NAME(){return"scrollspy"}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(const e of this._observableSections.values())this._observer.observe(e)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(e){return e.target=Me(e.target)||document.body,e.rootMargin=e.offset?`${e.offset}px 0px -30%`:e.rootMargin,"string"==typeof e.threshold&&(e.threshold=e.threshold.split(",").map((e=>Number.parseFloat(e)))),e}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(gt.off(this._config.target,wi),gt.on(this._config.target,wi,Ai,(e=>{const t=this._observableSections.get(e.target.hash);if(t){e.preventDefault();const r=this._rootElement||window,n=t.offsetTop-this._element.offsetTop;if(r.scrollTo)return void r.scrollTo({top:n,behavior:"smooth"});r.scrollTop=n}})))}_getNewObserver(){const e={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver((e=>this._observerCallback(e)),e)}_observerCallback(e){const t=e=>this._targetLinks.get(`#${e.target.id}`),r=e=>{this._previousScrollData.visibleEntryTop=e.target.offsetTop,this._process(t(e))},n=(this._rootElement||document.documentElement).scrollTop,i=n>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=n;for(const o of e){if(!o.isIntersecting){this._activeTarget=null,this._clearActiveClass(t(o));continue}const e=o.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(i&&e){if(r(o),!n)return}else i||e||r(o)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;const e=St.find(Ai,this._config.target);for(const t of e){if(!t.hash||Ve(t))continue;const e=St.findOne(t.hash,this._element);Ue(e)&&(this._targetLinks.set(t.hash,t),this._observableSections.set(t.hash,e))}}_process(e){this._activeTarget!==e&&(this._clearActiveClass(this._config.target),this._activeTarget=e,e.classList.add(xi),this._activateParents(e),gt.trigger(this._element,yi,{relatedTarget:e}))}_activateParents(e){if(e.classList.contains("dropdown-item"))St.findOne(".dropdown-toggle",e.closest(".dropdown")).classList.add(xi);else for(const t of St.parents(e,".nav, .list-group"))for(const e of St.prev(t,Di))e.classList.add(xi)}_clearActiveClass(e){e.classList.remove(xi);const t=St.find(`${Ai}.${xi}`,e);for(const e of t)e.classList.remove(xi)}static jQueryInterface(e){return this.each((function(){const t=qi.getOrCreateInstance(this,e);if("string"==typeof e){if(void 0===t[e]||e.startsWith("_")||"constructor"===e)throw new TypeError(`No method named "${e}"`);t[e]()}}))}}gt.on(window,_i,(()=>{for(const e of St.find('[data-bs-spy="scroll"]'))qi.getOrCreateInstance(e)})),Ke(qi);const ki=".bs.tab",Li=`hide${ki}`,Si=`hidden${ki}`,Ni=`show${ki}`,Oi=`shown${ki}`,ji=`click${ki}`,Ri=`keydown${ki}`,Bi=`load${ki}`,Ii="ArrowLeft",Pi="ArrowRight",Fi="ArrowUp",Hi="ArrowDown",Mi="active",Ui="fade",Vi="show",$i=":not(.dropdown-toggle)",zi='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',Gi=`${`.nav-link${$i}, .list-group-item${$i}, [role="tab"]${$i}`}, ${zi}`,Wi=`.${Mi}[data-bs-toggle="tab"], .${Mi}[data-bs-toggle="pill"], .${Mi}[data-bs-toggle="list"]`;class Yi extends At{constructor(e){super(e),this._parent=this._element.closest('.list-group, .nav, [role="tablist"]'),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),gt.on(this._element,Ri,(e=>this._keydown(e))))}static get NAME(){return"tab"}show(){const e=this._element;if(this._elemIsActive(e))return;const t=this._getActiveElem(),r=t?gt.trigger(t,Li,{relatedTarget:e}):null;gt.trigger(e,Ni,{relatedTarget:t}).defaultPrevented||r&&r.defaultPrevented||(this._deactivate(t,e),this._activate(e,t))}_activate(e,t){if(!e)return;e.classList.add(Mi),this._activate(Pe(e));this._queueCallback((()=>{"tab"===e.getAttribute("role")?(e.removeAttribute("tabindex"),e.setAttribute("aria-selected",!0),this._toggleDropDown(e,!0),gt.trigger(e,Oi,{relatedTarget:t})):e.classList.add(Vi)}),e,e.classList.contains(Ui))}_deactivate(e,t){if(!e)return;e.classList.remove(Mi),e.blur(),this._deactivate(Pe(e));this._queueCallback((()=>{"tab"===e.getAttribute("role")?(e.setAttribute("aria-selected",!1),e.setAttribute("tabindex","-1"),this._toggleDropDown(e,!1),gt.trigger(e,Si,{relatedTarget:t})):e.classList.remove(Vi)}),e,e.classList.contains(Ui))}_keydown(e){if(![Ii,Pi,Fi,Hi].includes(e.key))return;e.stopPropagation(),e.preventDefault();const t=[Pi,Hi].includes(e.key),r=Ze(this._getChildren().filter((e=>!Ve(e))),e.target,t,!0);r&&(r.focus({preventScroll:!0}),Yi.getOrCreateInstance(r).show())}_getChildren(){return St.find(Gi,this._parent)}_getActiveElem(){return this._getChildren().find((e=>this._elemIsActive(e)))||null}_setInitialAttributes(e,t){this._setAttributeIfNotExists(e,"role","tablist");for(const e of t)this._setInitialAttributesOnChild(e)}_setInitialAttributesOnChild(e){e=this._getInnerElement(e);const t=this._elemIsActive(e),r=this._getOuterElement(e);e.setAttribute("aria-selected",t),r!==e&&this._setAttributeIfNotExists(r,"role","presentation"),t||e.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(e,"role","tab"),this._setInitialAttributesOnTargetPanel(e)}_setInitialAttributesOnTargetPanel(e){const t=Pe(e);t&&(this._setAttributeIfNotExists(t,"role","tabpanel"),e.id&&this._setAttributeIfNotExists(t,"aria-labelledby",`#${e.id}`))}_toggleDropDown(e,t){const r=this._getOuterElement(e);if(!r.classList.contains("dropdown"))return;const n=(e,n)=>{const i=St.findOne(e,r);i&&i.classList.toggle(n,t)};n(".dropdown-toggle",Mi),n(".dropdown-menu",Vi),r.setAttribute("aria-expanded",t)}_setAttributeIfNotExists(e,t,r){e.hasAttribute(t)||e.setAttribute(t,r)}_elemIsActive(e){return e.classList.contains(Mi)}_getInnerElement(e){return e.matches(Gi)?e:St.findOne(Gi,e)}_getOuterElement(e){return e.closest(".nav-item, .list-group-item")||e}static jQueryInterface(e){return this.each((function(){const t=Yi.getOrCreateInstance(this);if("string"==typeof e){if(void 0===t[e]||e.startsWith("_")||"constructor"===e)throw new TypeError(`No method named "${e}"`);t[e]()}}))}}gt.on(document,ji,zi,(function(e){["A","AREA"].includes(this.tagName)&&e.preventDefault(),Ve(this)||Yi.getOrCreateInstance(this).show()})),gt.on(window,Bi,(()=>{for(const e of St.find(Wi))Yi.getOrCreateInstance(e)})),Ke(Yi);const Xi=".bs.toast",Ki=`mouseover${Xi}`,Ji=`mouseout${Xi}`,Qi=`focusin${Xi}`,Zi=`focusout${Xi}`,eo=`hide${Xi}`,to=`hidden${Xi}`,ro=`show${Xi}`,no=`shown${Xi}`,io="hide",oo="show",so="showing",ao={animation:"boolean",autohide:"boolean",delay:"number"},lo={animation:!0,autohide:!0,delay:5e3};class co extends At{constructor(e,t){super(e,t),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return lo}static get DefaultType(){return ao}static get NAME(){return"toast"}show(){if(gt.trigger(this._element,ro).defaultPrevented)return;this._clearTimeout(),this._config.animation&&this._element.classList.add("fade");this._element.classList.remove(io),Ge(this._element),this._element.classList.add(oo,so),this._queueCallback((()=>{this._element.classList.remove(so),gt.trigger(this._element,no),this._maybeScheduleHide()}),this._element,this._config.animation)}hide(){if(!this.isShown())return;if(gt.trigger(this._element,eo).defaultPrevented)return;this._element.classList.add(so),this._queueCallback((()=>{this._element.classList.add(io),this._element.classList.remove(so,oo),gt.trigger(this._element,to)}),this._element,this._config.animation)}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(oo),super.dispose()}isShown(){return this._element.classList.contains(oo)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(e,t){switch(e.type){case"mouseover":case"mouseout":this._hasMouseInteraction=t;break;case"focusin":case"focusout":this._hasKeyboardInteraction=t}if(t)return void this._clearTimeout();const r=e.relatedTarget;this._element===r||this._element.contains(r)||this._maybeScheduleHide()}_setListeners(){gt.on(this._element,Ki,(e=>this._onInteraction(e,!0))),gt.on(this._element,Ji,(e=>this._onInteraction(e,!1))),gt.on(this._element,Qi,(e=>this._onInteraction(e,!0))),gt.on(this._element,Zi,(e=>this._onInteraction(e,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(e){return this.each((function(){const t=co.getOrCreateInstance(this,e);if("string"==typeof e){if(void 0===t[e])throw new TypeError(`No method named "${e}"`);t[e](this)}}))}}Et(co),Ke(co)},702:function(e,t,r){e.exports=function(){"use strict";function e(e){var t=typeof e;return null!==e&&("object"===t||"function"===t)}function t(e){return"function"==typeof e}var n=Array.isArray?Array.isArray:function(e){return"[object Array]"===Object.prototype.toString.call(e)},i=0,o=void 0,s=void 0,a=function(e,t){w[i]=e,w[i+1]=t,2===(i+=2)&&(s?s(_):A())};function l(e){s=e}function c(e){a=e}var u="undefined"!=typeof window?window:void 0,p=u||{},f=p.MutationObserver||p.WebKitMutationObserver,d="undefined"==typeof self&&"undefined"!=typeof process&&"[object process]"==={}.toString.call(process),h="undefined"!=typeof Uint8ClampedArray&&"undefined"!=typeof importScripts&&"undefined"!=typeof MessageChannel;function g(){return function(){return process.nextTick(_)}}function m(){return void 0!==o?function(){o(_)}:y()}function v(){var e=0,t=new f(_),r=document.createTextNode("");return t.observe(r,{characterData:!0}),function(){r.data=e=++e%2}}function b(){var e=new MessageChannel;return e.port1.onmessage=_,function(){return e.port2.postMessage(0)}}function y(){var e=setTimeout;return function(){return e(_,1)}}var w=new Array(1e3);function _(){for(var e=0;e\u20D2|\u205F\u200A|\u219D\u0338|\u2202\u0338|\u2220\u20D2|\u2229\uFE00|\u222A\uFE00|\u223C\u20D2|\u223D\u0331|\u223E\u0333|\u2242\u0338|\u224B\u0338|\u224D\u20D2|\u224E\u0338|\u224F\u0338|\u2250\u0338|\u2261\u20E5|\u2264\u20D2|\u2265\u20D2|\u2266\u0338|\u2267\u0338|\u2268\uFE00|\u2269\uFE00|\u226A\u0338|\u226A\u20D2|\u226B\u0338|\u226B\u20D2|\u227F\u0338|\u2282\u20D2|\u2283\u20D2|\u228A\uFE00|\u228B\uFE00|\u228F\u0338|\u2290\u0338|\u2293\uFE00|\u2294\uFE00|\u22B4\u20D2|\u22B5\u20D2|\u22D8\u0338|\u22D9\u0338|\u22DA\uFE00|\u22DB\uFE00|\u22F5\u0338|\u22F9\u0338|\u2933\u0338|\u29CF\u0338|\u29D0\u0338|\u2A6D\u0338|\u2A70\u0338|\u2A7D\u0338|\u2A7E\u0338|\u2AA1\u0338|\u2AA2\u0338|\u2AAC\uFE00|\u2AAD\uFE00|\u2AAF\u0338|\u2AB0\u0338|\u2AC5\u0338|\u2AC6\u0338|\u2ACB\uFE00|\u2ACC\uFE00|\u2AFD\u20E5|[\xA0-\u0113\u0116-\u0122\u0124-\u012B\u012E-\u014D\u0150-\u017E\u0192\u01B5\u01F5\u0237\u02C6\u02C7\u02D8-\u02DD\u0311\u0391-\u03A1\u03A3-\u03A9\u03B1-\u03C9\u03D1\u03D2\u03D5\u03D6\u03DC\u03DD\u03F0\u03F1\u03F5\u03F6\u0401-\u040C\u040E-\u044F\u0451-\u045C\u045E\u045F\u2002-\u2005\u2007-\u2010\u2013-\u2016\u2018-\u201A\u201C-\u201E\u2020-\u2022\u2025\u2026\u2030-\u2035\u2039\u203A\u203E\u2041\u2043\u2044\u204F\u2057\u205F-\u2063\u20AC\u20DB\u20DC\u2102\u2105\u210A-\u2113\u2115-\u211E\u2122\u2124\u2127-\u2129\u212C\u212D\u212F-\u2131\u2133-\u2138\u2145-\u2148\u2153-\u215E\u2190-\u219B\u219D-\u21A7\u21A9-\u21AE\u21B0-\u21B3\u21B5-\u21B7\u21BA-\u21DB\u21DD\u21E4\u21E5\u21F5\u21FD-\u2205\u2207-\u2209\u220B\u220C\u220F-\u2214\u2216-\u2218\u221A\u221D-\u2238\u223A-\u2257\u2259\u225A\u225C\u225F-\u2262\u2264-\u228B\u228D-\u229B\u229D-\u22A5\u22A7-\u22B0\u22B2-\u22BB\u22BD-\u22DB\u22DE-\u22E3\u22E6-\u22F7\u22F9-\u22FE\u2305\u2306\u2308-\u2310\u2312\u2313\u2315\u2316\u231C-\u231F\u2322\u2323\u232D\u232E\u2336\u233D\u233F\u237C\u23B0\u23B1\u23B4-\u23B6\u23DC-\u23DF\u23E2\u23E7\u2423\u24C8\u2500\u2502\u250C\u2510\u2514\u2518\u251C\u2524\u252C\u2534\u253C\u2550-\u256C\u2580\u2584\u2588\u2591-\u2593\u25A1\u25AA\u25AB\u25AD\u25AE\u25B1\u25B3-\u25B5\u25B8\u25B9\u25BD-\u25BF\u25C2\u25C3\u25CA\u25CB\u25EC\u25EF\u25F8-\u25FC\u2605\u2606\u260E\u2640\u2642\u2660\u2663\u2665\u2666\u266A\u266D-\u266F\u2713\u2717\u2720\u2736\u2758\u2772\u2773\u27C8\u27C9\u27E6-\u27ED\u27F5-\u27FA\u27FC\u27FF\u2902-\u2905\u290C-\u2913\u2916\u2919-\u2920\u2923-\u292A\u2933\u2935-\u2939\u293C\u293D\u2945\u2948-\u294B\u294E-\u2976\u2978\u2979\u297B-\u297F\u2985\u2986\u298B-\u2996\u299A\u299C\u299D\u29A4-\u29B7\u29B9\u29BB\u29BC\u29BE-\u29C5\u29C9\u29CD-\u29D0\u29DC-\u29DE\u29E3-\u29E5\u29EB\u29F4\u29F6\u2A00-\u2A02\u2A04\u2A06\u2A0C\u2A0D\u2A10-\u2A17\u2A22-\u2A27\u2A29\u2A2A\u2A2D-\u2A31\u2A33-\u2A3C\u2A3F\u2A40\u2A42-\u2A4D\u2A50\u2A53-\u2A58\u2A5A-\u2A5D\u2A5F\u2A66\u2A6A\u2A6D-\u2A75\u2A77-\u2A9A\u2A9D-\u2AA2\u2AA4-\u2AB0\u2AB3-\u2AC8\u2ACB\u2ACC\u2ACF-\u2ADB\u2AE4\u2AE6-\u2AE9\u2AEB-\u2AF3\u2AFD\uFB00-\uFB04]|\uD835[\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDCCF\uDD04\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDD6B]/g,p={"­":"shy","‌":"zwnj","‍":"zwj","‎":"lrm","âŖ":"ic","âĸ":"it","⁥":"af","‏":"rlm","​":"ZeroWidthSpace","⁠":"NoBreak","Ė‘":"DownBreve","⃛":"tdot","⃜":"DotDot","\t":"Tab","\n":"NewLine"," ":"puncsp"," ":"MediumSpace"," ":"thinsp"," ":"hairsp"," ":"emsp13"," ":"ensp"," ":"emsp14"," ":"emsp"," ":"numsp"," ":"nbsp","  ":"ThickSpace","‾":"oline",_:"lowbar","‐":"dash","–":"ndash","—":"mdash","―":"horbar",",":"comma",";":"semi","⁏":"bsemi",":":"colon","⊴":"Colone","!":"excl","ÂĄ":"iexcl","?":"quest","Âŋ":"iquest",".":"period","â€Ĩ":"nldr","â€Ļ":"mldr","¡":"middot","'":"apos","‘":"lsquo","’":"rsquo","‚":"sbquo","‹":"lsaquo","â€ē":"rsaquo",'"':"quot","“":"ldquo","”":"rdquo","„":"bdquo","ÂĢ":"laquo","Âģ":"raquo","(":"lpar",")":"rpar","[":"lsqb","]":"rsqb","{":"lcub","}":"rcub","⌈":"lceil","⌉":"rceil","⌊":"lfloor","⌋":"rfloor","âĻ…":"lopar","âφ":"ropar","âĻ‹":"lbrke","âό":"rbrke","âĻ":"lbrkslu","âĻŽ":"rbrksld","âĻ":"lbrksld","âϐ":"rbrkslu","âĻ‘":"langd","âĻ’":"rangd","âĻ“":"lparlt","âĻ”":"rpargt","âĻ•":"gtlPar","âĻ–":"ltrPar","âŸĻ":"lobrk","⟧":"robrk","⟨":"lang","⟩":"rang","âŸĒ":"Lang","âŸĢ":"Rang","âŸŦ":"loang","⟭":"roang","❲":"lbbrk","âŗ":"rbbrk","‖":"Vert","§":"sect","Âļ":"para","@":"commat","*":"ast","/":"sol",undefined:null,"&":"amp","#":"num","%":"percnt","‰":"permil","‱":"pertenk","†":"dagger","‡":"Dagger","â€ĸ":"bull","⁃":"hybull","′":"prime","â€ŗ":"Prime","‴":"tprime","⁗":"qprime","â€ĩ":"bprime","⁁":"caret","`":"grave","´":"acute","˜":"tilde","^":"Hat","¯":"macr","˘":"breve","˙":"dot","¨":"die","˚":"ring","˝":"dblac","¸":"cedil","˛":"ogon",ˆ:"circ",ˇ:"caron","°":"deg","Š":"copy","ÂŽ":"reg","℗":"copysr",℘:"wp","℞":"rx","℧":"mho","℩":"iiota","←":"larr","↚":"nlarr","→":"rarr","↛":"nrarr","↑":"uarr","↓":"darr","↔":"harr","↮":"nharr","↕":"varr","↖":"nwarr","↗":"nearr","↘":"searr","↙":"swarr","↝":"rarrw","â†Ė¸":"nrarrw","↞":"Larr","↟":"Uarr","↠":"Rarr","↡":"Darr","â†ĸ":"larrtl","â†Ŗ":"rarrtl","↤":"mapstoleft","â†Ĩ":"mapstoup","â†Ļ":"map","↧":"mapstodown","↩":"larrhk","â†Ē":"rarrhk","â†Ģ":"larrlp","â†Ŧ":"rarrlp","↭":"harrw","↰":"lsh","↱":"rsh","↲":"ldsh","â†ŗ":"rdsh","â†ĩ":"crarr","â†ļ":"cularr","↷":"curarr","â†ē":"olarr","â†ģ":"orarr","â†ŧ":"lharu","â†Ŋ":"lhard","↾":"uharr","â†ŋ":"uharl","⇀":"rharu","⇁":"rhard","⇂":"dharr","⇃":"dharl","⇄":"rlarr","⇅":"udarr","⇆":"lrarr","⇇":"llarr","⇈":"uuarr","⇉":"rrarr","⇊":"ddarr","⇋":"lrhar","⇌":"rlhar","⇐":"lArr","⇍":"nlArr","⇑":"uArr","⇒":"rArr","⇏":"nrArr","⇓":"dArr","⇔":"iff","⇎":"nhArr","⇕":"vArr","⇖":"nwArr","⇗":"neArr","⇘":"seArr","⇙":"swArr","⇚":"lAarr","⇛":"rAarr","⇝":"zigrarr","⇤":"larrb","â‡Ĩ":"rarrb","â‡ĩ":"duarr","â‡Ŋ":"loarr","⇾":"roarr","â‡ŋ":"hoarr","∀":"forall","∁":"comp","∂":"part","âˆ‚Ė¸":"npart","∃":"exist","∄":"nexist","∅":"empty","∇":"Del","∈":"in","∉":"notin","∋":"ni","∌":"notni","Īļ":"bepsi","∏":"prod","∐":"coprod","∑":"sum","+":"plus","Âą":"pm","Ãˇ":"div","×":"times","<":"lt","≮":"nlt","<⃒":"nvlt","=":"equals","≠":"ne","=âƒĨ":"bne","âŠĩ":"Equal",">":"gt","≯":"ngt",">⃒":"nvgt","ÂŦ":"not","|":"vert","ÂĻ":"brvbar","−":"minus","∓":"mp","∔":"plusdo","⁄":"frasl","∖":"setmn","∗":"lowast","∘":"compfn","√":"Sqrt","∝":"prop","∞":"infin","∟":"angrt","∠":"ang","∠⃒":"nang","∥":"angmsd","âˆĸ":"angsph","âˆŖ":"mid","∤":"nmid","âˆĨ":"par","âˆĻ":"npar","∧":"and","∨":"or","∊":"cap","âˆŠī¸€":"caps","âˆĒ":"cup","âˆĒ":"cups","âˆĢ":"int","âˆŦ":"Int","∭":"tint","⨌":"qint","∎":"oint","∯":"Conint","∰":"Cconint","∹":"cwint","∲":"cwconint","âˆŗ":"awconint","∴":"there4","âˆĩ":"becaus","âˆļ":"ratio","∡":"Colon","∸":"minusd","âˆē":"mDDot","âˆģ":"homtht","âˆŧ":"sim","≁":"nsim","âˆŧ⃒":"nvsim","âˆŊ":"bsim","âˆŊĖą":"race","∞":"ac","âˆžĖŗ":"acE","âˆŋ":"acd","≀":"wr","≂":"esim","â‰‚Ė¸":"nesim","≃":"sime","≄":"nsime","≅":"cong","≇":"ncong","≆":"simne","≈":"ap","≉":"nap","≊":"ape","≋":"apid","â‰‹Ė¸":"napid","≌":"bcong","≍":"CupCap","≭":"NotCupCap","≍⃒":"nvap","≎":"bump","â‰ŽĖ¸":"nbump","≏":"bumpe","â‰Ė¸":"nbumpe","≐":"doteq","â‰Ė¸":"nedot","≑":"eDot","≒":"efDot","≓":"erDot","≔":"colone","≕":"ecolon","≖":"ecir","≗":"cire","≙":"wedgeq","≚":"veeeq","≜":"trie","≟":"equest","≡":"equiv","â‰ĸ":"nequiv","≡âƒĨ":"bnequiv","≤":"le","≰":"nle","≤⃒":"nvle","â‰Ĩ":"ge","≱":"nge","â‰Ĩ⃒":"nvge","â‰Ļ":"lE","â‰Ļˏ":"nlE","≧":"gE","â‰§Ė¸":"ngE","â‰¨ī¸€":"lvnE","≨":"lnE","≩":"gnE","â‰Šī¸€":"gvnE","â‰Ē":"ll","â‰Ēˏ":"nLtv","â‰Ē⃒":"nLt","â‰Ģ":"gg","â‰Ģˏ":"nGtv","â‰Ģ⃒":"nGt","â‰Ŧ":"twixt","≲":"lsim","≴":"nlsim","â‰ŗ":"gsim","â‰ĩ":"ngsim","â‰ļ":"lg","≸":"ntlg","≷":"gl","≹":"ntgl","â‰ē":"pr","⊀":"npr","â‰ģ":"sc","⊁":"nsc","â‰ŧ":"prcue","⋠":"nprcue","â‰Ŋ":"sccue","⋡":"nsccue","≾":"prsim","â‰ŋ":"scsim","â‰ŋˏ":"NotSucceedsTilde","⊂":"sub","⊄":"nsub","⊂⃒":"vnsub","⊃":"sup","⊅":"nsup","⊃⃒":"vnsup","⊆":"sube","⊈":"nsube","⊇":"supe","⊉":"nsupe","âŠŠī¸€":"vsubne","⊊":"subne","âŠ‹ī¸€":"vsupne","⊋":"supne","⊍":"cupdot","⊎":"uplus","⊏":"sqsub","âŠĖ¸":"NotSquareSubset","⊐":"sqsup","âŠĖ¸":"NotSquareSuperset","⊑":"sqsube","â‹ĸ":"nsqsube","⊒":"sqsupe","â‹Ŗ":"nsqsupe","⊓":"sqcap","âŠ“ī¸€":"sqcaps","⊔":"sqcup","âŠ”ī¸€":"sqcups","⊕":"oplus","⊖":"ominus","⊗":"otimes","⊘":"osol","⊙":"odot","⊚":"ocir","⊛":"oast","⊝":"odash","⊞":"plusb","⊟":"minusb","⊠":"timesb","⊡":"sdotb","âŠĸ":"vdash","âŠŦ":"nvdash","âŠŖ":"dashv","⊤":"top","âŠĨ":"bot","⊧":"models","⊨":"vDash","⊭":"nvDash","⊩":"Vdash","⊮":"nVdash","âŠĒ":"Vvdash","âŠĢ":"VDash","⊯":"nVDash","⊰":"prurel","⊲":"vltri","â‹Ē":"nltri","âŠŗ":"vrtri","â‹Ģ":"nrtri","⊴":"ltrie","â‹Ŧ":"nltrie","⊴⃒":"nvltrie","âŠĩ":"rtrie","⋭":"nrtrie","âŠĩ⃒":"nvrtrie","âŠļ":"origof","⊷":"imof","⊸":"mumap","⊹":"hercon","âŠē":"intcal","âŠģ":"veebar","âŠŊ":"barvee","⊾":"angrtvb","âŠŋ":"lrtri","⋀":"Wedge","⋁":"Vee","⋂":"xcap","⋃":"xcup","⋄":"diam","⋅":"sdot","⋆":"Star","⋇":"divonx","⋈":"bowtie","⋉":"ltimes","⋊":"rtimes","⋋":"lthree","⋌":"rthree","⋍":"bsime","⋎":"cuvee","⋏":"cuwed","⋐":"Sub","⋑":"Sup","⋒":"Cap","⋓":"Cup","⋔":"fork","⋕":"epar","⋖":"ltdot","⋗":"gtdot","⋘":"Ll","â‹˜Ė¸":"nLl","⋙":"Gg","â‹™Ė¸":"nGg","â‹šī¸€":"lesg","⋚":"leg","⋛":"gel","â‹›ī¸€":"gesl","⋞":"cuepr","⋟":"cuesc","â‹Ļ":"lnsim","⋧":"gnsim","⋨":"prnsim","⋩":"scnsim","⋮":"vellip","⋯":"ctdot","⋰":"utdot","⋱":"dtdot","⋲":"disin","â‹ŗ":"isinsv","⋴":"isins","â‹ĩ":"isindot","â‹ĩˏ":"notindot","â‹ļ":"notinvc","⋷":"notinvb","⋹":"isinE","â‹šĖ¸":"notinE","â‹ē":"nisd","â‹ģ":"xnis","â‹ŧ":"nis","â‹Ŋ":"notnivc","⋾":"notnivb","⌅":"barwed","⌆":"Barwed","⌌":"drcrop","⌍":"dlcrop","⌎":"urcrop","⌏":"ulcrop","⌐":"bnot","⌒":"profline","⌓":"profsurf","⌕":"telrec","⌖":"target","⌜":"ulcorn","⌝":"urcorn","⌞":"dlcorn","⌟":"drcorn","âŒĸ":"frown","âŒŖ":"smile","⌭":"cylcty","⌮":"profalar","âŒļ":"topbot","âŒŊ":"ovbar","âŒŋ":"solbar","âŧ":"angzarr","⎰":"lmoust","⎱":"rmoust","⎴":"tbrk","âŽĩ":"bbrk","âŽļ":"bbrktbrk","⏜":"OverParenthesis","⏝":"UnderParenthesis","⏞":"OverBrace","⏟":"UnderBrace","âĸ":"trpezium","⏧":"elinters","âŖ":"blank","─":"boxh","│":"boxv","┌":"boxdr","┐":"boxdl","└":"boxur","┘":"boxul","├":"boxvr","┤":"boxvl","â”Ŧ":"boxhd","┴":"boxhu","â”ŧ":"boxvh","═":"boxH","║":"boxV","╒":"boxdR","╓":"boxDr","╔":"boxDR","╕":"boxdL","╖":"boxDl","╗":"boxDL","╘":"boxuR","╙":"boxUr","╚":"boxUR","╛":"boxuL","╜":"boxUl","╝":"boxUL","╞":"boxvR","╟":"boxVr","╠":"boxVR","╡":"boxvL","â•ĸ":"boxVl","â•Ŗ":"boxVL","╤":"boxHd","â•Ĩ":"boxhD","â•Ļ":"boxHD","╧":"boxHu","╨":"boxhU","╩":"boxHU","â•Ē":"boxvH","â•Ģ":"boxVh","â•Ŧ":"boxVH","▀":"uhblk","▄":"lhblk","█":"block","░":"blk14","▒":"blk12","▓":"blk34","□":"squ","â–Ē":"squf","â–Ģ":"EmptyVerySmallSquare","▭":"rect","▮":"marker","▱":"fltns","â–ŗ":"xutri","▴":"utrif","â–ĩ":"utri","▸":"rtrif","▹":"rtri","â–Ŋ":"xdtri","▾":"dtrif","â–ŋ":"dtri","◂":"ltrif","◃":"ltri","◊":"loz","○":"cir","â—Ŧ":"tridot","◯":"xcirc","◸":"ultri","◹":"urtri","â—ē":"lltri","â—ģ":"EmptySmallSquare","â—ŧ":"FilledSmallSquare","★":"starf","☆":"star","☎":"phone","♀":"female","♂":"male","♠":"spades","â™Ŗ":"clubs","â™Ĩ":"hearts","â™Ļ":"diams","â™Ē":"sung","✓":"check","✗":"cross","✠":"malt","âœļ":"sext","❘":"VerticalSeparator","⟈":"bsolhsub","⟉":"suphsol","âŸĩ":"xlarr","âŸļ":"xrarr","⟷":"xharr","⟸":"xlArr","⟹":"xrArr","âŸē":"xhArr","âŸŧ":"xmap","âŸŋ":"dzigrarr","⤂":"nvlArr","⤃":"nvrArr","⤄":"nvHarr","⤅":"Map","⤌":"lbarr","⤍":"rbarr","⤎":"lBarr","⤏":"rBarr","⤐":"RBarr","⤑":"DDotrahd","⤒":"UpArrowBar","⤓":"DownArrowBar","⤖":"Rarrtl","⤙":"latail","⤚":"ratail","⤛":"lAtail","⤜":"rAtail","⤝":"larrfs","⤞":"rarrfs","⤟":"larrbfs","⤠":"rarrbfs","â¤Ŗ":"nwarhk","⤤":"nearhk","â¤Ĩ":"searhk","â¤Ļ":"swarhk","⤧":"nwnear","⤨":"toea","⤊":"tosa","â¤Ē":"swnwar","â¤ŗ":"rarrc","â¤ŗĖ¸":"nrarrc","â¤ĩ":"cudarrr","â¤ļ":"ldca","⤡":"rdca","⤸":"cudarrl","⤚":"larrpl","â¤ŧ":"curarrm","â¤Ŋ":"cularrp","âĨ…":"rarrpl","âĨˆ":"harrcir","âĨ‰":"Uarrocir","âĨŠ":"lurdshar","âĨ‹":"ldrushar","âĨŽ":"LeftRightVector","âĨ":"RightUpDownVector","âĨ":"DownLeftRightVector","âĨ‘":"LeftUpDownVector","âĨ’":"LeftVectorBar","âĨ“":"RightVectorBar","âĨ”":"RightUpVectorBar","âĨ•":"RightDownVectorBar","âĨ–":"DownLeftVectorBar","âĨ—":"DownRightVectorBar","âĨ˜":"LeftUpVectorBar","âĨ™":"LeftDownVectorBar","âĨš":"LeftTeeVector","âĨ›":"RightTeeVector","âĨœ":"RightUpTeeVector","âĨ":"RightDownTeeVector","âĨž":"DownLeftTeeVector","âĨŸ":"DownRightTeeVector","âĨ ":"LeftUpTeeVector","âĨĄ":"LeftDownTeeVector","âĨĸ":"lHar","âĨŖ":"uHar","âĨ¤":"rHar","âĨĨ":"dHar","âĨĻ":"luruhar","âĨ§":"ldrdhar","âĨ¨":"ruluhar","âĨŠ":"rdldhar","âĨĒ":"lharul","âĨĢ":"llhard","âĨŦ":"rharul","âĨ­":"lrhard","âĨŽ":"udhar","âĨ¯":"duhar","âĨ°":"RoundImplies","âĨą":"erarr","âĨ˛":"simrarr","âĨŗ":"larrsim","âĨ´":"rarrsim","âĨĩ":"rarrap","âĨļ":"ltlarr","âĨ¸":"gtrarr","âĨš":"subrarr","âĨģ":"suplarr","âĨŧ":"lfisht","âĨŊ":"rfisht","âĨž":"ufisht","âĨŋ":"dfisht","âϚ":"vzigzag","âϜ":"vangrt","âĻ":"angrtvbd","âϤ":"ange","âĻĨ":"range","âĻĻ":"dwangle","âϧ":"uwangle","âύ":"angmsdaa","âĻŠ":"angmsdab","âĻĒ":"angmsdac","âĻĢ":"angmsdad","âĻŦ":"angmsdae","âĻ­":"angmsdaf","âĻŽ":"angmsdag","âϝ":"angmsdah","âϰ":"bemptyv","âĻą":"demptyv","âϞ":"cemptyv","âĻŗ":"raemptyv","âĻ´":"laemptyv","âĻĩ":"ohbar","âĻļ":"omid","âώ":"opar","âĻš":"operp","âĻģ":"olcross","âĻŧ":"odsold","âĻž":"olcir","âĻŋ":"ofcir","⧀":"olt","⧁":"ogt","⧂":"cirscir","⧃":"cirE","⧄":"solb","⧅":"bsolb","⧉":"boxbox","⧍":"trisb","⧎":"rtriltri","⧏":"LeftTriangleBar","â§Ė¸":"NotLeftTriangleBar","⧐":"RightTriangleBar","â§Ė¸":"NotRightTriangleBar","⧜":"iinfin","⧝":"infintie","⧞":"nvinfin","â§Ŗ":"eparsl","⧤":"smeparsl","â§Ĩ":"eqvparsl","â§Ģ":"lozf","â§´":"RuleDelayed","â§ļ":"dsol","⨀":"xodot","⨁":"xoplus","⨂":"xotime","⨄":"xuplus","⨆":"xsqcup","⨍":"fpartint","⨐":"cirfnint","⨑":"awint","⨒":"rppolint","⨓":"scpolint","⨔":"npolint","⨕":"pointint","⨖":"quatint","⨗":"intlarhk","â¨ĸ":"pluscir","â¨Ŗ":"plusacir","⨤":"simplus","â¨Ĩ":"plusdu","â¨Ļ":"plussim","⨧":"plustwo","⨊":"mcomma","â¨Ē":"minusdu","⨭":"loplus","⨎":"roplus","⨯":"Cross","⨰":"timesd","⨹":"timesbar","â¨ŗ":"smashp","⨴":"lotimes","â¨ĩ":"rotimes","â¨ļ":"otimesas","⨡":"Otimes","⨸":"odiv","⨚":"triplus","â¨ē":"triminus","â¨ģ":"tritime","â¨ŧ":"iprod","â¨ŋ":"amalg","⩀":"capdot","⩂":"ncup","⊃":"ncap","⩄":"capand","⩅":"cupor","⩆":"cupcap","⩇":"capcup","⊈":"cupbrcap","⩉":"capbrcup","⩊":"cupcup","⩋":"capcap","⩌":"ccups","⊍":"ccaps","⊐":"ccupssm","⩓":"And","⩔":"Or","⩕":"andand","⩖":"oror","⩗":"orslope","⊘":"andslope","⩚":"andv","⩛":"orv","⩜":"andd","⊝":"ord","⩟":"wedbar","âŠĻ":"sdote","âŠĒ":"simdot","⊭":"congdot","âŠ­Ė¸":"ncongdot","⊎":"easter","⊯":"apacir","⊰":"apE","âŠ°Ė¸":"napE","⊹":"eplus","⊲":"pluse","âŠŗ":"Esim","⊡":"eDDot","⊸":"equivDD","⊚":"ltcir","âŠē":"gtcir","âŠģ":"ltquest","âŠŧ":"gtquest","âŠŊ":"les","âŠŊˏ":"nles","⊞":"ges","âŠžĖ¸":"nges","âŠŋ":"lesdot","âĒ€":"gesdot","âǁ":"lesdoto","âĒ‚":"gesdoto","âǃ":"lesdotor","âĒ„":"gesdotol","âĒ…":"lap","âdž":"gap","âLJ":"lne","âLj":"gne","âlj":"lnap","âNJ":"gnap","âĒ‹":"lEg","ânj":"gEl","âĒ":"lsime","âĒŽ":"gsime","âĒ":"lsimg","âǐ":"gsiml","âĒ‘":"lgE","âĒ’":"glE","âĒ“":"lesges","âĒ”":"gesles","âĒ•":"els","âĒ–":"egs","âĒ—":"elsdot","âǘ":"egsdot","âĒ™":"el","âǚ":"eg","âĒ":"siml","âĒž":"simg","âǟ":"simlE","âĒ ":"simgE","âĒĄ":"LessLess","âĒĄĖ¸":"NotNestedLessLess","âĒĸ":"GreaterGreater","âĒĸˏ":"NotNestedGreaterGreater","âǤ":"glj","âĒĨ":"gla","âĒĻ":"ltcc","âǧ":"gtcc","âǍ":"lescc","âĒŠ":"gescc","âĒĒ":"smt","âĒĢ":"lat","âĒŦ":"smte","âĒŦ":"smtes","âĒ­":"late","âǭ":"lates","âĒŽ":"bumpE","âǝ":"pre","âǝˏ":"npre","âǰ":"sce","âǰˏ":"nsce","âĒŗ":"prE","âĒ´":"scE","âĒĩ":"prnE","âĒļ":"scnE","âǎ":"prap","âǏ":"scap","âĒš":"prnap","âĒē":"scnap","âĒģ":"Pr","âĒŧ":"Sc","âĒŊ":"subdot","âĒž":"supdot","âĒŋ":"subplus","âĢ€":"supplus","ấ":"submult","âĢ‚":"supmult","ẫ":"subedot","âĢ„":"supedot","âĢ…":"subE","â̅ˏ":"nsubE","â̆":"supE","â̆ˏ":"nsupE","â̇":"subsim","â̈":"supsim","â̋":"vsubnE","âĢ‹":"subnE","âĢŒī¸€":"vsupnE","â̌":"supnE","âĢ":"csub","â̐":"csup","âĢ‘":"csube","âĢ’":"csupe","âĢ“":"subsup","âĢ”":"supsub","âĢ•":"subsub","âĢ–":"supsup","âĢ—":"suphsub","â̘":"supdsub","âĢ™":"forkv","â̚":"topfork","âĢ›":"mlcp","â̤":"Dashv","âĢĻ":"Vdashl","â̧":"Barv","â̍":"vBar","âĢŠ":"vBarv","âĢĢ":"Vbar","âĢŦ":"Not","âĢ­":"bNot","âĢŽ":"rnmid","â̝":"cirmid","â̰":"midcir","âĢą":"topcir","â̞":"nhpar","âĢŗ":"parsim","âĢŊ":"parsl","âĢŊâƒĨ":"nparsl","♭":"flat","♮":"natur","♯":"sharp","¤":"curren","Âĸ":"cent",$:"dollar","ÂŖ":"pound","ÂĨ":"yen","â‚Ŧ":"euro","š":"sup1","ÂŊ":"half","⅓":"frac13","Âŧ":"frac14","⅕":"frac15","⅙":"frac16","⅛":"frac18","²":"sup2","⅔":"frac23","⅖":"frac25","Âŗ":"sup3","ž":"frac34","⅗":"frac35","⅜":"frac38","⅘":"frac45","⅚":"frac56","⅝":"frac58","⅞":"frac78",đ’ļ:"ascr",𝕒:"aopf",𝔞:"afr",𝔸:"Aopf",𝔄:"Afr",𝒜:"Ascr",ÂĒ:"ordf",ÃĄ:"aacute",Á:"Aacute",à:"agrave",À:"Agrave",ă:"abreve",Ă:"Abreve",Ãĸ:"acirc",Â:"Acirc",ÃĨ:"aring",Å:"angst",ä:"auml",Ä:"Auml",ÃŖ:"atilde",Ã:"Atilde",ą:"aogon",Ą:"Aogon",ā:"amacr",Ā:"Amacr",ÃĻ:"aelig",Æ:"AElig",𝒷:"bscr",𝕓:"bopf",𝔟:"bfr",𝔹:"Bopf",â„Ŧ:"Bscr",𝔅:"Bfr",𝔠:"cfr",𝒸:"cscr",𝕔:"copf",ℭ:"Cfr",𝒞:"Cscr",ℂ:"Copf",ć:"cacute",Ć:"Cacute",ĉ:"ccirc",Ĉ:"Ccirc",č:"ccaron",Č:"Ccaron",ċ:"cdot",Ċ:"Cdot",ç:"ccedil",Ç:"Ccedil","℅":"incare",𝔡:"dfr",ⅆ:"dd",𝕕:"dopf",𝒹:"dscr",𝒟:"Dscr",𝔇:"Dfr",ⅅ:"DD",đ”ģ:"Dopf",ď:"dcaron",Ď:"Dcaron",đ:"dstrok",Đ:"Dstrok",ð:"eth",Ð:"ETH",ⅇ:"ee",ℯ:"escr",đ”ĸ:"efr",𝕖:"eopf",ℰ:"Escr",𝔈:"Efr",đ”ŧ:"Eopf",Ê:"eacute",É:"Eacute",è:"egrave",È:"Egrave",ÃĒ:"ecirc",Ê:"Ecirc",ě:"ecaron",Ě:"Ecaron",ÃĢ:"euml",Ë:"Euml",ė:"edot",Ė:"Edot",ę:"eogon",Ę:"Eogon",ē:"emacr",Ē:"Emacr",đ”Ŗ:"ffr",𝕗:"fopf",đ’ģ:"fscr",𝔉:"Ffr",đ”Ŋ:"Fopf",ℱ:"Fscr",īŦ€:"fflig",īŦƒ:"ffilig",īŦ„:"ffllig",īŦ:"filig",fj:"fjlig",īŦ‚:"fllig",ƒ:"fnof",ℊ:"gscr",𝕘:"gopf",𝔤:"gfr",đ’ĸ:"Gscr",𝔾:"Gopf",𝔊:"Gfr",Įĩ:"gacute",ğ:"gbreve",Ğ:"Gbreve",ĝ:"gcirc",Ĝ:"Gcirc",ÄĄ:"gdot",Ä :"Gdot",Äĸ:"Gcedil",đ”Ĩ:"hfr",ℎ:"planckh",đ’Ŋ:"hscr",𝕙:"hopf",ℋ:"Hscr",ℌ:"Hfr",ℍ:"Hopf",ÄĨ:"hcirc",Ĥ:"Hcirc",ℏ:"hbar",ħ:"hstrok",ÄĻ:"Hstrok",𝕚:"iopf",đ”Ļ:"ifr",𝒾:"iscr",ⅈ:"ii",𝕀:"Iopf",ℐ:"Iscr",ℑ:"Im",í:"iacute",Í:"Iacute",ÃŦ:"igrave",Ì:"Igrave",ÃŽ:"icirc",Î:"Icirc",ï:"iuml",Ï:"Iuml",ÄŠ:"itilde",Ĩ:"Itilde",İ:"Idot",į:"iogon",ÄŽ:"Iogon",ÄĢ:"imacr",ÄĒ:"Imacr",Äŗ:"ijlig",IJ:"IJlig",Äą:"imath",đ’ŋ:"jscr",𝕛:"jopf",𝔧:"jfr",đ’Ĩ:"Jscr",𝔍:"Jfr",𝕁:"Jopf",Äĩ:"jcirc",Ä´:"Jcirc",ȡ:"jmath",𝕜:"kopf",𝓀:"kscr",𝔨:"kfr",đ’Ļ:"Kscr",𝕂:"Kopf",𝔎:"Kfr",ġ:"kcedil",Äļ:"Kcedil",𝔩:"lfr",𝓁:"lscr",ℓ:"ell",𝕝:"lopf",ℒ:"Lscr",𝔏:"Lfr",𝕃:"Lopf",Äē:"lacute",Äš:"Lacute",Äž:"lcaron",ÄŊ:"Lcaron",Äŧ:"lcedil",Äģ:"Lcedil",ł:"lstrok",Ł:"Lstrok",ŀ:"lmidot",Äŋ:"Lmidot",đ”Ē:"mfr",𝕞:"mopf",𝓂:"mscr",𝔐:"Mfr",𝕄:"Mopf",â„ŗ:"Mscr",đ”Ģ:"nfr",𝕟:"nopf",𝓃:"nscr",ℕ:"Nopf",𝒩:"Nscr",𝔑:"Nfr",ń:"nacute",Ń:"Nacute",ň:"ncaron",Ň:"Ncaron",Ãą:"ntilde",Ñ:"Ntilde",ņ:"ncedil",Ņ:"Ncedil","№":"numero",ŋ:"eng",Ŋ:"ENG",𝕠:"oopf",đ”Ŧ:"ofr",ℴ:"oscr",đ’Ē:"Oscr",𝔒:"Ofr",𝕆:"Oopf",Âē:"ordm",Ãŗ:"oacute",Ó:"Oacute",Ã˛:"ograve",Ò:"Ograve",ô:"ocirc",Ô:"Ocirc",Ãļ:"ouml",Ö:"Ouml",ő:"odblac",Ő:"Odblac",Ãĩ:"otilde",Õ:"Otilde",ø:"oslash",Ø:"Oslash",ō:"omacr",Ō:"Omacr",œ:"oelig",Œ:"OElig",𝔭:"pfr",𝓅:"pscr",𝕡:"popf",ℙ:"Popf",𝔓:"Pfr",đ’Ģ:"Pscr",đ•ĸ:"qopf",𝔮:"qfr",𝓆:"qscr",đ’Ŧ:"Qscr",𝔔:"Qfr",ℚ:"Qopf",ĸ:"kgreen",đ”¯:"rfr",đ•Ŗ:"ropf",𝓇:"rscr",ℛ:"Rscr",ℜ:"Re",ℝ:"Ropf",ŕ:"racute",Ŕ:"Racute",ř:"rcaron",Ř:"Rcaron",ŗ:"rcedil",Ŗ:"Rcedil",𝕤:"sopf",𝓈:"sscr",𝔰:"sfr",𝕊:"Sopf",𝔖:"Sfr",𝒮:"Sscr","Ⓢ":"oS",ś:"sacute",Ś:"Sacute",ŝ:"scirc",Ŝ:"Scirc",ÅĄ:"scaron",Å :"Scaron",ş:"scedil",Ş:"Scedil",ß:"szlig",𝔱:"tfr",𝓉:"tscr",đ•Ĩ:"topf",đ’¯:"Tscr",𝔗:"Tfr",𝕋:"Topf",ÅĨ:"tcaron",Ť:"Tcaron",ÅŖ:"tcedil",Åĸ:"Tcedil","â„ĸ":"trade",ŧ:"tstrok",ÅĻ:"Tstrok",𝓊:"uscr",đ•Ļ:"uopf",𝔲:"ufr",𝕌:"Uopf",𝔘:"Ufr",𝒰:"Uscr",Ãē:"uacute",Ú:"Uacute",Ú:"ugrave",Ù:"Ugrave",Å­:"ubreve",ÅŦ:"Ubreve",Ãģ:"ucirc",Û:"Ucirc",ů:"uring",ÅŽ:"Uring",Ãŧ:"uuml",Ü:"Uuml",Åą:"udblac",Ű:"Udblac",ÅŠ:"utilde",Ũ:"Utilde",Åŗ:"uogon",Ş:"Uogon",ÅĢ:"umacr",ÅĒ:"Umacr",đ”ŗ:"vfr",𝕧:"vopf",𝓋:"vscr",𝔙:"Vfr",𝕍:"Vopf",𝒱:"Vscr",𝕨:"wopf",𝓌:"wscr",𝔴:"wfr",𝒲:"Wscr",𝕎:"Wopf",𝔚:"Wfr",Åĩ:"wcirc",Å´:"Wcirc",đ”ĩ:"xfr",𝓍:"xscr",𝕩:"xopf",𝕏:"Xopf",𝔛:"Xfr",đ’ŗ:"Xscr",đ”ļ:"yfr",𝓎:"yscr",đ•Ē:"yopf",𝒴:"Yscr",𝔜:"Yfr",𝕐:"Yopf",ÃŊ:"yacute",Ý:"Yacute",Ŏ:"ycirc",Åļ:"Ycirc",Ãŋ:"yuml",Ÿ:"Yuml",𝓏:"zscr",𝔷:"zfr",đ•Ģ:"zopf",ℨ:"Zfr",ℤ:"Zopf",đ’ĩ:"Zscr",Åē:"zacute",Åš:"Zacute",Åž:"zcaron",ÅŊ:"Zcaron",Åŧ:"zdot",Åģ:"Zdot",Æĩ:"imped",Þ:"thorn",Þ:"THORN",ʼn:"napos",Îą:"alpha",Α:"Alpha",β:"beta",Β:"Beta",Îŗ:"gamma",Γ:"Gamma",δ:"delta",Δ:"Delta",Îĩ:"epsi",Īĩ:"epsiv",Ε:"Epsilon",Ī:"gammad",Μ:"Gammad",Îļ:"zeta",Ζ:"Zeta",Ρ:"eta",Η:"Eta",θ:"theta",Ī‘:"thetav",Θ:"Theta",Κ:"iota",Ι:"Iota",Îē:"kappa",ΰ:"kappav",Κ:"Kappa",Îģ:"lambda",Λ:"Lambda",Îŧ:"mu",Âĩ:"micro",Μ:"Mu",ÎŊ:"nu",Ν:"Nu",Ξ:"xi",Ξ:"Xi",Îŋ:"omicron",Ο:"Omicron",Ī€:"pi",Ī–:"piv",Π:"Pi",΁:"rho",Īą:"rhov",ÎĄ:"Rho",΃:"sigma",ÎŖ:"Sigma",Ī‚:"sigmaf",Ī„:"tau",Τ:"Tau",Ī…:"upsi",ÎĨ:"Upsilon",Ī’:"Upsi",Ά:"phi",Ī•:"phiv",ÎĻ:"Phi",·:"chi",Χ:"Chi",Έ:"psi",Ψ:"Psi",Ή:"omega",Ί:"ohm",а:"acy",А:"Acy",Đą:"bcy",Б:"Bcy",в:"vcy",В:"Vcy",Đŗ:"gcy",Г:"Gcy",Ņ“:"gjcy",Ѓ:"GJcy",Đ´:"dcy",Д:"Dcy",Ņ’:"djcy",Ђ:"DJcy",Đĩ:"iecy",Е:"IEcy",Ņ‘:"iocy",Ё:"IOcy",Ņ”:"jukcy",Є:"Jukcy",Đļ:"zhcy",Ж:"ZHcy",С:"zcy",З:"Zcy",Ņ•:"dscy",Ѕ:"DScy",и:"icy",И:"Icy",Ņ–:"iukcy",І:"Iukcy",Ņ—:"yicy",Ї:"YIcy",Đš:"jcy",Й:"Jcy",Ҙ:"jsercy",Ј:"Jsercy",Đē:"kcy",К:"Kcy",Ҝ:"kjcy",Ќ:"KJcy",Đģ:"lcy",Л:"Lcy",Ņ™:"ljcy",Љ:"LJcy",Đŧ:"mcy",М:"Mcy",ĐŊ:"ncy",Н:"Ncy",Қ:"njcy",Њ:"NJcy",Đž:"ocy",О:"Ocy",Đŋ:"pcy",П:"Pcy",Ņ€:"rcy",Đ :"Rcy",ҁ:"scy",ĐĄ:"Scy",Ņ‚:"tcy",Đĸ:"Tcy",Ņ›:"tshcy",Ћ:"TSHcy",҃:"ucy",ĐŖ:"Ucy",Ņž:"ubrcy",Ў:"Ubrcy",Ņ„:"fcy",Ф:"Fcy",Ņ…:"khcy",ĐĨ:"KHcy",҆:"tscy",ĐĻ:"TScy",҇:"chcy",Ч:"CHcy",ҟ:"dzcy",Џ:"DZcy",҈:"shcy",Ш:"SHcy",҉:"shchcy",ĐŠ:"SHCHcy",Ҋ:"hardcy",ĐĒ:"HARDcy",Ņ‹:"ycy",ĐĢ:"Ycy",Ҍ:"softcy",ĐŦ:"SOFTcy",Ņ:"ecy",Đ­:"Ecy",ŅŽ:"yucy",ĐŽ:"YUcy",Ņ:"yacy",Đ¯:"YAcy",â„ĩ:"aleph",â„ļ:"beth",ℷ:"gimel",ℸ:"daleth"},f=/["&'<>`]/g,d={'"':""","&":"&","'":"'","<":"<",">":">","`":"`"},h=/&#(?:[xX][^a-fA-F0-9]|[^0-9xX])/,g=/[\0-\x08\x0B\x0E-\x1F\x7F-\x9F\uFDD0-\uFDEF\uFFFE\uFFFF]|[\uD83F\uD87F\uD8BF\uD8FF\uD93F\uD97F\uD9BF\uD9FF\uDA3F\uDA7F\uDABF\uDAFF\uDB3F\uDB7F\uDBBF\uDBFF][\uDFFE\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/,m=/&(CounterClockwiseContourIntegral|DoubleLongLeftRightArrow|ClockwiseContourIntegral|NotNestedGreaterGreater|NotSquareSupersetEqual|DiacriticalDoubleAcute|NotRightTriangleEqual|NotSucceedsSlantEqual|NotPrecedesSlantEqual|CloseCurlyDoubleQuote|NegativeVeryThinSpace|DoubleContourIntegral|FilledVerySmallSquare|CapitalDifferentialD|OpenCurlyDoubleQuote|EmptyVerySmallSquare|NestedGreaterGreater|DoubleLongRightArrow|NotLeftTriangleEqual|NotGreaterSlantEqual|ReverseUpEquilibrium|DoubleLeftRightArrow|NotSquareSubsetEqual|NotDoubleVerticalBar|RightArrowLeftArrow|NotGreaterFullEqual|NotRightTriangleBar|SquareSupersetEqual|DownLeftRightVector|DoubleLongLeftArrow|leftrightsquigarrow|LeftArrowRightArrow|NegativeMediumSpace|blacktriangleright|RightDownVectorBar|PrecedesSlantEqual|RightDoubleBracket|SucceedsSlantEqual|NotLeftTriangleBar|RightTriangleEqual|SquareIntersection|RightDownTeeVector|ReverseEquilibrium|NegativeThickSpace|longleftrightarrow|Longleftrightarrow|LongLeftRightArrow|DownRightTeeVector|DownRightVectorBar|GreaterSlantEqual|SquareSubsetEqual|LeftDownVectorBar|LeftDoubleBracket|VerticalSeparator|rightleftharpoons|NotGreaterGreater|NotSquareSuperset|blacktriangleleft|blacktriangledown|NegativeThinSpace|LeftDownTeeVector|NotLessSlantEqual|leftrightharpoons|DoubleUpDownArrow|DoubleVerticalBar|LeftTriangleEqual|FilledSmallSquare|twoheadrightarrow|NotNestedLessLess|DownLeftTeeVector|DownLeftVectorBar|RightAngleBracket|NotTildeFullEqual|NotReverseElement|RightUpDownVector|DiacriticalTilde|NotSucceedsTilde|circlearrowright|NotPrecedesEqual|rightharpoondown|DoubleRightArrow|NotSucceedsEqual|NonBreakingSpace|NotRightTriangle|LessEqualGreater|RightUpTeeVector|LeftAngleBracket|GreaterFullEqual|DownArrowUpArrow|RightUpVectorBar|twoheadleftarrow|GreaterEqualLess|downharpoonright|RightTriangleBar|ntrianglerighteq|NotSupersetEqual|LeftUpDownVector|DiacriticalAcute|rightrightarrows|vartriangleright|UpArrowDownArrow|DiacriticalGrave|UnderParenthesis|EmptySmallSquare|LeftUpVectorBar|leftrightarrows|DownRightVector|downharpoonleft|trianglerighteq|ShortRightArrow|OverParenthesis|DoubleLeftArrow|DoubleDownArrow|NotSquareSubset|bigtriangledown|ntrianglelefteq|UpperRightArrow|curvearrowright|vartriangleleft|NotLeftTriangle|nleftrightarrow|LowerRightArrow|NotHumpDownHump|NotGreaterTilde|rightthreetimes|LeftUpTeeVector|NotGreaterEqual|straightepsilon|LeftTriangleBar|rightsquigarrow|ContourIntegral|rightleftarrows|CloseCurlyQuote|RightDownVector|LeftRightVector|nLeftrightarrow|leftharpoondown|circlearrowleft|SquareSuperset|OpenCurlyQuote|hookrightarrow|HorizontalLine|DiacriticalDot|NotLessGreater|ntriangleright|DoubleRightTee|InvisibleComma|InvisibleTimes|LowerLeftArrow|DownLeftVector|NotSubsetEqual|curvearrowleft|trianglelefteq|NotVerticalBar|TildeFullEqual|downdownarrows|NotGreaterLess|RightTeeVector|ZeroWidthSpace|looparrowright|LongRightArrow|doublebarwedge|ShortLeftArrow|ShortDownArrow|RightVectorBar|GreaterGreater|ReverseElement|rightharpoonup|LessSlantEqual|leftthreetimes|upharpoonright|rightarrowtail|LeftDownVector|Longrightarrow|NestedLessLess|UpperLeftArrow|nshortparallel|leftleftarrows|leftrightarrow|Leftrightarrow|LeftRightArrow|longrightarrow|upharpoonleft|RightArrowBar|ApplyFunction|LeftTeeVector|leftarrowtail|NotEqualTilde|varsubsetneqq|varsupsetneqq|RightTeeArrow|SucceedsEqual|SucceedsTilde|LeftVectorBar|SupersetEqual|hookleftarrow|DifferentialD|VerticalTilde|VeryThinSpace|blacktriangle|bigtriangleup|LessFullEqual|divideontimes|leftharpoonup|UpEquilibrium|ntriangleleft|RightTriangle|measuredangle|shortparallel|longleftarrow|Longleftarrow|LongLeftArrow|DoubleLeftTee|Poincareplane|PrecedesEqual|triangleright|DoubleUpArrow|RightUpVector|fallingdotseq|looparrowleft|PrecedesTilde|NotTildeEqual|NotTildeTilde|smallsetminus|Proportional|triangleleft|triangledown|UnderBracket|NotHumpEqual|exponentiale|ExponentialE|NotLessTilde|HilbertSpace|RightCeiling|blacklozenge|varsupsetneq|HumpDownHump|GreaterEqual|VerticalLine|LeftTeeArrow|NotLessEqual|DownTeeArrow|LeftTriangle|varsubsetneq|Intersection|NotCongruent|DownArrowBar|LeftUpVector|LeftArrowBar|risingdotseq|GreaterTilde|RoundImplies|SquareSubset|ShortUpArrow|NotSuperset|quaternions|precnapprox|backepsilon|preccurlyeq|OverBracket|blacksquare|MediumSpace|VerticalBar|circledcirc|circleddash|CircleMinus|CircleTimes|LessGreater|curlyeqprec|curlyeqsucc|diamondsuit|UpDownArrow|Updownarrow|RuleDelayed|Rrightarrow|updownarrow|RightVector|nRightarrow|nrightarrow|eqslantless|LeftCeiling|Equilibrium|SmallCircle|expectation|NotSucceeds|thickapprox|GreaterLess|SquareUnion|NotPrecedes|NotLessLess|straightphi|succnapprox|succcurlyeq|SubsetEqual|sqsupseteq|Proportion|Laplacetrf|ImaginaryI|supsetneqq|NotGreater|gtreqqless|NotElement|ThickSpace|TildeEqual|TildeTilde|Fouriertrf|rmoustache|EqualTilde|eqslantgtr|UnderBrace|LeftVector|UpArrowBar|nLeftarrow|nsubseteqq|subsetneqq|nsupseteqq|nleftarrow|succapprox|lessapprox|UpTeeArrow|upuparrows|curlywedge|lesseqqgtr|varepsilon|varnothing|RightFloor|complement|CirclePlus|sqsubseteq|Lleftarrow|circledast|RightArrow|Rightarrow|rightarrow|lmoustache|Bernoullis|precapprox|mapstoleft|mapstodown|longmapsto|dotsquare|downarrow|DoubleDot|nsubseteq|supsetneq|leftarrow|nsupseteq|subsetneq|ThinSpace|ngeqslant|subseteqq|HumpEqual|NotSubset|triangleq|NotCupCap|lesseqgtr|heartsuit|TripleDot|Leftarrow|Coproduct|Congruent|varpropto|complexes|gvertneqq|LeftArrow|LessTilde|supseteqq|MinusPlus|CircleDot|nleqslant|NotExists|gtreqless|nparallel|UnionPlus|LeftFloor|checkmark|CenterDot|centerdot|Mellintrf|gtrapprox|bigotimes|OverBrace|spadesuit|therefore|pitchfork|rationals|PlusMinus|Backslash|Therefore|DownBreve|backsimeq|backprime|DownArrow|nshortmid|Downarrow|lvertneqq|eqvparsl|imagline|imagpart|infintie|integers|Integral|intercal|LessLess|Uarrocir|intlarhk|sqsupset|angmsdaf|sqsubset|llcorner|vartheta|cupbrcap|lnapprox|Superset|SuchThat|succnsim|succneqq|angmsdag|biguplus|curlyvee|trpezium|Succeeds|NotTilde|bigwedge|angmsdah|angrtvbd|triminus|cwconint|fpartint|lrcorner|smeparsl|subseteq|urcorner|lurdshar|laemptyv|DDotrahd|approxeq|ldrushar|awconint|mapstoup|backcong|shortmid|triangle|geqslant|gesdotol|timesbar|circledR|circledS|setminus|multimap|naturals|scpolint|ncongdot|RightTee|boxminus|gnapprox|boxtimes|andslope|thicksim|angmsdaa|varsigma|cirfnint|rtriltri|angmsdab|rppolint|angmsdac|barwedge|drbkarow|clubsuit|thetasym|bsolhsub|capbrcup|dzigrarr|doteqdot|DotEqual|dotminus|UnderBar|NotEqual|realpart|otimesas|ulcorner|hksearow|hkswarow|parallel|PartialD|elinters|emptyset|plusacir|bbrktbrk|angmsdad|pointint|bigoplus|angmsdae|Precedes|bigsqcup|varkappa|notindot|supseteq|precneqq|precnsim|profalar|profline|profsurf|leqslant|lesdotor|raemptyv|subplus|notnivb|notnivc|subrarr|zigrarr|vzigzag|submult|subedot|Element|between|cirscir|larrbfs|larrsim|lotimes|lbrksld|lbrkslu|lozenge|ldrdhar|dbkarow|bigcirc|epsilon|simrarr|simplus|ltquest|Epsilon|luruhar|gtquest|maltese|npolint|eqcolon|npreceq|bigodot|ddagger|gtrless|bnequiv|harrcir|ddotseq|equivDD|backsim|demptyv|nsqsube|nsqsupe|Upsilon|nsubset|upsilon|minusdu|nsucceq|swarrow|nsupset|coloneq|searrow|boxplus|napprox|natural|asympeq|alefsym|congdot|nearrow|bigstar|diamond|supplus|tritime|LeftTee|nvinfin|triplus|NewLine|nvltrie|nvrtrie|nwarrow|nexists|Diamond|ruluhar|Implies|supmult|angzarr|suplarr|suphsub|questeq|because|digamma|Because|olcross|bemptyv|omicron|Omicron|rotimes|NoBreak|intprod|angrtvb|orderof|uwangle|suphsol|lesdoto|orslope|DownTee|realine|cudarrl|rdldhar|OverBar|supedot|lessdot|supdsub|topfork|succsim|rbrkslu|rbrksld|pertenk|cudarrr|isindot|planckh|lessgtr|pluscir|gesdoto|plussim|plustwo|lesssim|cularrp|rarrsim|Cayleys|notinva|notinvb|notinvc|UpArrow|Uparrow|uparrow|NotLess|dwangle|precsim|Product|curarrm|Cconint|dotplus|rarrbfs|ccupssm|Cedilla|cemptyv|notniva|quatint|frac35|frac38|frac45|frac56|frac58|frac78|tridot|xoplus|gacute|gammad|Gammad|lfisht|lfloor|bigcup|sqsupe|gbreve|Gbreve|lharul|sqsube|sqcups|Gcedil|apacir|llhard|lmidot|Lmidot|lmoust|andand|sqcaps|approx|Abreve|spades|circeq|tprime|divide|topcir|Assign|topbot|gesdot|divonx|xuplus|timesd|gesles|atilde|solbar|SOFTcy|loplus|timesb|lowast|lowbar|dlcorn|dlcrop|softcy|dollar|lparlt|thksim|lrhard|Atilde|lsaquo|smashp|bigvee|thinsp|wreath|bkarow|lsquor|lstrok|Lstrok|lthree|ltimes|ltlarr|DotDot|simdot|ltrPar|weierp|xsqcup|angmsd|sigmav|sigmaf|zeetrf|Zcaron|zcaron|mapsto|vsupne|thetav|cirmid|marker|mcomma|Zacute|vsubnE|there4|gtlPar|vsubne|bottom|gtrarr|SHCHcy|shchcy|midast|midcir|middot|minusb|minusd|gtrdot|bowtie|sfrown|mnplus|models|colone|seswar|Colone|mstpos|searhk|gtrsim|nacute|Nacute|boxbox|telrec|hairsp|Tcedil|nbumpe|scnsim|ncaron|Ncaron|ncedil|Ncedil|hamilt|Scedil|nearhk|hardcy|HARDcy|tcedil|Tcaron|commat|nequiv|nesear|tcaron|target|hearts|nexist|varrho|scedil|Scaron|scaron|hellip|Sacute|sacute|hercon|swnwar|compfn|rtimes|rthree|rsquor|rsaquo|zacute|wedgeq|homtht|barvee|barwed|Barwed|rpargt|horbar|conint|swarhk|roplus|nltrie|hslash|hstrok|Hstrok|rmoust|Conint|bprime|hybull|hyphen|iacute|Iacute|supsup|supsub|supsim|varphi|coprod|brvbar|agrave|Supset|supset|igrave|Igrave|notinE|Agrave|iiiint|iinfin|copysr|wedbar|Verbar|vangrt|becaus|incare|verbar|inodot|bullet|drcorn|intcal|drcrop|cularr|vellip|Utilde|bumpeq|cupcap|dstrok|Dstrok|CupCap|cupcup|cupdot|eacute|Eacute|supdot|iquest|easter|ecaron|Ecaron|ecolon|isinsv|utilde|itilde|Itilde|curarr|succeq|Bumpeq|cacute|ulcrop|nparsl|Cacute|nprcue|egrave|Egrave|nrarrc|nrarrw|subsup|subsub|nrtrie|jsercy|nsccue|Jsercy|kappav|kcedil|Kcedil|subsim|ulcorn|nsimeq|egsdot|veebar|kgreen|capand|elsdot|Subset|subset|curren|aacute|lacute|Lacute|emptyv|ntilde|Ntilde|lagran|lambda|Lambda|capcap|Ugrave|langle|subdot|emsp13|numero|emsp14|nvdash|nvDash|nVdash|nVDash|ugrave|ufisht|nvHarr|larrfs|nvlArr|larrhk|larrlp|larrpl|nvrArr|Udblac|nwarhk|larrtl|nwnear|oacute|Oacute|latail|lAtail|sstarf|lbrace|odblac|Odblac|lbrack|udblac|odsold|eparsl|lcaron|Lcaron|ograve|Ograve|lcedil|Lcedil|Aacute|ssmile|ssetmn|squarf|ldquor|capcup|ominus|cylcty|rharul|eqcirc|dagger|rfloor|rfisht|Dagger|daleth|equals|origof|capdot|equest|dcaron|Dcaron|rdquor|oslash|Oslash|otilde|Otilde|otimes|Otimes|urcrop|Ubreve|ubreve|Yacute|Uacute|uacute|Rcedil|rcedil|urcorn|parsim|Rcaron|Vdashl|rcaron|Tstrok|percnt|period|permil|Exists|yacute|rbrack|rbrace|phmmat|ccaron|Ccaron|planck|ccedil|plankv|tstrok|female|plusdo|plusdu|ffilig|plusmn|ffllig|Ccedil|rAtail|dfisht|bernou|ratail|Rarrtl|rarrtl|angsph|rarrpl|rarrlp|rarrhk|xwedge|xotime|forall|ForAll|Vvdash|vsupnE|preceq|bigcap|frac12|frac13|frac14|primes|rarrfs|prnsim|frac15|Square|frac16|square|lesdot|frac18|frac23|propto|prurel|rarrap|rangle|puncsp|frac25|Racute|qprime|racute|lesges|frac34|abreve|AElig|eqsim|utdot|setmn|urtri|Equal|Uring|seArr|uring|searr|dashv|Dashv|mumap|nabla|iogon|Iogon|sdote|sdotb|scsim|napid|napos|equiv|natur|Acirc|dblac|erarr|nbump|iprod|erDot|ucirc|awint|esdot|angrt|ncong|isinE|scnap|Scirc|scirc|ndash|isins|Ubrcy|nearr|neArr|isinv|nedot|ubrcy|acute|Ycirc|iukcy|Iukcy|xutri|nesim|caret|jcirc|Jcirc|caron|twixt|ddarr|sccue|exist|jmath|sbquo|ngeqq|angst|ccaps|lceil|ngsim|UpTee|delta|Delta|rtrif|nharr|nhArr|nhpar|rtrie|jukcy|Jukcy|kappa|rsquo|Kappa|nlarr|nlArr|TSHcy|rrarr|aogon|Aogon|fflig|xrarr|tshcy|ccirc|nleqq|filig|upsih|nless|dharl|nlsim|fjlig|ropar|nltri|dharr|robrk|roarr|fllig|fltns|roang|rnmid|subnE|subne|lAarr|trisb|Ccirc|acirc|ccups|blank|VDash|forkv|Vdash|langd|cedil|blk12|blk14|laquo|strns|diams|notin|vDash|larrb|blk34|block|disin|uplus|vdash|vBarv|aelig|starf|Wedge|check|xrArr|lates|lbarr|lBarr|notni|lbbrk|bcong|frasl|lbrke|frown|vrtri|vprop|vnsup|gamma|Gamma|wedge|xodot|bdquo|srarr|doteq|ldquo|boxdl|boxdL|gcirc|Gcirc|boxDl|boxDL|boxdr|boxdR|boxDr|TRADE|trade|rlhar|boxDR|vnsub|npart|vltri|rlarr|boxhd|boxhD|nprec|gescc|nrarr|nrArr|boxHd|boxHD|boxhu|boxhU|nrtri|boxHu|clubs|boxHU|times|colon|Colon|gimel|xlArr|Tilde|nsime|tilde|nsmid|nspar|THORN|thorn|xlarr|nsube|nsubE|thkap|xhArr|comma|nsucc|boxul|boxuL|nsupe|nsupE|gneqq|gnsim|boxUl|boxUL|grave|boxur|boxuR|boxUr|boxUR|lescc|angle|bepsi|boxvh|varpi|boxvH|numsp|Theta|gsime|gsiml|theta|boxVh|boxVH|boxvl|gtcir|gtdot|boxvL|boxVl|boxVL|crarr|cross|Cross|nvsim|boxvr|nwarr|nwArr|sqsup|dtdot|Uogon|lhard|lharu|dtrif|ocirc|Ocirc|lhblk|duarr|odash|sqsub|Hacek|sqcup|llarr|duhar|oelig|OElig|ofcir|boxvR|uogon|lltri|boxVr|csube|uuarr|ohbar|csupe|ctdot|olarr|olcir|harrw|oline|sqcap|omacr|Omacr|omega|Omega|boxVR|aleph|lneqq|lnsim|loang|loarr|rharu|lobrk|hcirc|operp|oplus|rhard|Hcirc|orarr|Union|order|ecirc|Ecirc|cuepr|szlig|cuesc|breve|reals|eDDot|Breve|hoarr|lopar|utrif|rdquo|Umacr|umacr|efDot|swArr|ultri|alpha|rceil|ovbar|swarr|Wcirc|wcirc|smtes|smile|bsemi|lrarr|aring|parsl|lrhar|bsime|uhblk|lrtri|cupor|Aring|uharr|uharl|slarr|rbrke|bsolb|lsime|rbbrk|RBarr|lsimg|phone|rBarr|rbarr|icirc|lsquo|Icirc|emacr|Emacr|ratio|simne|plusb|simlE|simgE|simeq|pluse|ltcir|ltdot|empty|xharr|xdtri|iexcl|Alpha|ltrie|rarrw|pound|ltrif|xcirc|bumpe|prcue|bumpE|asymp|amacr|cuvee|Sigma|sigma|iiint|udhar|iiota|ijlig|IJlig|supnE|imacr|Imacr|prime|Prime|image|prnap|eogon|Eogon|rarrc|mdash|mDDot|cuwed|imath|supne|imped|Amacr|udarr|prsim|micro|rarrb|cwint|raquo|infin|eplus|range|rangd|Ucirc|radic|minus|amalg|veeeq|rAarr|epsiv|ycirc|quest|sharp|quot|zwnj|Qscr|race|qscr|Qopf|qopf|qint|rang|Rang|Zscr|zscr|Zopf|zopf|rarr|rArr|Rarr|Pscr|pscr|prop|prod|prnE|prec|ZHcy|zhcy|prap|Zeta|zeta|Popf|popf|Zdot|plus|zdot|Yuml|yuml|phiv|YUcy|yucy|Yscr|yscr|perp|Yopf|yopf|part|para|YIcy|Ouml|rcub|yicy|YAcy|rdca|ouml|osol|Oscr|rdsh|yacy|real|oscr|xvee|andd|rect|andv|Xscr|oror|ordm|ordf|xscr|ange|aopf|Aopf|rHar|Xopf|opar|Oopf|xopf|xnis|rhov|oopf|omid|xmap|oint|apid|apos|ogon|ascr|Ascr|odot|odiv|xcup|xcap|ocir|oast|nvlt|nvle|nvgt|nvge|nvap|Wscr|wscr|auml|ntlg|ntgl|nsup|nsub|nsim|Nscr|nscr|nsce|Wopf|ring|npre|wopf|npar|Auml|Barv|bbrk|Nopf|nopf|nmid|nLtv|beta|ropf|Ropf|Beta|beth|nles|rpar|nleq|bnot|bNot|nldr|NJcy|rscr|Rscr|Vscr|vscr|rsqb|njcy|bopf|nisd|Bopf|rtri|Vopf|nGtv|ngtr|vopf|boxh|boxH|boxv|nges|ngeq|boxV|bscr|scap|Bscr|bsim|Vert|vert|bsol|bull|bump|caps|cdot|ncup|scnE|ncap|nbsp|napE|Cdot|cent|sdot|Vbar|nang|vBar|chcy|Mscr|mscr|sect|semi|CHcy|Mopf|mopf|sext|circ|cire|mldr|mlcp|cirE|comp|shcy|SHcy|vArr|varr|cong|copf|Copf|copy|COPY|malt|male|macr|lvnE|cscr|ltri|sime|ltcc|simg|Cscr|siml|csub|Uuml|lsqb|lsim|uuml|csup|Lscr|lscr|utri|smid|lpar|cups|smte|lozf|darr|Lopf|Uscr|solb|lopf|sopf|Sopf|lneq|uscr|spar|dArr|lnap|Darr|dash|Sqrt|LJcy|ljcy|lHar|dHar|Upsi|upsi|diam|lesg|djcy|DJcy|leqq|dopf|Dopf|dscr|Dscr|dscy|ldsh|ldca|squf|DScy|sscr|Sscr|dsol|lcub|late|star|Star|Uopf|Larr|lArr|larr|uopf|dtri|dzcy|sube|subE|Lang|lang|Kscr|kscr|Kopf|kopf|KJcy|kjcy|KHcy|khcy|DZcy|ecir|edot|eDot|Jscr|jscr|succ|Jopf|jopf|Edot|uHar|emsp|ensp|Iuml|iuml|eopf|isin|Iscr|iscr|Eopf|epar|sung|epsi|escr|sup1|sup2|sup3|Iota|iota|supe|supE|Iopf|iopf|IOcy|iocy|Escr|esim|Esim|imof|Uarr|QUOT|uArr|uarr|euml|IEcy|iecy|Idot|Euml|euro|excl|Hscr|hscr|Hopf|hopf|TScy|tscy|Tscr|hbar|tscr|flat|tbrk|fnof|hArr|harr|half|fopf|Fopf|tdot|gvnE|fork|trie|gtcc|fscr|Fscr|gdot|gsim|Gscr|gscr|Gopf|gopf|gneq|Gdot|tosa|gnap|Topf|topf|geqq|toea|GJcy|gjcy|tint|gesl|mid|Sfr|ggg|top|ges|gla|glE|glj|geq|gne|gEl|gel|gnE|Gcy|gcy|gap|Tfr|tfr|Tcy|tcy|Hat|Tau|Ffr|tau|Tab|hfr|Hfr|ffr|Fcy|fcy|icy|Icy|iff|ETH|eth|ifr|Ifr|Eta|eta|int|Int|Sup|sup|ucy|Ucy|Sum|sum|jcy|ENG|ufr|Ufr|eng|Jcy|jfr|els|ell|egs|Efr|efr|Jfr|uml|kcy|Kcy|Ecy|ecy|kfr|Kfr|lap|Sub|sub|lat|lcy|Lcy|leg|Dot|dot|lEg|leq|les|squ|div|die|lfr|Lfr|lgE|Dfr|dfr|Del|deg|Dcy|dcy|lne|lnE|sol|loz|smt|Cup|lrm|cup|lsh|Lsh|sim|shy|map|Map|mcy|Mcy|mfr|Mfr|mho|gfr|Gfr|sfr|cir|Chi|chi|nap|Cfr|vcy|Vcy|cfr|Scy|scy|ncy|Ncy|vee|Vee|Cap|cap|nfr|scE|sce|Nfr|nge|ngE|nGg|vfr|Vfr|ngt|bot|nGt|nis|niv|Rsh|rsh|nle|nlE|bne|Bfr|bfr|nLl|nlt|nLt|Bcy|bcy|not|Not|rlm|wfr|Wfr|npr|nsc|num|ocy|ast|Ocy|ofr|xfr|Xfr|Ofr|ogt|ohm|apE|olt|Rho|ape|rho|Rfr|rfr|ord|REG|ang|reg|orv|And|and|AMP|Rcy|amp|Afr|ycy|Ycy|yen|yfr|Yfr|rcy|par|pcy|Pcy|pfr|Pfr|phi|Phi|afr|Acy|acy|zcy|Zcy|piv|acE|acd|zfr|Zfr|pre|prE|psi|Psi|qfr|Qfr|zwj|Or|ge|Gg|gt|gg|el|oS|lt|Lt|LT|Re|lg|gl|eg|ne|Im|it|le|DD|wp|wr|nu|Nu|dd|lE|Sc|sc|pi|Pi|ee|af|ll|Ll|rx|gE|xi|pm|Xi|ic|pr|Pr|in|ni|mp|mu|ac|Mu|or|ap|Gt|GT|ii);|&(Aacute|Agrave|Atilde|Ccedil|Eacute|Egrave|Iacute|Igrave|Ntilde|Oacute|Ograve|Oslash|Otilde|Uacute|Ugrave|Yacute|aacute|agrave|atilde|brvbar|ccedil|curren|divide|eacute|egrave|frac12|frac14|frac34|iacute|igrave|iquest|middot|ntilde|oacute|ograve|oslash|otilde|plusmn|uacute|ugrave|yacute|AElig|Acirc|Aring|Ecirc|Icirc|Ocirc|THORN|Ucirc|acirc|acute|aelig|aring|cedil|ecirc|icirc|iexcl|laquo|micro|ocirc|pound|raquo|szlig|thorn|times|ucirc|Auml|COPY|Euml|Iuml|Ouml|QUOT|Uuml|auml|cent|copy|euml|iuml|macr|nbsp|ordf|ordm|ouml|para|quot|sect|sup1|sup2|sup3|uuml|yuml|AMP|ETH|REG|amp|deg|eth|not|reg|shy|uml|yen|GT|LT|gt|lt)(?!;)([=a-zA-Z0-9]?)|&#([0-9]+)(;?)|&#[xX]([a-fA-F0-9]+)(;?)|&([0-9a-zA-Z]+)/g,v={aacute:"ÃĄ",Aacute:"Á",abreve:"ă",Abreve:"Ă",ac:"∞",acd:"âˆŋ",acE:"âˆžĖŗ",acirc:"Ãĸ",Acirc:"Â",acute:"´",acy:"а",Acy:"А",aelig:"ÃĻ",AElig:"Æ",af:"⁥",afr:"𝔞",Afr:"𝔄",agrave:"à",Agrave:"À",alefsym:"â„ĩ",aleph:"â„ĩ",alpha:"Îą",Alpha:"Α",amacr:"ā",Amacr:"Ā",amalg:"â¨ŋ",amp:"&",AMP:"&",and:"∧",And:"⩓",andand:"⩕",andd:"⩜",andslope:"⊘",andv:"⩚",ang:"∠",ange:"âϤ",angle:"∠",angmsd:"∥",angmsdaa:"âύ",angmsdab:"âĻŠ",angmsdac:"âĻĒ",angmsdad:"âĻĢ",angmsdae:"âĻŦ",angmsdaf:"âĻ­",angmsdag:"âĻŽ",angmsdah:"âϝ",angrt:"∟",angrtvb:"⊾",angrtvbd:"âĻ",angsph:"âˆĸ",angst:"Å",angzarr:"âŧ",aogon:"ą",Aogon:"Ą",aopf:"𝕒",Aopf:"𝔸",ap:"≈",apacir:"⊯",ape:"≊",apE:"⊰",apid:"≋",apos:"'",ApplyFunction:"⁥",approx:"≈",approxeq:"≊",aring:"ÃĨ",Aring:"Å",ascr:"đ’ļ",Ascr:"𝒜",Assign:"≔",ast:"*",asymp:"≈",asympeq:"≍",atilde:"ÃŖ",Atilde:"Ã",auml:"ä",Auml:"Ä",awconint:"âˆŗ",awint:"⨑",backcong:"≌",backepsilon:"Īļ",backprime:"â€ĩ",backsim:"âˆŊ",backsimeq:"⋍",Backslash:"∖",Barv:"â̧",barvee:"âŠŊ",barwed:"⌅",Barwed:"⌆",barwedge:"⌅",bbrk:"âŽĩ",bbrktbrk:"âŽļ",bcong:"≌",bcy:"Đą",Bcy:"Б",bdquo:"„",becaus:"âˆĩ",because:"âˆĩ",Because:"âˆĩ",bemptyv:"âϰ",bepsi:"Īļ",bernou:"â„Ŧ",Bernoullis:"â„Ŧ",beta:"β",Beta:"Β",beth:"â„ļ",between:"â‰Ŧ",bfr:"𝔟",Bfr:"𝔅",bigcap:"⋂",bigcirc:"◯",bigcup:"⋃",bigodot:"⨀",bigoplus:"⨁",bigotimes:"⨂",bigsqcup:"⨆",bigstar:"★",bigtriangledown:"â–Ŋ",bigtriangleup:"â–ŗ",biguplus:"⨄",bigvee:"⋁",bigwedge:"⋀",bkarow:"⤍",blacklozenge:"â§Ģ",blacksquare:"â–Ē",blacktriangle:"▴",blacktriangledown:"▾",blacktriangleleft:"◂",blacktriangleright:"▸",blank:"âŖ",blk12:"▒",blk14:"░",blk34:"▓",block:"█",bne:"=âƒĨ",bnequiv:"≡âƒĨ",bnot:"⌐",bNot:"âĢ­",bopf:"𝕓",Bopf:"𝔹",bot:"âŠĨ",bottom:"âŠĨ",bowtie:"⋈",boxbox:"⧉",boxdl:"┐",boxdL:"╕",boxDl:"╖",boxDL:"╗",boxdr:"┌",boxdR:"╒",boxDr:"╓",boxDR:"╔",boxh:"─",boxH:"═",boxhd:"â”Ŧ",boxhD:"â•Ĩ",boxHd:"╤",boxHD:"â•Ļ",boxhu:"┴",boxhU:"╨",boxHu:"╧",boxHU:"╩",boxminus:"⊟",boxplus:"⊞",boxtimes:"⊠",boxul:"┘",boxuL:"╛",boxUl:"╜",boxUL:"╝",boxur:"└",boxuR:"╘",boxUr:"╙",boxUR:"╚",boxv:"│",boxV:"║",boxvh:"â”ŧ",boxvH:"â•Ē",boxVh:"â•Ģ",boxVH:"â•Ŧ",boxvl:"┤",boxvL:"╡",boxVl:"â•ĸ",boxVL:"â•Ŗ",boxvr:"├",boxvR:"╞",boxVr:"╟",boxVR:"╠",bprime:"â€ĩ",breve:"˘",Breve:"˘",brvbar:"ÂĻ",bscr:"𝒷",Bscr:"â„Ŧ",bsemi:"⁏",bsim:"âˆŊ",bsime:"⋍",bsol:"\\",bsolb:"⧅",bsolhsub:"⟈",bull:"â€ĸ",bullet:"â€ĸ",bump:"≎",bumpe:"≏",bumpE:"âĒŽ",bumpeq:"≏",Bumpeq:"≎",cacute:"ć",Cacute:"Ć",cap:"∊",Cap:"⋒",capand:"⩄",capbrcup:"⩉",capcap:"⩋",capcup:"⩇",capdot:"⩀",CapitalDifferentialD:"ⅅ",caps:"âˆŠī¸€",caret:"⁁",caron:"ˇ",Cayleys:"ℭ",ccaps:"⊍",ccaron:"č",Ccaron:"Č",ccedil:"ç",Ccedil:"Ç",ccirc:"ĉ",Ccirc:"Ĉ",Cconint:"∰",ccups:"⩌",ccupssm:"⊐",cdot:"ċ",Cdot:"Ċ",cedil:"¸",Cedilla:"¸",cemptyv:"âϞ",cent:"Âĸ",centerdot:"¡",CenterDot:"¡",cfr:"𝔠",Cfr:"ℭ",chcy:"҇",CHcy:"Ч",check:"✓",checkmark:"✓",chi:"·",Chi:"Χ",cir:"○",circ:"ˆ",circeq:"≗",circlearrowleft:"â†ē",circlearrowright:"â†ģ",circledast:"⊛",circledcirc:"⊚",circleddash:"⊝",CircleDot:"⊙",circledR:"ÂŽ",circledS:"Ⓢ",CircleMinus:"⊖",CirclePlus:"⊕",CircleTimes:"⊗",cire:"≗",cirE:"⧃",cirfnint:"⨐",cirmid:"â̝",cirscir:"⧂",ClockwiseContourIntegral:"∲",CloseCurlyDoubleQuote:"”",CloseCurlyQuote:"’",clubs:"â™Ŗ",clubsuit:"â™Ŗ",colon:":",Colon:"∡",colone:"≔",Colone:"⊴",coloneq:"≔",comma:",",commat:"@",comp:"∁",compfn:"∘",complement:"∁",complexes:"ℂ",cong:"≅",congdot:"⊭",Congruent:"≡",conint:"∎",Conint:"∯",ContourIntegral:"∎",copf:"𝕔",Copf:"ℂ",coprod:"∐",Coproduct:"∐",copy:"Š",COPY:"Š",copysr:"℗",CounterClockwiseContourIntegral:"âˆŗ",crarr:"â†ĩ",cross:"✗",Cross:"⨯",cscr:"𝒸",Cscr:"𝒞",csub:"âĢ",csube:"âĢ‘",csup:"â̐",csupe:"âĢ’",ctdot:"⋯",cudarrl:"⤸",cudarrr:"â¤ĩ",cuepr:"⋞",cuesc:"⋟",cularr:"â†ļ",cularrp:"â¤Ŋ",cup:"âˆĒ",Cup:"⋓",cupbrcap:"⊈",cupcap:"⩆",CupCap:"≍",cupcup:"⩊",cupdot:"⊍",cupor:"⩅",cups:"âˆĒ",curarr:"↷",curarrm:"â¤ŧ",curlyeqprec:"⋞",curlyeqsucc:"⋟",curlyvee:"⋎",curlywedge:"⋏",curren:"¤",curvearrowleft:"â†ļ",curvearrowright:"↷",cuvee:"⋎",cuwed:"⋏",cwconint:"∲",cwint:"∹",cylcty:"⌭",dagger:"†",Dagger:"‡",daleth:"ℸ",darr:"↓",dArr:"⇓",Darr:"↡",dash:"‐",dashv:"âŠŖ",Dashv:"â̤",dbkarow:"⤏",dblac:"˝",dcaron:"ď",Dcaron:"Ď",dcy:"Đ´",Dcy:"Д",dd:"ⅆ",DD:"ⅅ",ddagger:"‡",ddarr:"⇊",DDotrahd:"⤑",ddotseq:"⊡",deg:"°",Del:"∇",delta:"δ",Delta:"Δ",demptyv:"âĻą",dfisht:"âĨŋ",dfr:"𝔡",Dfr:"𝔇",dHar:"âĨĨ",dharl:"⇃",dharr:"⇂",DiacriticalAcute:"´",DiacriticalDot:"˙",DiacriticalDoubleAcute:"˝",DiacriticalGrave:"`",DiacriticalTilde:"˜",diam:"⋄",diamond:"⋄",Diamond:"⋄",diamondsuit:"â™Ļ",diams:"â™Ļ",die:"¨",DifferentialD:"ⅆ",digamma:"Ī",disin:"⋲",div:"Ãˇ",divide:"Ãˇ",divideontimes:"⋇",divonx:"⋇",djcy:"Ņ’",DJcy:"Ђ",dlcorn:"⌞",dlcrop:"⌍",dollar:"$",dopf:"𝕕",Dopf:"đ”ģ",dot:"˙",Dot:"¨",DotDot:"⃜",doteq:"≐",doteqdot:"≑",DotEqual:"≐",dotminus:"∸",dotplus:"∔",dotsquare:"⊡",doublebarwedge:"⌆",DoubleContourIntegral:"∯",DoubleDot:"¨",DoubleDownArrow:"⇓",DoubleLeftArrow:"⇐",DoubleLeftRightArrow:"⇔",DoubleLeftTee:"â̤",DoubleLongLeftArrow:"⟸",DoubleLongLeftRightArrow:"âŸē",DoubleLongRightArrow:"⟹",DoubleRightArrow:"⇒",DoubleRightTee:"⊨",DoubleUpArrow:"⇑",DoubleUpDownArrow:"⇕",DoubleVerticalBar:"âˆĨ",downarrow:"↓",Downarrow:"⇓",DownArrow:"↓",DownArrowBar:"⤓",DownArrowUpArrow:"â‡ĩ",DownBreve:"Ė‘",downdownarrows:"⇊",downharpoonleft:"⇃",downharpoonright:"⇂",DownLeftRightVector:"âĨ",DownLeftTeeVector:"âĨž",DownLeftVector:"â†Ŋ",DownLeftVectorBar:"âĨ–",DownRightTeeVector:"âĨŸ",DownRightVector:"⇁",DownRightVectorBar:"âĨ—",DownTee:"⊤",DownTeeArrow:"↧",drbkarow:"⤐",drcorn:"⌟",drcrop:"⌌",dscr:"𝒹",Dscr:"𝒟",dscy:"Ņ•",DScy:"Ѕ",dsol:"â§ļ",dstrok:"đ",Dstrok:"Đ",dtdot:"⋱",dtri:"â–ŋ",dtrif:"▾",duarr:"â‡ĩ",duhar:"âĨ¯",dwangle:"âĻĻ",dzcy:"ҟ",DZcy:"Џ",dzigrarr:"âŸŋ",eacute:"Ê",Eacute:"É",easter:"⊎",ecaron:"ě",Ecaron:"Ě",ecir:"≖",ecirc:"ÃĒ",Ecirc:"Ê",ecolon:"≕",ecy:"Ņ",Ecy:"Đ­",eDDot:"⊡",edot:"ė",eDot:"≑",Edot:"Ė",ee:"ⅇ",efDot:"≒",efr:"đ”ĸ",Efr:"𝔈",eg:"âǚ",egrave:"è",Egrave:"È",egs:"âĒ–",egsdot:"âǘ",el:"âĒ™",Element:"∈",elinters:"⏧",ell:"ℓ",els:"âĒ•",elsdot:"âĒ—",emacr:"ē",Emacr:"Ē",empty:"∅",emptyset:"∅",EmptySmallSquare:"â—ģ",emptyv:"∅",EmptyVerySmallSquare:"â–Ģ",emsp:" ",emsp13:" ",emsp14:" ",eng:"ŋ",ENG:"Ŋ",ensp:" ",eogon:"ę",Eogon:"Ę",eopf:"𝕖",Eopf:"đ”ŧ",epar:"⋕",eparsl:"â§Ŗ",eplus:"⊹",epsi:"Îĩ",epsilon:"Îĩ",Epsilon:"Ε",epsiv:"Īĩ",eqcirc:"≖",eqcolon:"≕",eqsim:"≂",eqslantgtr:"âĒ–",eqslantless:"âĒ•",Equal:"âŠĩ",equals:"=",EqualTilde:"≂",equest:"≟",Equilibrium:"⇌",equiv:"≡",equivDD:"⊸",eqvparsl:"â§Ĩ",erarr:"âĨą",erDot:"≓",escr:"ℯ",Escr:"ℰ",esdot:"≐",esim:"≂",Esim:"âŠŗ",eta:"Ρ",Eta:"Η",eth:"ð",ETH:"Ð",euml:"ÃĢ",Euml:"Ë",euro:"â‚Ŧ",excl:"!",exist:"∃",Exists:"∃",expectation:"ℰ",exponentiale:"ⅇ",ExponentialE:"ⅇ",fallingdotseq:"≒",fcy:"Ņ„",Fcy:"Ф",female:"♀",ffilig:"īŦƒ",fflig:"īŦ€",ffllig:"īŦ„",ffr:"đ”Ŗ",Ffr:"𝔉",filig:"īŦ",FilledSmallSquare:"â—ŧ",FilledVerySmallSquare:"â–Ē",fjlig:"fj",flat:"♭",fllig:"īŦ‚",fltns:"▱",fnof:"ƒ",fopf:"𝕗",Fopf:"đ”Ŋ",forall:"∀",ForAll:"∀",fork:"⋔",forkv:"âĢ™",Fouriertrf:"ℱ",fpartint:"⨍",frac12:"ÂŊ",frac13:"⅓",frac14:"Âŧ",frac15:"⅕",frac16:"⅙",frac18:"⅛",frac23:"⅔",frac25:"⅖",frac34:"ž",frac35:"⅗",frac38:"⅜",frac45:"⅘",frac56:"⅚",frac58:"⅝",frac78:"⅞",frasl:"⁄",frown:"âŒĸ",fscr:"đ’ģ",Fscr:"ℱ",gacute:"Įĩ",gamma:"Îŗ",Gamma:"Γ",gammad:"Ī",Gammad:"Μ",gap:"âdž",gbreve:"ğ",Gbreve:"Ğ",Gcedil:"Äĸ",gcirc:"ĝ",Gcirc:"Ĝ",gcy:"Đŗ",Gcy:"Г",gdot:"ÄĄ",Gdot:"Ä ",ge:"â‰Ĩ",gE:"≧",gel:"⋛",gEl:"ânj",geq:"â‰Ĩ",geqq:"≧",geqslant:"⊞",ges:"⊞",gescc:"âĒŠ",gesdot:"âĒ€",gesdoto:"âĒ‚",gesdotol:"âĒ„",gesl:"â‹›ī¸€",gesles:"âĒ”",gfr:"𝔤",Gfr:"𝔊",gg:"â‰Ģ",Gg:"⋙",ggg:"⋙",gimel:"ℷ",gjcy:"Ņ“",GJcy:"Ѓ",gl:"≷",gla:"âĒĨ",glE:"âĒ’",glj:"âǤ",gnap:"âNJ",gnapprox:"âNJ",gne:"âLj",gnE:"≩",gneq:"âLj",gneqq:"≩",gnsim:"⋧",gopf:"𝕘",Gopf:"𝔾",grave:"`",GreaterEqual:"â‰Ĩ",GreaterEqualLess:"⋛",GreaterFullEqual:"≧",GreaterGreater:"âĒĸ",GreaterLess:"≷",GreaterSlantEqual:"⊞",GreaterTilde:"â‰ŗ",gscr:"ℊ",Gscr:"đ’ĸ",gsim:"â‰ŗ",gsime:"âĒŽ",gsiml:"âǐ",gt:">",Gt:"â‰Ģ",GT:">",gtcc:"âǧ",gtcir:"âŠē",gtdot:"⋗",gtlPar:"âĻ•",gtquest:"âŠŧ",gtrapprox:"âdž",gtrarr:"âĨ¸",gtrdot:"⋗",gtreqless:"⋛",gtreqqless:"ânj",gtrless:"≷",gtrsim:"â‰ŗ",gvertneqq:"â‰Šī¸€",gvnE:"â‰Šī¸€",Hacek:"ˇ",hairsp:" ",half:"ÂŊ",hamilt:"ℋ",hardcy:"Ҋ",HARDcy:"ĐĒ",harr:"↔",hArr:"⇔",harrcir:"âĨˆ",harrw:"↭",Hat:"^",hbar:"ℏ",hcirc:"ÄĨ",Hcirc:"Ĥ",hearts:"â™Ĩ",heartsuit:"â™Ĩ",hellip:"â€Ļ",hercon:"⊹",hfr:"đ”Ĩ",Hfr:"ℌ",HilbertSpace:"ℋ",hksearow:"â¤Ĩ",hkswarow:"â¤Ļ",hoarr:"â‡ŋ",homtht:"âˆģ",hookleftarrow:"↩",hookrightarrow:"â†Ē",hopf:"𝕙",Hopf:"ℍ",horbar:"―",HorizontalLine:"─",hscr:"đ’Ŋ",Hscr:"ℋ",hslash:"ℏ",hstrok:"ħ",Hstrok:"ÄĻ",HumpDownHump:"≎",HumpEqual:"≏",hybull:"⁃",hyphen:"‐",iacute:"í",Iacute:"Í",ic:"âŖ",icirc:"ÃŽ",Icirc:"Î",icy:"и",Icy:"И",Idot:"İ",iecy:"Đĩ",IEcy:"Е",iexcl:"ÂĄ",iff:"⇔",ifr:"đ”Ļ",Ifr:"ℑ",igrave:"ÃŦ",Igrave:"Ì",ii:"ⅈ",iiiint:"⨌",iiint:"∭",iinfin:"⧜",iiota:"℩",ijlig:"Äŗ",IJlig:"IJ",Im:"ℑ",imacr:"ÄĢ",Imacr:"ÄĒ",image:"ℑ",ImaginaryI:"ⅈ",imagline:"ℐ",imagpart:"ℑ",imath:"Äą",imof:"⊷",imped:"Æĩ",Implies:"⇒",in:"∈",incare:"℅",infin:"∞",infintie:"⧝",inodot:"Äą",int:"âˆĢ",Int:"âˆŦ",intcal:"âŠē",integers:"ℤ",Integral:"âˆĢ",intercal:"âŠē",Intersection:"⋂",intlarhk:"⨗",intprod:"â¨ŧ",InvisibleComma:"âŖ",InvisibleTimes:"âĸ",iocy:"Ņ‘",IOcy:"Ё",iogon:"į",Iogon:"ÄŽ",iopf:"𝕚",Iopf:"𝕀",iota:"Κ",Iota:"Ι",iprod:"â¨ŧ",iquest:"Âŋ",iscr:"𝒾",Iscr:"ℐ",isin:"∈",isindot:"â‹ĩ",isinE:"⋹",isins:"⋴",isinsv:"â‹ŗ",isinv:"∈",it:"âĸ",itilde:"ÄŠ",Itilde:"Ĩ",iukcy:"Ņ–",Iukcy:"І",iuml:"ï",Iuml:"Ï",jcirc:"Äĩ",Jcirc:"Ä´",jcy:"Đš",Jcy:"Й",jfr:"𝔧",Jfr:"𝔍",jmath:"ȡ",jopf:"𝕛",Jopf:"𝕁",jscr:"đ’ŋ",Jscr:"đ’Ĩ",jsercy:"Ҙ",Jsercy:"Ј",jukcy:"Ņ”",Jukcy:"Є",kappa:"Îē",Kappa:"Κ",kappav:"ΰ",kcedil:"ġ",Kcedil:"Äļ",kcy:"Đē",Kcy:"К",kfr:"𝔨",Kfr:"𝔎",kgreen:"ĸ",khcy:"Ņ…",KHcy:"ĐĨ",kjcy:"Ҝ",KJcy:"Ќ",kopf:"𝕜",Kopf:"𝕂",kscr:"𝓀",Kscr:"đ’Ļ",lAarr:"⇚",lacute:"Äē",Lacute:"Äš",laemptyv:"âĻ´",lagran:"ℒ",lambda:"Îģ",Lambda:"Λ",lang:"⟨",Lang:"âŸĒ",langd:"âĻ‘",langle:"⟨",lap:"âĒ…",Laplacetrf:"ℒ",laquo:"ÂĢ",larr:"←",lArr:"⇐",Larr:"↞",larrb:"⇤",larrbfs:"⤟",larrfs:"⤝",larrhk:"↩",larrlp:"â†Ģ",larrpl:"⤚",larrsim:"âĨŗ",larrtl:"â†ĸ",lat:"âĒĢ",latail:"⤙",lAtail:"⤛",late:"âĒ­",lates:"âǭ",lbarr:"⤌",lBarr:"⤎",lbbrk:"❲",lbrace:"{",lbrack:"[",lbrke:"âĻ‹",lbrksld:"âĻ",lbrkslu:"âĻ",lcaron:"Äž",Lcaron:"ÄŊ",lcedil:"Äŧ",Lcedil:"Äģ",lceil:"⌈",lcub:"{",lcy:"Đģ",Lcy:"Л",ldca:"â¤ļ",ldquo:"“",ldquor:"„",ldrdhar:"âĨ§",ldrushar:"âĨ‹",ldsh:"↲",le:"≤",lE:"â‰Ļ",LeftAngleBracket:"⟨",leftarrow:"←",Leftarrow:"⇐",LeftArrow:"←",LeftArrowBar:"⇤",LeftArrowRightArrow:"⇆",leftarrowtail:"â†ĸ",LeftCeiling:"⌈",LeftDoubleBracket:"âŸĻ",LeftDownTeeVector:"âĨĄ",LeftDownVector:"⇃",LeftDownVectorBar:"âĨ™",LeftFloor:"⌊",leftharpoondown:"â†Ŋ",leftharpoonup:"â†ŧ",leftleftarrows:"⇇",leftrightarrow:"↔",Leftrightarrow:"⇔",LeftRightArrow:"↔",leftrightarrows:"⇆",leftrightharpoons:"⇋",leftrightsquigarrow:"↭",LeftRightVector:"âĨŽ",LeftTee:"âŠŖ",LeftTeeArrow:"↤",LeftTeeVector:"âĨš",leftthreetimes:"⋋",LeftTriangle:"⊲",LeftTriangleBar:"⧏",LeftTriangleEqual:"⊴",LeftUpDownVector:"âĨ‘",LeftUpTeeVector:"âĨ ",LeftUpVector:"â†ŋ",LeftUpVectorBar:"âĨ˜",LeftVector:"â†ŧ",LeftVectorBar:"âĨ’",leg:"⋚",lEg:"âĒ‹",leq:"≤",leqq:"â‰Ļ",leqslant:"âŠŊ",les:"âŠŊ",lescc:"âǍ",lesdot:"âŠŋ",lesdoto:"âǁ",lesdotor:"âǃ",lesg:"â‹šī¸€",lesges:"âĒ“",lessapprox:"âĒ…",lessdot:"⋖",lesseqgtr:"⋚",lesseqqgtr:"âĒ‹",LessEqualGreater:"⋚",LessFullEqual:"â‰Ļ",LessGreater:"â‰ļ",lessgtr:"â‰ļ",LessLess:"âĒĄ",lesssim:"≲",LessSlantEqual:"âŠŊ",LessTilde:"≲",lfisht:"âĨŧ",lfloor:"⌊",lfr:"𝔩",Lfr:"𝔏",lg:"â‰ļ",lgE:"âĒ‘",lHar:"âĨĸ",lhard:"â†Ŋ",lharu:"â†ŧ",lharul:"âĨĒ",lhblk:"▄",ljcy:"Ņ™",LJcy:"Љ",ll:"â‰Ē",Ll:"⋘",llarr:"⇇",llcorner:"⌞",Lleftarrow:"⇚",llhard:"âĨĢ",lltri:"â—ē",lmidot:"ŀ",Lmidot:"Äŋ",lmoust:"⎰",lmoustache:"⎰",lnap:"âlj",lnapprox:"âlj",lne:"âLJ",lnE:"≨",lneq:"âLJ",lneqq:"≨",lnsim:"â‹Ļ",loang:"âŸŦ",loarr:"â‡Ŋ",lobrk:"âŸĻ",longleftarrow:"âŸĩ",Longleftarrow:"⟸",LongLeftArrow:"âŸĩ",longleftrightarrow:"⟷",Longleftrightarrow:"âŸē",LongLeftRightArrow:"⟷",longmapsto:"âŸŧ",longrightarrow:"âŸļ",Longrightarrow:"⟹",LongRightArrow:"âŸļ",looparrowleft:"â†Ģ",looparrowright:"â†Ŧ",lopar:"âĻ…",lopf:"𝕝",Lopf:"𝕃",loplus:"⨭",lotimes:"⨴",lowast:"∗",lowbar:"_",LowerLeftArrow:"↙",LowerRightArrow:"↘",loz:"◊",lozenge:"◊",lozf:"â§Ģ",lpar:"(",lparlt:"âĻ“",lrarr:"⇆",lrcorner:"⌟",lrhar:"⇋",lrhard:"âĨ­",lrm:"‎",lrtri:"âŠŋ",lsaquo:"‹",lscr:"𝓁",Lscr:"ℒ",lsh:"↰",Lsh:"↰",lsim:"≲",lsime:"âĒ",lsimg:"âĒ",lsqb:"[",lsquo:"‘",lsquor:"‚",lstrok:"ł",Lstrok:"Ł",lt:"<",Lt:"â‰Ē",LT:"<",ltcc:"âĒĻ",ltcir:"⊚",ltdot:"⋖",lthree:"⋋",ltimes:"⋉",ltlarr:"âĨļ",ltquest:"âŠģ",ltri:"◃",ltrie:"⊴",ltrif:"◂",ltrPar:"âĻ–",lurdshar:"âĨŠ",luruhar:"âĨĻ",lvertneqq:"â‰¨ī¸€",lvnE:"â‰¨ī¸€",macr:"¯",male:"♂",malt:"✠",maltese:"✠",map:"â†Ļ",Map:"⤅",mapsto:"â†Ļ",mapstodown:"↧",mapstoleft:"↤",mapstoup:"â†Ĩ",marker:"▮",mcomma:"⨊",mcy:"Đŧ",Mcy:"М",mdash:"—",mDDot:"âˆē",measuredangle:"∥",MediumSpace:" ",Mellintrf:"â„ŗ",mfr:"đ”Ē",Mfr:"𝔐",mho:"℧",micro:"Âĩ",mid:"âˆŖ",midast:"*",midcir:"â̰",middot:"¡",minus:"−",minusb:"⊟",minusd:"∸",minusdu:"â¨Ē",MinusPlus:"∓",mlcp:"âĢ›",mldr:"â€Ļ",mnplus:"∓",models:"⊧",mopf:"𝕞",Mopf:"𝕄",mp:"∓",mscr:"𝓂",Mscr:"â„ŗ",mstpos:"∞",mu:"Îŧ",Mu:"Μ",multimap:"⊸",mumap:"⊸",nabla:"∇",nacute:"ń",Nacute:"Ń",nang:"∠⃒",nap:"≉",napE:"âŠ°Ė¸",napid:"â‰‹Ė¸",napos:"ʼn",napprox:"≉",natur:"♮",natural:"♮",naturals:"ℕ",nbsp:" ",nbump:"â‰ŽĖ¸",nbumpe:"â‰Ė¸",ncap:"⊃",ncaron:"ň",Ncaron:"Ň",ncedil:"ņ",Ncedil:"Ņ",ncong:"≇",ncongdot:"âŠ­Ė¸",ncup:"⩂",ncy:"ĐŊ",Ncy:"Н",ndash:"–",ne:"≠",nearhk:"⤤",nearr:"↗",neArr:"⇗",nearrow:"↗",nedot:"â‰Ė¸",NegativeMediumSpace:"​",NegativeThickSpace:"​",NegativeThinSpace:"​",NegativeVeryThinSpace:"​",nequiv:"â‰ĸ",nesear:"⤨",nesim:"â‰‚Ė¸",NestedGreaterGreater:"â‰Ģ",NestedLessLess:"â‰Ē",NewLine:"\n",nexist:"∄",nexists:"∄",nfr:"đ”Ģ",Nfr:"𝔑",nge:"≱",ngE:"â‰§Ė¸",ngeq:"≱",ngeqq:"â‰§Ė¸",ngeqslant:"âŠžĖ¸",nges:"âŠžĖ¸",nGg:"â‹™Ė¸",ngsim:"â‰ĩ",ngt:"≯",nGt:"â‰Ģ⃒",ngtr:"≯",nGtv:"â‰Ģˏ",nharr:"↮",nhArr:"⇎",nhpar:"â̞",ni:"∋",nis:"â‹ŧ",nisd:"â‹ē",niv:"∋",njcy:"Қ",NJcy:"Њ",nlarr:"↚",nlArr:"⇍",nldr:"â€Ĩ",nle:"≰",nlE:"â‰Ļˏ",nleftarrow:"↚",nLeftarrow:"⇍",nleftrightarrow:"↮",nLeftrightarrow:"⇎",nleq:"≰",nleqq:"â‰Ļˏ",nleqslant:"âŠŊˏ",nles:"âŠŊˏ",nless:"≮",nLl:"â‹˜Ė¸",nlsim:"≴",nlt:"≮",nLt:"â‰Ē⃒",nltri:"â‹Ē",nltrie:"â‹Ŧ",nLtv:"â‰Ēˏ",nmid:"∤",NoBreak:"⁠",NonBreakingSpace:" ",nopf:"𝕟",Nopf:"ℕ",not:"ÂŦ",Not:"âĢŦ",NotCongruent:"â‰ĸ",NotCupCap:"≭",NotDoubleVerticalBar:"âˆĻ",NotElement:"∉",NotEqual:"≠",NotEqualTilde:"â‰‚Ė¸",NotExists:"∄",NotGreater:"≯",NotGreaterEqual:"≱",NotGreaterFullEqual:"â‰§Ė¸",NotGreaterGreater:"â‰Ģˏ",NotGreaterLess:"≹",NotGreaterSlantEqual:"âŠžĖ¸",NotGreaterTilde:"â‰ĩ",NotHumpDownHump:"â‰ŽĖ¸",NotHumpEqual:"â‰Ė¸",notin:"∉",notindot:"â‹ĩˏ",notinE:"â‹šĖ¸",notinva:"∉",notinvb:"⋷",notinvc:"â‹ļ",NotLeftTriangle:"â‹Ē",NotLeftTriangleBar:"â§Ė¸",NotLeftTriangleEqual:"â‹Ŧ",NotLess:"≮",NotLessEqual:"≰",NotLessGreater:"≸",NotLessLess:"â‰Ēˏ",NotLessSlantEqual:"âŠŊˏ",NotLessTilde:"≴",NotNestedGreaterGreater:"âĒĸˏ",NotNestedLessLess:"âĒĄĖ¸",notni:"∌",notniva:"∌",notnivb:"⋾",notnivc:"â‹Ŋ",NotPrecedes:"⊀",NotPrecedesEqual:"âǝˏ",NotPrecedesSlantEqual:"⋠",NotReverseElement:"∌",NotRightTriangle:"â‹Ģ",NotRightTriangleBar:"â§Ė¸",NotRightTriangleEqual:"⋭",NotSquareSubset:"âŠĖ¸",NotSquareSubsetEqual:"â‹ĸ",NotSquareSuperset:"âŠĖ¸",NotSquareSupersetEqual:"â‹Ŗ",NotSubset:"⊂⃒",NotSubsetEqual:"⊈",NotSucceeds:"⊁",NotSucceedsEqual:"âǰˏ",NotSucceedsSlantEqual:"⋡",NotSucceedsTilde:"â‰ŋˏ",NotSuperset:"⊃⃒",NotSupersetEqual:"⊉",NotTilde:"≁",NotTildeEqual:"≄",NotTildeFullEqual:"≇",NotTildeTilde:"≉",NotVerticalBar:"∤",npar:"âˆĻ",nparallel:"âˆĻ",nparsl:"âĢŊâƒĨ",npart:"âˆ‚Ė¸",npolint:"⨔",npr:"⊀",nprcue:"⋠",npre:"âǝˏ",nprec:"⊀",npreceq:"âǝˏ",nrarr:"↛",nrArr:"⇏",nrarrc:"â¤ŗĖ¸",nrarrw:"â†Ė¸",nrightarrow:"↛",nRightarrow:"⇏",nrtri:"â‹Ģ",nrtrie:"⋭",nsc:"⊁",nsccue:"⋡",nsce:"âǰˏ",nscr:"𝓃",Nscr:"𝒩",nshortmid:"∤",nshortparallel:"âˆĻ",nsim:"≁",nsime:"≄",nsimeq:"≄",nsmid:"∤",nspar:"âˆĻ",nsqsube:"â‹ĸ",nsqsupe:"â‹Ŗ",nsub:"⊄",nsube:"⊈",nsubE:"â̅ˏ",nsubset:"⊂⃒",nsubseteq:"⊈",nsubseteqq:"â̅ˏ",nsucc:"⊁",nsucceq:"âǰˏ",nsup:"⊅",nsupe:"⊉",nsupE:"â̆ˏ",nsupset:"⊃⃒",nsupseteq:"⊉",nsupseteqq:"â̆ˏ",ntgl:"≹",ntilde:"Ãą",Ntilde:"Ñ",ntlg:"≸",ntriangleleft:"â‹Ē",ntrianglelefteq:"â‹Ŧ",ntriangleright:"â‹Ģ",ntrianglerighteq:"⋭",nu:"ÎŊ",Nu:"Ν",num:"#",numero:"№",numsp:" ",nvap:"≍⃒",nvdash:"âŠŦ",nvDash:"⊭",nVdash:"⊮",nVDash:"⊯",nvge:"â‰Ĩ⃒",nvgt:">⃒",nvHarr:"⤄",nvinfin:"⧞",nvlArr:"⤂",nvle:"≤⃒",nvlt:"<⃒",nvltrie:"⊴⃒",nvrArr:"⤃",nvrtrie:"âŠĩ⃒",nvsim:"âˆŧ⃒",nwarhk:"â¤Ŗ",nwarr:"↖",nwArr:"⇖",nwarrow:"↖",nwnear:"⤧",oacute:"Ãŗ",Oacute:"Ó",oast:"⊛",ocir:"⊚",ocirc:"ô",Ocirc:"Ô",ocy:"Đž",Ocy:"О",odash:"⊝",odblac:"ő",Odblac:"Ő",odiv:"⨸",odot:"⊙",odsold:"âĻŧ",oelig:"œ",OElig:"Œ",ofcir:"âĻŋ",ofr:"đ”Ŧ",Ofr:"𝔒",ogon:"˛",ograve:"Ã˛",Ograve:"Ò",ogt:"⧁",ohbar:"âĻĩ",ohm:"Ί",oint:"∎",olarr:"â†ē",olcir:"âĻž",olcross:"âĻģ",oline:"‾",olt:"⧀",omacr:"ō",Omacr:"Ō",omega:"Ή",Omega:"Ί",omicron:"Îŋ",Omicron:"Ο",omid:"âĻļ",ominus:"⊖",oopf:"𝕠",Oopf:"𝕆",opar:"âώ",OpenCurlyDoubleQuote:"“",OpenCurlyQuote:"‘",operp:"âĻš",oplus:"⊕",or:"∨",Or:"⩔",orarr:"â†ģ",ord:"⊝",order:"ℴ",orderof:"ℴ",ordf:"ÂĒ",ordm:"Âē",origof:"âŠļ",oror:"⩖",orslope:"⩗",orv:"⩛",oS:"Ⓢ",oscr:"ℴ",Oscr:"đ’Ē",oslash:"ø",Oslash:"Ø",osol:"⊘",otilde:"Ãĩ",Otilde:"Õ",otimes:"⊗",Otimes:"⨡",otimesas:"â¨ļ",ouml:"Ãļ",Ouml:"Ö",ovbar:"âŒŊ",OverBar:"‾",OverBrace:"⏞",OverBracket:"⎴",OverParenthesis:"⏜",par:"âˆĨ",para:"Âļ",parallel:"âˆĨ",parsim:"âĢŗ",parsl:"âĢŊ",part:"∂",PartialD:"∂",pcy:"Đŋ",Pcy:"П",percnt:"%",period:".",permil:"‰",perp:"âŠĨ",pertenk:"‱",pfr:"𝔭",Pfr:"𝔓",phi:"Ά",Phi:"ÎĻ",phiv:"Ī•",phmmat:"â„ŗ",phone:"☎",pi:"Ī€",Pi:"Π",pitchfork:"⋔",piv:"Ī–",planck:"ℏ",planckh:"ℎ",plankv:"ℏ",plus:"+",plusacir:"â¨Ŗ",plusb:"⊞",pluscir:"â¨ĸ",plusdo:"∔",plusdu:"â¨Ĩ",pluse:"⊲",PlusMinus:"Âą",plusmn:"Âą",plussim:"â¨Ļ",plustwo:"⨧",pm:"Âą",Poincareplane:"ℌ",pointint:"⨕",popf:"𝕡",Popf:"ℙ",pound:"ÂŖ",pr:"â‰ē",Pr:"âĒģ",prap:"âǎ",prcue:"â‰ŧ",pre:"âǝ",prE:"âĒŗ",prec:"â‰ē",precapprox:"âǎ",preccurlyeq:"â‰ŧ",Precedes:"â‰ē",PrecedesEqual:"âǝ",PrecedesSlantEqual:"â‰ŧ",PrecedesTilde:"≾",preceq:"âǝ",precnapprox:"âĒš",precneqq:"âĒĩ",precnsim:"⋨",precsim:"≾",prime:"′",Prime:"â€ŗ",primes:"ℙ",prnap:"âĒš",prnE:"âĒĩ",prnsim:"⋨",prod:"∏",Product:"∏",profalar:"⌮",profline:"⌒",profsurf:"⌓",prop:"∝",Proportion:"∡",Proportional:"∝",propto:"∝",prsim:"≾",prurel:"⊰",pscr:"𝓅",Pscr:"đ’Ģ",psi:"Έ",Psi:"Ψ",puncsp:" ",qfr:"𝔮",Qfr:"𝔔",qint:"⨌",qopf:"đ•ĸ",Qopf:"ℚ",qprime:"⁗",qscr:"𝓆",Qscr:"đ’Ŧ",quaternions:"ℍ",quatint:"⨖",quest:"?",questeq:"≟",quot:'"',QUOT:'"',rAarr:"⇛",race:"âˆŊĖą",racute:"ŕ",Racute:"Ŕ",radic:"√",raemptyv:"âĻŗ",rang:"⟩",Rang:"âŸĢ",rangd:"âĻ’",range:"âĻĨ",rangle:"⟩",raquo:"Âģ",rarr:"→",rArr:"⇒",Rarr:"↠",rarrap:"âĨĩ",rarrb:"â‡Ĩ",rarrbfs:"⤠",rarrc:"â¤ŗ",rarrfs:"⤞",rarrhk:"â†Ē",rarrlp:"â†Ŧ",rarrpl:"âĨ…",rarrsim:"âĨ´",rarrtl:"â†Ŗ",Rarrtl:"⤖",rarrw:"↝",ratail:"⤚",rAtail:"⤜",ratio:"âˆļ",rationals:"ℚ",rbarr:"⤍",rBarr:"⤏",RBarr:"⤐",rbbrk:"âŗ",rbrace:"}",rbrack:"]",rbrke:"âό",rbrksld:"âĻŽ",rbrkslu:"âϐ",rcaron:"ř",Rcaron:"Ř",rcedil:"ŗ",Rcedil:"Ŗ",rceil:"⌉",rcub:"}",rcy:"Ņ€",Rcy:"Đ ",rdca:"⤡",rdldhar:"âĨŠ",rdquo:"”",rdquor:"”",rdsh:"â†ŗ",Re:"ℜ",real:"ℜ",realine:"ℛ",realpart:"ℜ",reals:"ℝ",rect:"▭",reg:"ÂŽ",REG:"ÂŽ",ReverseElement:"∋",ReverseEquilibrium:"⇋",ReverseUpEquilibrium:"âĨ¯",rfisht:"âĨŊ",rfloor:"⌋",rfr:"đ”¯",Rfr:"ℜ",rHar:"âĨ¤",rhard:"⇁",rharu:"⇀",rharul:"âĨŦ",rho:"΁",Rho:"ÎĄ",rhov:"Īą",RightAngleBracket:"⟩",rightarrow:"→",Rightarrow:"⇒",RightArrow:"→",RightArrowBar:"â‡Ĩ",RightArrowLeftArrow:"⇄",rightarrowtail:"â†Ŗ",RightCeiling:"⌉",RightDoubleBracket:"⟧",RightDownTeeVector:"âĨ",RightDownVector:"⇂",RightDownVectorBar:"âĨ•",RightFloor:"⌋",rightharpoondown:"⇁",rightharpoonup:"⇀",rightleftarrows:"⇄",rightleftharpoons:"⇌",rightrightarrows:"⇉",rightsquigarrow:"↝",RightTee:"âŠĸ",RightTeeArrow:"â†Ļ",RightTeeVector:"âĨ›",rightthreetimes:"⋌",RightTriangle:"âŠŗ",RightTriangleBar:"⧐",RightTriangleEqual:"âŠĩ",RightUpDownVector:"âĨ",RightUpTeeVector:"âĨœ",RightUpVector:"↾",RightUpVectorBar:"âĨ”",RightVector:"⇀",RightVectorBar:"âĨ“",ring:"˚",risingdotseq:"≓",rlarr:"⇄",rlhar:"⇌",rlm:"‏",rmoust:"⎱",rmoustache:"⎱",rnmid:"âĢŽ",roang:"⟭",roarr:"⇾",robrk:"⟧",ropar:"âφ",ropf:"đ•Ŗ",Ropf:"ℝ",roplus:"⨎",rotimes:"â¨ĩ",RoundImplies:"âĨ°",rpar:")",rpargt:"âĻ”",rppolint:"⨒",rrarr:"⇉",Rrightarrow:"⇛",rsaquo:"â€ē",rscr:"𝓇",Rscr:"ℛ",rsh:"↱",Rsh:"↱",rsqb:"]",rsquo:"’",rsquor:"’",rthree:"⋌",rtimes:"⋊",rtri:"▹",rtrie:"âŠĩ",rtrif:"▸",rtriltri:"⧎",RuleDelayed:"â§´",ruluhar:"âĨ¨",rx:"℞",sacute:"ś",Sacute:"Ś",sbquo:"‚",sc:"â‰ģ",Sc:"âĒŧ",scap:"âǏ",scaron:"ÅĄ",Scaron:"Å ",sccue:"â‰Ŋ",sce:"âǰ",scE:"âĒ´",scedil:"ş",Scedil:"Ş",scirc:"ŝ",Scirc:"Ŝ",scnap:"âĒē",scnE:"âĒļ",scnsim:"⋩",scpolint:"⨓",scsim:"â‰ŋ",scy:"ҁ",Scy:"ĐĄ",sdot:"⋅",sdotb:"⊡",sdote:"âŠĻ",searhk:"â¤Ĩ",searr:"↘",seArr:"⇘",searrow:"↘",sect:"§",semi:";",seswar:"⤊",setminus:"∖",setmn:"∖",sext:"âœļ",sfr:"𝔰",Sfr:"𝔖",sfrown:"âŒĸ",sharp:"♯",shchcy:"҉",SHCHcy:"ĐŠ",shcy:"҈",SHcy:"Ш",ShortDownArrow:"↓",ShortLeftArrow:"←",shortmid:"âˆŖ",shortparallel:"âˆĨ",ShortRightArrow:"→",ShortUpArrow:"↑",shy:"­",sigma:"΃",Sigma:"ÎŖ",sigmaf:"Ī‚",sigmav:"Ī‚",sim:"âˆŧ",simdot:"âŠĒ",sime:"≃",simeq:"≃",simg:"âĒž",simgE:"âĒ ",siml:"âĒ",simlE:"âǟ",simne:"≆",simplus:"⨤",simrarr:"âĨ˛",slarr:"←",SmallCircle:"∘",smallsetminus:"∖",smashp:"â¨ŗ",smeparsl:"⧤",smid:"âˆŖ",smile:"âŒŖ",smt:"âĒĒ",smte:"âĒŦ",smtes:"âĒŦ",softcy:"Ҍ",SOFTcy:"ĐŦ",sol:"/",solb:"⧄",solbar:"âŒŋ",sopf:"𝕤",Sopf:"𝕊",spades:"♠",spadesuit:"♠",spar:"âˆĨ",sqcap:"⊓",sqcaps:"âŠ“ī¸€",sqcup:"⊔",sqcups:"âŠ”ī¸€",Sqrt:"√",sqsub:"⊏",sqsube:"⊑",sqsubset:"⊏",sqsubseteq:"⊑",sqsup:"⊐",sqsupe:"⊒",sqsupset:"⊐",sqsupseteq:"⊒",squ:"□",square:"□",Square:"□",SquareIntersection:"⊓",SquareSubset:"⊏",SquareSubsetEqual:"⊑",SquareSuperset:"⊐",SquareSupersetEqual:"⊒",SquareUnion:"⊔",squarf:"â–Ē",squf:"â–Ē",srarr:"→",sscr:"𝓈",Sscr:"𝒮",ssetmn:"∖",ssmile:"âŒŖ",sstarf:"⋆",star:"☆",Star:"⋆",starf:"★",straightepsilon:"Īĩ",straightphi:"Ī•",strns:"¯",sub:"⊂",Sub:"⋐",subdot:"âĒŊ",sube:"⊆",subE:"âĢ…",subedot:"ẫ",submult:"ấ",subne:"⊊",subnE:"âĢ‹",subplus:"âĒŋ",subrarr:"âĨš",subset:"⊂",Subset:"⋐",subseteq:"⊆",subseteqq:"âĢ…",SubsetEqual:"⊆",subsetneq:"⊊",subsetneqq:"âĢ‹",subsim:"â̇",subsub:"âĢ•",subsup:"âĢ“",succ:"â‰ģ",succapprox:"âǏ",succcurlyeq:"â‰Ŋ",Succeeds:"â‰ģ",SucceedsEqual:"âǰ",SucceedsSlantEqual:"â‰Ŋ",SucceedsTilde:"â‰ŋ",succeq:"âǰ",succnapprox:"âĒē",succneqq:"âĒļ",succnsim:"⋩",succsim:"â‰ŋ",SuchThat:"∋",sum:"∑",Sum:"∑",sung:"â™Ē",sup:"⊃",Sup:"⋑",sup1:"š",sup2:"²",sup3:"Âŗ",supdot:"âĒž",supdsub:"â̘",supe:"⊇",supE:"â̆",supedot:"âĢ„",Superset:"⊃",SupersetEqual:"⊇",suphsol:"⟉",suphsub:"âĢ—",suplarr:"âĨģ",supmult:"âĢ‚",supne:"⊋",supnE:"â̌",supplus:"âĢ€",supset:"⊃",Supset:"⋑",supseteq:"⊇",supseteqq:"â̆",supsetneq:"⊋",supsetneqq:"â̌",supsim:"â̈",supsub:"âĢ”",supsup:"âĢ–",swarhk:"â¤Ļ",swarr:"↙",swArr:"⇙",swarrow:"↙",swnwar:"â¤Ē",szlig:"ß",Tab:"\t",target:"⌖",tau:"Ī„",Tau:"Τ",tbrk:"⎴",tcaron:"ÅĨ",Tcaron:"Ť",tcedil:"ÅŖ",Tcedil:"Åĸ",tcy:"Ņ‚",Tcy:"Đĸ",tdot:"⃛",telrec:"⌕",tfr:"𝔱",Tfr:"𝔗",there4:"∴",therefore:"∴",Therefore:"∴",theta:"θ",Theta:"Θ",thetasym:"Ī‘",thetav:"Ī‘",thickapprox:"≈",thicksim:"âˆŧ",ThickSpace:"  ",thinsp:" ",ThinSpace:" ",thkap:"≈",thksim:"âˆŧ",thorn:"Þ",THORN:"Þ",tilde:"˜",Tilde:"âˆŧ",TildeEqual:"≃",TildeFullEqual:"≅",TildeTilde:"≈",times:"×",timesb:"⊠",timesbar:"⨹",timesd:"⨰",tint:"∭",toea:"⤨",top:"⊤",topbot:"âŒļ",topcir:"âĢą",topf:"đ•Ĩ",Topf:"𝕋",topfork:"â̚",tosa:"⤊",tprime:"‴",trade:"â„ĸ",TRADE:"â„ĸ",triangle:"â–ĩ",triangledown:"â–ŋ",triangleleft:"◃",trianglelefteq:"⊴",triangleq:"≜",triangleright:"▹",trianglerighteq:"âŠĩ",tridot:"â—Ŧ",trie:"≜",triminus:"â¨ē",TripleDot:"⃛",triplus:"⨚",trisb:"⧍",tritime:"â¨ģ",trpezium:"âĸ",tscr:"𝓉",Tscr:"đ’¯",tscy:"҆",TScy:"ĐĻ",tshcy:"Ņ›",TSHcy:"Ћ",tstrok:"ŧ",Tstrok:"ÅĻ",twixt:"â‰Ŧ",twoheadleftarrow:"↞",twoheadrightarrow:"↠",uacute:"Ãē",Uacute:"Ú",uarr:"↑",uArr:"⇑",Uarr:"↟",Uarrocir:"âĨ‰",ubrcy:"Ņž",Ubrcy:"Ў",ubreve:"Å­",Ubreve:"ÅŦ",ucirc:"Ãģ",Ucirc:"Û",ucy:"҃",Ucy:"ĐŖ",udarr:"⇅",udblac:"Åą",Udblac:"Ű",udhar:"âĨŽ",ufisht:"âĨž",ufr:"𝔲",Ufr:"𝔘",ugrave:"Ú",Ugrave:"Ù",uHar:"âĨŖ",uharl:"â†ŋ",uharr:"↾",uhblk:"▀",ulcorn:"⌜",ulcorner:"⌜",ulcrop:"⌏",ultri:"◸",umacr:"ÅĢ",Umacr:"ÅĒ",uml:"¨",UnderBar:"_",UnderBrace:"⏟",UnderBracket:"âŽĩ",UnderParenthesis:"⏝",Union:"⋃",UnionPlus:"⊎",uogon:"Åŗ",Uogon:"Ş",uopf:"đ•Ļ",Uopf:"𝕌",uparrow:"↑",Uparrow:"⇑",UpArrow:"↑",UpArrowBar:"⤒",UpArrowDownArrow:"⇅",updownarrow:"↕",Updownarrow:"⇕",UpDownArrow:"↕",UpEquilibrium:"âĨŽ",upharpoonleft:"â†ŋ",upharpoonright:"↾",uplus:"⊎",UpperLeftArrow:"↖",UpperRightArrow:"↗",upsi:"Ī…",Upsi:"Ī’",upsih:"Ī’",upsilon:"Ī…",Upsilon:"ÎĨ",UpTee:"âŠĨ",UpTeeArrow:"â†Ĩ",upuparrows:"⇈",urcorn:"⌝",urcorner:"⌝",urcrop:"⌎",uring:"ů",Uring:"ÅŽ",urtri:"◹",uscr:"𝓊",Uscr:"𝒰",utdot:"⋰",utilde:"ÅŠ",Utilde:"Ũ",utri:"â–ĩ",utrif:"▴",uuarr:"⇈",uuml:"Ãŧ",Uuml:"Ü",uwangle:"âϧ",vangrt:"âϜ",varepsilon:"Īĩ",varkappa:"ΰ",varnothing:"∅",varphi:"Ī•",varpi:"Ī–",varpropto:"∝",varr:"↕",vArr:"⇕",varrho:"Īą",varsigma:"Ī‚",varsubsetneq:"âŠŠī¸€",varsubsetneqq:"â̋",varsupsetneq:"âŠ‹ī¸€",varsupsetneqq:"âĢŒī¸€",vartheta:"Ī‘",vartriangleleft:"⊲",vartriangleright:"âŠŗ",vBar:"â̍",Vbar:"âĢĢ",vBarv:"âĢŠ",vcy:"в",Vcy:"В",vdash:"âŠĸ",vDash:"⊨",Vdash:"⊩",VDash:"âŠĢ",Vdashl:"âĢĻ",vee:"∨",Vee:"⋁",veebar:"âŠģ",veeeq:"≚",vellip:"⋮",verbar:"|",Verbar:"‖",vert:"|",Vert:"‖",VerticalBar:"âˆŖ",VerticalLine:"|",VerticalSeparator:"❘",VerticalTilde:"≀",VeryThinSpace:" ",vfr:"đ”ŗ",Vfr:"𝔙",vltri:"⊲",vnsub:"⊂⃒",vnsup:"⊃⃒",vopf:"𝕧",Vopf:"𝕍",vprop:"∝",vrtri:"âŠŗ",vscr:"𝓋",Vscr:"𝒱",vsubne:"âŠŠī¸€",vsubnE:"â̋",vsupne:"âŠ‹ī¸€",vsupnE:"âĢŒī¸€",Vvdash:"âŠĒ",vzigzag:"âϚ",wcirc:"Åĩ",Wcirc:"Å´",wedbar:"⩟",wedge:"∧",Wedge:"⋀",wedgeq:"≙",weierp:"℘",wfr:"𝔴",Wfr:"𝔚",wopf:"𝕨",Wopf:"𝕎",wp:"℘",wr:"≀",wreath:"≀",wscr:"𝓌",Wscr:"𝒲",xcap:"⋂",xcirc:"◯",xcup:"⋃",xdtri:"â–Ŋ",xfr:"đ”ĩ",Xfr:"𝔛",xharr:"⟷",xhArr:"âŸē",xi:"Ξ",Xi:"Ξ",xlarr:"âŸĩ",xlArr:"⟸",xmap:"âŸŧ",xnis:"â‹ģ",xodot:"⨀",xopf:"𝕩",Xopf:"𝕏",xoplus:"⨁",xotime:"⨂",xrarr:"âŸļ",xrArr:"⟹",xscr:"𝓍",Xscr:"đ’ŗ",xsqcup:"⨆",xuplus:"⨄",xutri:"â–ŗ",xvee:"⋁",xwedge:"⋀",yacute:"ÃŊ",Yacute:"Ý",yacy:"Ņ",YAcy:"Đ¯",ycirc:"Ŏ",Ycirc:"Åļ",ycy:"Ņ‹",Ycy:"ĐĢ",yen:"ÂĨ",yfr:"đ”ļ",Yfr:"𝔜",yicy:"Ņ—",YIcy:"Ї",yopf:"đ•Ē",Yopf:"𝕐",yscr:"𝓎",Yscr:"𝒴",yucy:"ŅŽ",YUcy:"ĐŽ",yuml:"Ãŋ",Yuml:"Ÿ",zacute:"Åē",Zacute:"Åš",zcaron:"Åž",Zcaron:"ÅŊ",zcy:"С",Zcy:"З",zdot:"Åŧ",Zdot:"Åģ",zeetrf:"ℨ",ZeroWidthSpace:"​",zeta:"Îļ",Zeta:"Ζ",zfr:"𝔷",Zfr:"ℨ",zhcy:"Đļ",ZHcy:"Ж",zigrarr:"⇝",zopf:"đ•Ģ",Zopf:"ℤ",zscr:"𝓏",Zscr:"đ’ĩ",zwj:"‍",zwnj:"‌"},b={aacute:"ÃĄ",Aacute:"Á",acirc:"Ãĸ",Acirc:"Â",acute:"´",aelig:"ÃĻ",AElig:"Æ",agrave:"à",Agrave:"À",amp:"&",AMP:"&",aring:"ÃĨ",Aring:"Å",atilde:"ÃŖ",Atilde:"Ã",auml:"ä",Auml:"Ä",brvbar:"ÂĻ",ccedil:"ç",Ccedil:"Ç",cedil:"¸",cent:"Âĸ",copy:"Š",COPY:"Š",curren:"¤",deg:"°",divide:"Ãˇ",eacute:"Ê",Eacute:"É",ecirc:"ÃĒ",Ecirc:"Ê",egrave:"è",Egrave:"È",eth:"ð",ETH:"Ð",euml:"ÃĢ",Euml:"Ë",frac12:"ÂŊ",frac14:"Âŧ",frac34:"ž",gt:">",GT:">",iacute:"í",Iacute:"Í",icirc:"ÃŽ",Icirc:"Î",iexcl:"ÂĄ",igrave:"ÃŦ",Igrave:"Ì",iquest:"Âŋ",iuml:"ï",Iuml:"Ï",laquo:"ÂĢ",lt:"<",LT:"<",macr:"¯",micro:"Âĩ",middot:"¡",nbsp:" ",not:"ÂŦ",ntilde:"Ãą",Ntilde:"Ñ",oacute:"Ãŗ",Oacute:"Ó",ocirc:"ô",Ocirc:"Ô",ograve:"Ã˛",Ograve:"Ò",ordf:"ÂĒ",ordm:"Âē",oslash:"ø",Oslash:"Ø",otilde:"Ãĩ",Otilde:"Õ",ouml:"Ãļ",Ouml:"Ö",para:"Âļ",plusmn:"Âą",pound:"ÂŖ",quot:'"',QUOT:'"',raquo:"Âģ",reg:"ÂŽ",REG:"ÂŽ",sect:"§",shy:"­",sup1:"š",sup2:"²",sup3:"Âŗ",szlig:"ß",thorn:"Þ",THORN:"Þ",times:"×",uacute:"Ãē",Uacute:"Ú",ucirc:"Ãģ",Ucirc:"Û",ugrave:"Ú",Ugrave:"Ù",uml:"¨",uuml:"Ãŧ",Uuml:"Ü",yacute:"ÃŊ",Yacute:"Ý",yen:"ÂĨ",yuml:"Ãŋ"},y={0:"īŋŊ",128:"â‚Ŧ",130:"‚",131:"ƒ",132:"„",133:"â€Ļ",134:"†",135:"‡",136:"ˆ",137:"‰",138:"Å ",139:"‹",140:"Œ",142:"ÅŊ",145:"‘",146:"’",147:"“",148:"”",149:"â€ĸ",150:"–",151:"—",152:"˜",153:"â„ĸ",154:"ÅĄ",155:"â€ē",156:"œ",158:"Åž",159:"Ÿ"},w=[1,2,3,4,5,6,7,8,11,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,64976,64977,64978,64979,64980,64981,64982,64983,64984,64985,64986,64987,64988,64989,64990,64991,64992,64993,64994,64995,64996,64997,64998,64999,65e3,65001,65002,65003,65004,65005,65006,65007,65534,65535,131070,131071,196606,196607,262142,262143,327678,327679,393214,393215,458750,458751,524286,524287,589822,589823,655358,655359,720894,720895,786430,786431,851966,851967,917502,917503,983038,983039,1048574,1048575,1114110,1114111],_=String.fromCharCode,x={}.hasOwnProperty,A=function(e,t){return x.call(e,t)},E=function(e,t){if(!e)return t;var r,n={};for(r in t)n[r]=A(e,r)?e[r]:t[r];return n},D=function(e,t){var r="";return e>=55296&&e<=57343||e>1114111?(t&&q("character reference outside the permissible Unicode range"),"īŋŊ"):A(y,e)?(t&&q("disallowed character reference"),y[e]):(t&&function(e,t){for(var r=-1,n=e.length;++r65535&&(r+=_((e-=65536)>>>10&1023|55296),e=56320|1023&e),r+=_(e))},T=function(e){return"&#x"+e.toString(16).toUpperCase()+";"},C=function(e){return"&#"+e+";"},q=function(e){throw Error("Parse error: "+e)},k=function(e,t){(t=E(t,k.options)).strict&&g.test(e)&&q("forbidden code point");var r=t.encodeEverything,n=t.useNamedReferences,i=t.allowUnsafeSymbols,o=t.decimal?C:T,s=function(e){return o(e.charCodeAt(0))};return r?(e=e.replace(l,(function(e){return n&&A(p,e)?"&"+p[e]+";":s(e)})),n&&(e=e.replace(/>\u20D2/g,">⃒").replace(/<\u20D2/g,"<⃒").replace(/fj/g,"fj")),n&&(e=e.replace(u,(function(e){return"&"+p[e]+";"})))):n?(i||(e=e.replace(f,(function(e){return"&"+p[e]+";"}))),e=(e=e.replace(/>\u20D2/g,">⃒").replace(/<\u20D2/g,"<⃒")).replace(u,(function(e){return"&"+p[e]+";"}))):i||(e=e.replace(f,s)),e.replace(a,(function(e){var t=e.charCodeAt(0),r=e.charCodeAt(1);return o(1024*(t-55296)+r-56320+65536)})).replace(c,s)};k.options={allowUnsafeSymbols:!1,encodeEverything:!1,strict:!1,useNamedReferences:!1,decimal:!1};var L=function(e,t){var r=(t=E(t,L.options)).strict;return r&&h.test(e)&&q("malformed character reference"),e.replace(m,(function(e,n,i,o,s,a,l,c,u){var p,f,d,h,g,m;return n?v[g=n]:i?(g=i,(m=o)&&t.isAttributeValue?(r&&"="==m&&q("`&` did not start a character reference"),e):(r&&q("named character reference was not terminated by a semicolon"),b[g]+(m||""))):s?(d=s,f=a,r&&!f&&q("character reference was not terminated by a semicolon"),p=parseInt(d,10),D(p,r)):l?(h=l,f=c,r&&!f&&q("character reference was not terminated by a semicolon"),p=parseInt(h,16),D(p,r)):(r&&q("named character reference was not terminated by a semicolon"),e)}))};L.options={isAttributeValue:!1,strict:!1};var S={version:"1.2.0",encode:k,decode:L,escape:function(e){return e.replace(f,(function(e){return d[e]}))},unescape:L};void 0===(n=function(){return S}.call(t,r,t,e))||(e.exports=n)}()},755:function(e,t){var r;!function(t,r){"use strict";"object"==typeof e.exports?e.exports=t.document?r(t,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return r(e)}:r(t)}("undefined"!=typeof window?window:this,(function(n,i){"use strict";var o=[],s=Object.getPrototypeOf,a=o.slice,l=o.flat?function(e){return o.flat.call(e)}:function(e){return o.concat.apply([],e)},c=o.push,u=o.indexOf,p={},f=p.toString,d=p.hasOwnProperty,h=d.toString,g=h.call(Object),m={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},b=function(e){return null!=e&&e===e.window},y=n.document,w={type:!0,src:!0,nonce:!0,noModule:!0};function _(e,t,r){var n,i,o=(r=r||y).createElement("script");if(o.text=e,t)for(n in w)(i=t[n]||t.getAttribute&&t.getAttribute(n))&&o.setAttribute(n,i);r.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?p[f.call(e)]||"object":typeof e}var A="3.6.4",E=function(e,t){return new E.fn.init(e,t)};function D(e){var t=!!e&&"length"in e&&e.length,r=x(e);return!v(e)&&!b(e)&&("array"===r||0===t||"number"==typeof t&&t>0&&t-1 in e)}E.fn=E.prototype={jquery:A,constructor:E,length:0,toArray:function(){return a.call(this)},get:function(e){return null==e?a.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=E.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return E.each(this,e)},map:function(e){return this.pushStack(E.map(this,(function(t,r){return e.call(t,r,t)})))},slice:function(){return this.pushStack(a.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(E.grep(this,(function(e,t){return(t+1)%2})))},odd:function(){return this.pushStack(E.grep(this,(function(e,t){return t%2})))},eq:function(e){var t=this.length,r=+e+(e<0?t:0);return this.pushStack(r>=0&&r+~]|"+I+")"+I+"*"),z=new RegExp(I+"|>"),G=new RegExp(H),W=new RegExp("^"+P+"$"),Y={ID:new RegExp("^#("+P+")"),CLASS:new RegExp("^\\.("+P+")"),TAG:new RegExp("^("+P+"|[*])"),ATTR:new RegExp("^"+F),PSEUDO:new RegExp("^"+H),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+I+"*(even|odd|(([+-]|)(\\d*)n|)"+I+"*(?:([+-]|)"+I+"*(\\d+)|))"+I+"*\\)|)","i"),bool:new RegExp("^(?:"+B+")$","i"),needsContext:new RegExp("^"+I+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+I+"*((?:-\\d)?\\d*)"+I+"*\\)|)(?=[^-]|$)","i")},X=/HTML$/i,K=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,Q=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+I+"?|\\\\([^\\r\\n\\f])","g"),re=function(e,t){var r="0x"+e.slice(1)-65536;return t||(r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320))},ne=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"īŋŊ":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){f()},se=we((function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()}),{dir:"parentNode",next:"legend"});try{O.apply(L=j.call(_.childNodes),_.childNodes),L[_.childNodes.length].nodeType}catch(e){O={apply:L.length?function(e,t){N.apply(e,j.call(t))}:function(e,t){for(var r=e.length,n=0;e[r++]=t[n++];);e.length=r-1}}}function ae(e,t,n,i){var o,a,c,u,p,h,v,b=t&&t.ownerDocument,_=t?t.nodeType:9;if(n=n||[],"string"!=typeof e||!e||1!==_&&9!==_&&11!==_)return n;if(!i&&(f(t),t=t||d,g)){if(11!==_&&(p=Z.exec(e)))if(o=p[1]){if(9===_){if(!(c=t.getElementById(o)))return n;if(c.id===o)return n.push(c),n}else if(b&&(c=b.getElementById(o))&&y(t,c)&&c.id===o)return n.push(c),n}else{if(p[2])return O.apply(n,t.getElementsByTagName(e)),n;if((o=p[3])&&r.getElementsByClassName&&t.getElementsByClassName)return O.apply(n,t.getElementsByClassName(o)),n}if(r.qsa&&!C[e+" "]&&(!m||!m.test(e))&&(1!==_||"object"!==t.nodeName.toLowerCase())){if(v=e,b=t,1===_&&(z.test(e)||$.test(e))){for((b=ee.test(e)&&ve(t.parentNode)||t)===t&&r.scope||((u=t.getAttribute("id"))?u=u.replace(ne,ie):t.setAttribute("id",u=w)),a=(h=s(e)).length;a--;)h[a]=(u?"#"+u:":scope")+" "+ye(h[a]);v=h.join(",")}try{return O.apply(n,b.querySelectorAll(v)),n}catch(t){C(e,!0)}finally{u===w&&t.removeAttribute("id")}}}return l(e.replace(U,"$1"),t,n,i)}function le(){var e=[];return function t(r,i){return e.push(r+" ")>n.cacheLength&&delete t[e.shift()],t[r+" "]=i}}function ce(e){return e[w]=!0,e}function ue(e){var t=d.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function pe(e,t){for(var r=e.split("|"),i=r.length;i--;)n.attrHandle[r[i]]=t}function fe(e,t){var r=t&&e,n=r&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(n)return n;if(r)for(;r=r.nextSibling;)if(r===t)return-1;return e?1:-1}function de(e){return function(t){return"input"===t.nodeName.toLowerCase()&&t.type===e}}function he(e){return function(t){var r=t.nodeName.toLowerCase();return("input"===r||"button"===r)&&t.type===e}}function ge(e){return function(t){return"form"in t?t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&se(t)===e:t.disabled===e:"label"in t&&t.disabled===e}}function me(e){return ce((function(t){return t=+t,ce((function(r,n){for(var i,o=e([],r.length,t),s=o.length;s--;)r[i=o[s]]&&(r[i]=!(n[i]=r[i]))}))}))}function ve(e){return e&&void 0!==e.getElementsByTagName&&e}for(t in r=ae.support={},o=ae.isXML=function(e){var t=e&&e.namespaceURI,r=e&&(e.ownerDocument||e).documentElement;return!X.test(t||r&&r.nodeName||"HTML")},f=ae.setDocument=function(e){var t,i,s=e?e.ownerDocument||e:_;return s!=d&&9===s.nodeType&&s.documentElement?(h=(d=s).documentElement,g=!o(d),_!=d&&(i=d.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",oe,!1):i.attachEvent&&i.attachEvent("onunload",oe)),r.scope=ue((function(e){return h.appendChild(e).appendChild(d.createElement("div")),void 0!==e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length})),r.cssHas=ue((function(){try{return d.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}})),r.attributes=ue((function(e){return e.className="i",!e.getAttribute("className")})),r.getElementsByTagName=ue((function(e){return e.appendChild(d.createComment("")),!e.getElementsByTagName("*").length})),r.getElementsByClassName=Q.test(d.getElementsByClassName),r.getById=ue((function(e){return h.appendChild(e).id=w,!d.getElementsByName||!d.getElementsByName(w).length})),r.getById?(n.filter.ID=function(e){var t=e.replace(te,re);return function(e){return e.getAttribute("id")===t}},n.find.ID=function(e,t){if(void 0!==t.getElementById&&g){var r=t.getElementById(e);return r?[r]:[]}}):(n.filter.ID=function(e){var t=e.replace(te,re);return function(e){var r=void 0!==e.getAttributeNode&&e.getAttributeNode("id");return r&&r.value===t}},n.find.ID=function(e,t){if(void 0!==t.getElementById&&g){var r,n,i,o=t.getElementById(e);if(o){if((r=o.getAttributeNode("id"))&&r.value===e)return[o];for(i=t.getElementsByName(e),n=0;o=i[n++];)if((r=o.getAttributeNode("id"))&&r.value===e)return[o]}return[]}}),n.find.TAG=r.getElementsByTagName?function(e,t){return void 0!==t.getElementsByTagName?t.getElementsByTagName(e):r.qsa?t.querySelectorAll(e):void 0}:function(e,t){var r,n=[],i=0,o=t.getElementsByTagName(e);if("*"===e){for(;r=o[i++];)1===r.nodeType&&n.push(r);return n}return o},n.find.CLASS=r.getElementsByClassName&&function(e,t){if(void 0!==t.getElementsByClassName&&g)return t.getElementsByClassName(e)},v=[],m=[],(r.qsa=Q.test(d.querySelectorAll))&&(ue((function(e){var t;h.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&m.push("[*^$]="+I+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||m.push("\\["+I+"*(?:value|"+B+")"),e.querySelectorAll("[id~="+w+"-]").length||m.push("~="),(t=d.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||m.push("\\["+I+"*name"+I+"*="+I+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||m.push(":checked"),e.querySelectorAll("a#"+w+"+*").length||m.push(".#.+[+~]"),e.querySelectorAll("\\\f"),m.push("[\\r\\n\\f]")})),ue((function(e){e.innerHTML="";var t=d.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&m.push("name"+I+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&m.push(":enabled",":disabled"),h.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&m.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),m.push(",.*:")}))),(r.matchesSelector=Q.test(b=h.matches||h.webkitMatchesSelector||h.mozMatchesSelector||h.oMatchesSelector||h.msMatchesSelector))&&ue((function(e){r.disconnectedMatch=b.call(e,"*"),b.call(e,"[s!='']:x"),v.push("!=",H)})),r.cssHas||m.push(":has"),m=m.length&&new RegExp(m.join("|")),v=v.length&&new RegExp(v.join("|")),t=Q.test(h.compareDocumentPosition),y=t||Q.test(h.contains)?function(e,t){var r=9===e.nodeType&&e.documentElement||e,n=t&&t.parentNode;return e===n||!(!n||1!==n.nodeType||!(r.contains?r.contains(n):e.compareDocumentPosition&&16&e.compareDocumentPosition(n)))}:function(e,t){if(t)for(;t=t.parentNode;)if(t===e)return!0;return!1},q=t?function(e,t){if(e===t)return p=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!r.sortDetached&&t.compareDocumentPosition(e)===n?e==d||e.ownerDocument==_&&y(_,e)?-1:t==d||t.ownerDocument==_&&y(_,t)?1:u?R(u,e)-R(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return p=!0,0;var r,n=0,i=e.parentNode,o=t.parentNode,s=[e],a=[t];if(!i||!o)return e==d?-1:t==d?1:i?-1:o?1:u?R(u,e)-R(u,t):0;if(i===o)return fe(e,t);for(r=e;r=r.parentNode;)s.unshift(r);for(r=t;r=r.parentNode;)a.unshift(r);for(;s[n]===a[n];)n++;return n?fe(s[n],a[n]):s[n]==_?-1:a[n]==_?1:0},d):d},ae.matches=function(e,t){return ae(e,null,null,t)},ae.matchesSelector=function(e,t){if(f(e),r.matchesSelector&&g&&!C[t+" "]&&(!v||!v.test(t))&&(!m||!m.test(t)))try{var n=b.call(e,t);if(n||r.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){C(t,!0)}return ae(t,d,null,[e]).length>0},ae.contains=function(e,t){return(e.ownerDocument||e)!=d&&f(e),y(e,t)},ae.attr=function(e,t){(e.ownerDocument||e)!=d&&f(e);var i=n.attrHandle[t.toLowerCase()],o=i&&k.call(n.attrHandle,t.toLowerCase())?i(e,t,!g):void 0;return void 0!==o?o:r.attributes||!g?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null},ae.escape=function(e){return(e+"").replace(ne,ie)},ae.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},ae.uniqueSort=function(e){var t,n=[],i=0,o=0;if(p=!r.detectDuplicates,u=!r.sortStable&&e.slice(0),e.sort(q),p){for(;t=e[o++];)t===e[o]&&(i=n.push(o));for(;i--;)e.splice(n[i],1)}return u=null,e},i=ae.getText=function(e){var t,r="",n=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)r+=i(e)}else if(3===o||4===o)return e.nodeValue}else for(;t=e[n++];)r+=i(t);return r},n=ae.selectors={cacheLength:50,createPseudo:ce,match:Y,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,re),e[3]=(e[3]||e[4]||e[5]||"").replace(te,re),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||ae.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&ae.error(e[0]),e},PSEUDO:function(e){var t,r=!e[6]&&e[2];return Y.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":r&&G.test(r)&&(t=s(r,!0))&&(t=r.indexOf(")",r.length-t)-r.length)&&(e[0]=e[0].slice(0,t),e[2]=r.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,re).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=E[e+" "];return t||(t=new RegExp("(^|"+I+")"+e+"("+I+"|$)"))&&E(e,(function(e){return t.test("string"==typeof e.className&&e.className||void 0!==e.getAttribute&&e.getAttribute("class")||"")}))},ATTR:function(e,t,r){return function(n){var i=ae.attr(n,e);return null==i?"!="===t:!t||(i+="","="===t?i===r:"!="===t?i!==r:"^="===t?r&&0===i.indexOf(r):"*="===t?r&&i.indexOf(r)>-1:"$="===t?r&&i.slice(-r.length)===r:"~="===t?(" "+i.replace(M," ")+" ").indexOf(r)>-1:"|="===t&&(i===r||i.slice(0,r.length+1)===r+"-"))}},CHILD:function(e,t,r,n,i){var o="nth"!==e.slice(0,3),s="last"!==e.slice(-4),a="of-type"===t;return 1===n&&0===i?function(e){return!!e.parentNode}:function(t,r,l){var c,u,p,f,d,h,g=o!==s?"nextSibling":"previousSibling",m=t.parentNode,v=a&&t.nodeName.toLowerCase(),b=!l&&!a,y=!1;if(m){if(o){for(;g;){for(f=t;f=f[g];)if(a?f.nodeName.toLowerCase()===v:1===f.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[s?m.firstChild:m.lastChild],s&&b){for(y=(d=(c=(u=(p=(f=m)[w]||(f[w]={}))[f.uniqueID]||(p[f.uniqueID]={}))[e]||[])[0]===x&&c[1])&&c[2],f=d&&m.childNodes[d];f=++d&&f&&f[g]||(y=d=0)||h.pop();)if(1===f.nodeType&&++y&&f===t){u[e]=[x,d,y];break}}else if(b&&(y=d=(c=(u=(p=(f=t)[w]||(f[w]={}))[f.uniqueID]||(p[f.uniqueID]={}))[e]||[])[0]===x&&c[1]),!1===y)for(;(f=++d&&f&&f[g]||(y=d=0)||h.pop())&&((a?f.nodeName.toLowerCase()!==v:1!==f.nodeType)||!++y||(b&&((u=(p=f[w]||(f[w]={}))[f.uniqueID]||(p[f.uniqueID]={}))[e]=[x,y]),f!==t)););return(y-=i)===n||y%n==0&&y/n>=0}}},PSEUDO:function(e,t){var r,i=n.pseudos[e]||n.setFilters[e.toLowerCase()]||ae.error("unsupported pseudo: "+e);return i[w]?i(t):i.length>1?(r=[e,e,"",t],n.setFilters.hasOwnProperty(e.toLowerCase())?ce((function(e,r){for(var n,o=i(e,t),s=o.length;s--;)e[n=R(e,o[s])]=!(r[n]=o[s])})):function(e){return i(e,0,r)}):i}},pseudos:{not:ce((function(e){var t=[],r=[],n=a(e.replace(U,"$1"));return n[w]?ce((function(e,t,r,i){for(var o,s=n(e,null,i,[]),a=e.length;a--;)(o=s[a])&&(e[a]=!(t[a]=o))})):function(e,i,o){return t[0]=e,n(t,null,o,r),t[0]=null,!r.pop()}})),has:ce((function(e){return function(t){return ae(e,t).length>0}})),contains:ce((function(e){return e=e.replace(te,re),function(t){return(t.textContent||i(t)).indexOf(e)>-1}})),lang:ce((function(e){return W.test(e||"")||ae.error("unsupported lang: "+e),e=e.replace(te,re).toLowerCase(),function(t){var r;do{if(r=g?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(r=r.toLowerCase())===e||0===r.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}})),target:function(t){var r=e.location&&e.location.hash;return r&&r.slice(1)===t.id},root:function(e){return e===h},focus:function(e){return e===d.activeElement&&(!d.hasFocus||d.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:ge(!1),disabled:ge(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!n.pseudos.empty(e)},header:function(e){return J.test(e.nodeName)},input:function(e){return K.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:me((function(){return[0]})),last:me((function(e,t){return[t-1]})),eq:me((function(e,t,r){return[r<0?r+t:r]})),even:me((function(e,t){for(var r=0;rt?t:r;--n>=0;)e.push(n);return e})),gt:me((function(e,t,r){for(var n=r<0?r+t:r;++n1?function(t,r,n){for(var i=e.length;i--;)if(!e[i](t,r,n))return!1;return!0}:e[0]}function xe(e,t,r,n,i){for(var o,s=[],a=0,l=e.length,c=null!=t;a-1&&(o[c]=!(s[c]=p))}}else v=xe(v===s?v.splice(h,v.length):v),i?i(null,s,v,l):O.apply(s,v)}))}function Ee(e){for(var t,r,i,o=e.length,s=n.relative[e[0].type],a=s||n.relative[" "],l=s?1:0,u=we((function(e){return e===t}),a,!0),p=we((function(e){return R(t,e)>-1}),a,!0),f=[function(e,r,n){var i=!s&&(n||r!==c)||((t=r).nodeType?u(e,r,n):p(e,r,n));return t=null,i}];l1&&_e(f),l>1&&ye(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(U,"$1"),r,l0,i=e.length>0,o=function(o,s,a,l,u){var p,h,m,v=0,b="0",y=o&&[],w=[],_=c,A=o||i&&n.find.TAG("*",u),E=x+=null==_?1:Math.random()||.1,D=A.length;for(u&&(c=s==d||s||u);b!==D&&null!=(p=A[b]);b++){if(i&&p){for(h=0,s||p.ownerDocument==d||(f(p),a=!g);m=e[h++];)if(m(p,s||d,a)){l.push(p);break}u&&(x=E)}r&&((p=!m&&p)&&v--,o&&y.push(p))}if(v+=b,r&&b!==v){for(h=0;m=t[h++];)m(y,w,s,a);if(o){if(v>0)for(;b--;)y[b]||w[b]||(w[b]=S.call(l));w=xe(w)}O.apply(l,w),u&&!o&&w.length>0&&v+t.length>1&&ae.uniqueSort(l)}return u&&(x=E,c=_),y};return r?ce(o):o}(o,i)),a.selector=e}return a},l=ae.select=function(e,t,r,i){var o,l,c,u,p,f="function"==typeof e&&e,d=!i&&s(e=f.selector||e);if(r=r||[],1===d.length){if((l=d[0]=d[0].slice(0)).length>2&&"ID"===(c=l[0]).type&&9===t.nodeType&&g&&n.relative[l[1].type]){if(!(t=(n.find.ID(c.matches[0].replace(te,re),t)||[])[0]))return r;f&&(t=t.parentNode),e=e.slice(l.shift().value.length)}for(o=Y.needsContext.test(e)?0:l.length;o--&&(c=l[o],!n.relative[u=c.type]);)if((p=n.find[u])&&(i=p(c.matches[0].replace(te,re),ee.test(l[0].type)&&ve(t.parentNode)||t))){if(l.splice(o,1),!(e=i.length&&ye(l)))return O.apply(r,i),r;break}}return(f||a(e,d))(i,t,!g,r,!t||ee.test(e)&&ve(t.parentNode)||t),r},r.sortStable=w.split("").sort(q).join("")===w,r.detectDuplicates=!!p,f(),r.sortDetached=ue((function(e){return 1&e.compareDocumentPosition(d.createElement("fieldset"))})),ue((function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")}))||pe("type|href|height|width",(function(e,t,r){if(!r)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)})),r.attributes&&ue((function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")}))||pe("value",(function(e,t,r){if(!r&&"input"===e.nodeName.toLowerCase())return e.defaultValue})),ue((function(e){return null==e.getAttribute("disabled")}))||pe(B,(function(e,t,r){var n;if(!r)return!0===e[t]?t.toLowerCase():(n=e.getAttributeNode(t))&&n.specified?n.value:null})),ae}(n);E.find=T,E.expr=T.selectors,E.expr[":"]=E.expr.pseudos,E.uniqueSort=E.unique=T.uniqueSort,E.text=T.getText,E.isXMLDoc=T.isXML,E.contains=T.contains,E.escapeSelector=T.escape;var C=function(e,t,r){for(var n=[],i=void 0!==r;(e=e[t])&&9!==e.nodeType;)if(1===e.nodeType){if(i&&E(e).is(r))break;n.push(e)}return n},q=function(e,t){for(var r=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&r.push(e);return r},k=E.expr.match.needsContext;function L(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var S=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function N(e,t,r){return v(t)?E.grep(e,(function(e,n){return!!t.call(e,n,e)!==r})):t.nodeType?E.grep(e,(function(e){return e===t!==r})):"string"!=typeof t?E.grep(e,(function(e){return u.call(t,e)>-1!==r})):E.filter(t,e,r)}E.filter=function(e,t,r){var n=t[0];return r&&(e=":not("+e+")"),1===t.length&&1===n.nodeType?E.find.matchesSelector(n,e)?[n]:[]:E.find.matches(e,E.grep(t,(function(e){return 1===e.nodeType})))},E.fn.extend({find:function(e){var t,r,n=this.length,i=this;if("string"!=typeof e)return this.pushStack(E(e).filter((function(){for(t=0;t1?E.uniqueSort(r):r},filter:function(e){return this.pushStack(N(this,e||[],!1))},not:function(e){return this.pushStack(N(this,e||[],!0))},is:function(e){return!!N(this,"string"==typeof e&&k.test(e)?E(e):e||[],!1).length}});var O,j=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(E.fn.init=function(e,t,r){var n,i;if(!e)return this;if(r=r||O,"string"==typeof e){if(!(n="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:j.exec(e))||!n[1]&&t)return!t||t.jquery?(t||r).find(e):this.constructor(t).find(e);if(n[1]){if(t=t instanceof E?t[0]:t,E.merge(this,E.parseHTML(n[1],t&&t.nodeType?t.ownerDocument||t:y,!0)),S.test(n[1])&&E.isPlainObject(t))for(n in t)v(this[n])?this[n](t[n]):this.attr(n,t[n]);return this}return(i=y.getElementById(n[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==r.ready?r.ready(e):e(E):E.makeArray(e,this)}).prototype=E.fn,O=E(y);var R=/^(?:parents|prev(?:Until|All))/,B={children:!0,contents:!0,next:!0,prev:!0};function I(e,t){for(;(e=e[t])&&1!==e.nodeType;);return e}E.fn.extend({has:function(e){var t=E(e,this),r=t.length;return this.filter((function(){for(var e=0;e-1:1===r.nodeType&&E.find.matchesSelector(r,e))){o.push(r);break}return this.pushStack(o.length>1?E.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?u.call(E(e),this[0]):u.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(E.uniqueSort(E.merge(this.get(),E(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),E.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return C(e,"parentNode")},parentsUntil:function(e,t,r){return C(e,"parentNode",r)},next:function(e){return I(e,"nextSibling")},prev:function(e){return I(e,"previousSibling")},nextAll:function(e){return C(e,"nextSibling")},prevAll:function(e){return C(e,"previousSibling")},nextUntil:function(e,t,r){return C(e,"nextSibling",r)},prevUntil:function(e,t,r){return C(e,"previousSibling",r)},siblings:function(e){return q((e.parentNode||{}).firstChild,e)},children:function(e){return q(e.firstChild)},contents:function(e){return null!=e.contentDocument&&s(e.contentDocument)?e.contentDocument:(L(e,"template")&&(e=e.content||e),E.merge([],e.childNodes))}},(function(e,t){E.fn[e]=function(r,n){var i=E.map(this,t,r);return"Until"!==e.slice(-5)&&(n=r),n&&"string"==typeof n&&(i=E.filter(n,i)),this.length>1&&(B[e]||E.uniqueSort(i),R.test(e)&&i.reverse()),this.pushStack(i)}}));var P=/[^\x20\t\r\n\f]+/g;function F(e){return e}function H(e){throw e}function M(e,t,r,n){var i;try{e&&v(i=e.promise)?i.call(e).done(t).fail(r):e&&v(i=e.then)?i.call(e,t,r):t.apply(void 0,[e].slice(n))}catch(e){r.apply(void 0,[e])}}E.Callbacks=function(e){e="string"==typeof e?function(e){var t={};return E.each(e.match(P)||[],(function(e,r){t[r]=!0})),t}(e):E.extend({},e);var t,r,n,i,o=[],s=[],a=-1,l=function(){for(i=i||e.once,n=t=!0;s.length;a=-1)for(r=s.shift();++a-1;)o.splice(r,1),r<=a&&a--})),this},has:function(e){return e?E.inArray(e,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return i=s=[],o=r="",this},disabled:function(){return!o},lock:function(){return i=s=[],r||t||(o=r=""),this},locked:function(){return!!i},fireWith:function(e,r){return i||(r=[e,(r=r||[]).slice?r.slice():r],s.push(r),t||l()),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!n}};return c},E.extend({Deferred:function(e){var t=[["notify","progress",E.Callbacks("memory"),E.Callbacks("memory"),2],["resolve","done",E.Callbacks("once memory"),E.Callbacks("once memory"),0,"resolved"],["reject","fail",E.Callbacks("once memory"),E.Callbacks("once memory"),1,"rejected"]],r="pending",i={state:function(){return r},always:function(){return o.done(arguments).fail(arguments),this},catch:function(e){return i.then(null,e)},pipe:function(){var e=arguments;return E.Deferred((function(r){E.each(t,(function(t,n){var i=v(e[n[4]])&&e[n[4]];o[n[1]]((function(){var e=i&&i.apply(this,arguments);e&&v(e.promise)?e.promise().progress(r.notify).done(r.resolve).fail(r.reject):r[n[0]+"With"](this,i?[e]:arguments)}))})),e=null})).promise()},then:function(e,r,i){var o=0;function s(e,t,r,i){return function(){var a=this,l=arguments,c=function(){var n,c;if(!(e=o&&(r!==H&&(a=void 0,l=[n]),t.rejectWith(a,l))}};e?u():(E.Deferred.getStackHook&&(u.stackTrace=E.Deferred.getStackHook()),n.setTimeout(u))}}return E.Deferred((function(n){t[0][3].add(s(0,n,v(i)?i:F,n.notifyWith)),t[1][3].add(s(0,n,v(e)?e:F)),t[2][3].add(s(0,n,v(r)?r:H))})).promise()},promise:function(e){return null!=e?E.extend(e,i):i}},o={};return E.each(t,(function(e,n){var s=n[2],a=n[5];i[n[1]]=s.add,a&&s.add((function(){r=a}),t[3-e][2].disable,t[3-e][3].disable,t[0][2].lock,t[0][3].lock),s.add(n[3].fire),o[n[0]]=function(){return o[n[0]+"With"](this===o?void 0:this,arguments),this},o[n[0]+"With"]=s.fireWith})),i.promise(o),e&&e.call(o,o),o},when:function(e){var t=arguments.length,r=t,n=Array(r),i=a.call(arguments),o=E.Deferred(),s=function(e){return function(r){n[e]=this,i[e]=arguments.length>1?a.call(arguments):r,--t||o.resolveWith(n,i)}};if(t<=1&&(M(e,o.done(s(r)).resolve,o.reject,!t),"pending"===o.state()||v(i[r]&&i[r].then)))return o.then();for(;r--;)M(i[r],s(r),o.reject);return o.promise()}});var U=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;E.Deferred.exceptionHook=function(e,t){n.console&&n.console.warn&&e&&U.test(e.name)&&n.console.warn("jQuery.Deferred exception: "+e.message,e.stack,t)},E.readyException=function(e){n.setTimeout((function(){throw e}))};var V=E.Deferred();function $(){y.removeEventListener("DOMContentLoaded",$),n.removeEventListener("load",$),E.ready()}E.fn.ready=function(e){return V.then(e).catch((function(e){E.readyException(e)})),this},E.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--E.readyWait:E.isReady)||(E.isReady=!0,!0!==e&&--E.readyWait>0||V.resolveWith(y,[E]))}}),E.ready.then=V.then,"complete"===y.readyState||"loading"!==y.readyState&&!y.documentElement.doScroll?n.setTimeout(E.ready):(y.addEventListener("DOMContentLoaded",$),n.addEventListener("load",$));var z=function(e,t,r,n,i,o,s){var a=0,l=e.length,c=null==r;if("object"===x(r))for(a in i=!0,r)z(e,t,a,r[a],!0,o,s);else if(void 0!==n&&(i=!0,v(n)||(s=!0),c&&(s?(t.call(e,n),t=null):(c=t,t=function(e,t,r){return c.call(E(e),r)})),t))for(;a1,null,!0)},removeData:function(e){return this.each((function(){Z.remove(this,e)}))}}),E.extend({queue:function(e,t,r){var n;if(e)return t=(t||"fx")+"queue",n=Q.get(e,t),r&&(!n||Array.isArray(r)?n=Q.access(e,t,E.makeArray(r)):n.push(r)),n||[]},dequeue:function(e,t){t=t||"fx";var r=E.queue(e,t),n=r.length,i=r.shift(),o=E._queueHooks(e,t);"inprogress"===i&&(i=r.shift(),n--),i&&("fx"===t&&r.unshift("inprogress"),delete o.stop,i.call(e,(function(){E.dequeue(e,t)}),o)),!n&&o&&o.empty.fire()},_queueHooks:function(e,t){var r=t+"queueHooks";return Q.get(e,r)||Q.access(e,r,{empty:E.Callbacks("once memory").add((function(){Q.remove(e,[t+"queue",r])}))})}}),E.fn.extend({queue:function(e,t){var r=2;return"string"!=typeof e&&(t=e,e="fx",r--),arguments.length\x20\t\r\n\f]*)/i,be=/^$|^module$|\/(?:java|ecma)script/i;he=y.createDocumentFragment().appendChild(y.createElement("div")),(ge=y.createElement("input")).setAttribute("type","radio"),ge.setAttribute("checked","checked"),ge.setAttribute("name","t"),he.appendChild(ge),m.checkClone=he.cloneNode(!0).cloneNode(!0).lastChild.checked,he.innerHTML="",m.noCloneChecked=!!he.cloneNode(!0).lastChild.defaultValue,he.innerHTML="",m.option=!!he.lastChild;var ye={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function we(e,t){var r;return r=void 0!==e.getElementsByTagName?e.getElementsByTagName(t||"*"):void 0!==e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&L(e,t)?E.merge([e],r):r}function _e(e,t){for(var r=0,n=e.length;r",""]);var xe=/<|&#?\w+;/;function Ae(e,t,r,n,i){for(var o,s,a,l,c,u,p=t.createDocumentFragment(),f=[],d=0,h=e.length;d-1)i&&i.push(o);else if(c=ae(o),s=we(p.appendChild(o),"script"),c&&_e(s),r)for(u=0;o=s[u++];)be.test(o.type||"")&&r.push(o);return p}var Ee=/^([^.]*)(?:\.(.+)|)/;function De(){return!0}function Te(){return!1}function Ce(e,t){return e===function(){try{return y.activeElement}catch(e){}}()==("focus"===t)}function qe(e,t,r,n,i,o){var s,a;if("object"==typeof t){for(a in"string"!=typeof r&&(n=n||r,r=void 0),t)qe(e,a,r,n,t[a],o);return e}if(null==n&&null==i?(i=r,n=r=void 0):null==i&&("string"==typeof r?(i=n,n=void 0):(i=n,n=r,r=void 0)),!1===i)i=Te;else if(!i)return e;return 1===o&&(s=i,i=function(e){return E().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=E.guid++)),e.each((function(){E.event.add(this,t,i,n,r)}))}function ke(e,t,r){r?(Q.set(e,t,!1),E.event.add(e,t,{namespace:!1,handler:function(e){var n,i,o=Q.get(this,t);if(1&e.isTrigger&&this[t]){if(o.length)(E.event.special[t]||{}).delegateType&&e.stopPropagation();else if(o=a.call(arguments),Q.set(this,t,o),n=r(this,t),this[t](),o!==(i=Q.get(this,t))||n?Q.set(this,t,!1):i={},o!==i)return e.stopImmediatePropagation(),e.preventDefault(),i&&i.value}else o.length&&(Q.set(this,t,{value:E.event.trigger(E.extend(o[0],E.Event.prototype),o.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Q.get(e,t)&&E.event.add(e,t,De)}E.event={global:{},add:function(e,t,r,n,i){var o,s,a,l,c,u,p,f,d,h,g,m=Q.get(e);if(K(e))for(r.handler&&(r=(o=r).handler,i=o.selector),i&&E.find.matchesSelector(se,i),r.guid||(r.guid=E.guid++),(l=m.events)||(l=m.events=Object.create(null)),(s=m.handle)||(s=m.handle=function(t){return void 0!==E&&E.event.triggered!==t.type?E.event.dispatch.apply(e,arguments):void 0}),c=(t=(t||"").match(P)||[""]).length;c--;)d=g=(a=Ee.exec(t[c])||[])[1],h=(a[2]||"").split(".").sort(),d&&(p=E.event.special[d]||{},d=(i?p.delegateType:p.bindType)||d,p=E.event.special[d]||{},u=E.extend({type:d,origType:g,data:n,handler:r,guid:r.guid,selector:i,needsContext:i&&E.expr.match.needsContext.test(i),namespace:h.join(".")},o),(f=l[d])||((f=l[d]=[]).delegateCount=0,p.setup&&!1!==p.setup.call(e,n,h,s)||e.addEventListener&&e.addEventListener(d,s)),p.add&&(p.add.call(e,u),u.handler.guid||(u.handler.guid=r.guid)),i?f.splice(f.delegateCount++,0,u):f.push(u),E.event.global[d]=!0)},remove:function(e,t,r,n,i){var o,s,a,l,c,u,p,f,d,h,g,m=Q.hasData(e)&&Q.get(e);if(m&&(l=m.events)){for(c=(t=(t||"").match(P)||[""]).length;c--;)if(d=g=(a=Ee.exec(t[c])||[])[1],h=(a[2]||"").split(".").sort(),d){for(p=E.event.special[d]||{},f=l[d=(n?p.delegateType:p.bindType)||d]||[],a=a[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),s=o=f.length;o--;)u=f[o],!i&&g!==u.origType||r&&r.guid!==u.guid||a&&!a.test(u.namespace)||n&&n!==u.selector&&("**"!==n||!u.selector)||(f.splice(o,1),u.selector&&f.delegateCount--,p.remove&&p.remove.call(e,u));s&&!f.length&&(p.teardown&&!1!==p.teardown.call(e,h,m.handle)||E.removeEvent(e,d,m.handle),delete l[d])}else for(d in l)E.event.remove(e,d+t[c],r,n,!0);E.isEmptyObject(l)&&Q.remove(e,"handle events")}},dispatch:function(e){var t,r,n,i,o,s,a=new Array(arguments.length),l=E.event.fix(e),c=(Q.get(this,"events")||Object.create(null))[l.type]||[],u=E.event.special[l.type]||{};for(a[0]=l,t=1;t=1))for(;c!==this;c=c.parentNode||this)if(1===c.nodeType&&("click"!==e.type||!0!==c.disabled)){for(o=[],s={},r=0;r-1:E.find(i,this,null,[c]).length),s[i]&&o.push(n);o.length&&a.push({elem:c,handlers:o})}return c=this,l\s*$/g;function Oe(e,t){return L(e,"table")&&L(11!==t.nodeType?t:t.firstChild,"tr")&&E(e).children("tbody")[0]||e}function je(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Re(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Be(e,t){var r,n,i,o,s,a;if(1===t.nodeType){if(Q.hasData(e)&&(a=Q.get(e).events))for(i in Q.remove(t,"handle events"),a)for(r=0,n=a[i].length;r1&&"string"==typeof h&&!m.checkClone&&Se.test(h))return e.each((function(i){var o=e.eq(i);g&&(t[0]=h.call(this,i,o.html())),Pe(o,t,r,n)}));if(f&&(o=(i=Ae(t,e[0].ownerDocument,!1,e,n)).firstChild,1===i.childNodes.length&&(i=o),o||n)){for(a=(s=E.map(we(i,"script"),je)).length;p0&&_e(s,!l&&we(e,"script")),a},cleanData:function(e){for(var t,r,n,i=E.event.special,o=0;void 0!==(r=e[o]);o++)if(K(r)){if(t=r[Q.expando]){if(t.events)for(n in t.events)i[n]?E.event.remove(r,n):E.removeEvent(r,n,t.handle);r[Q.expando]=void 0}r[Z.expando]&&(r[Z.expando]=void 0)}}}),E.fn.extend({detach:function(e){return Fe(this,e,!0)},remove:function(e){return Fe(this,e)},text:function(e){return z(this,(function(e){return void 0===e?E.text(this):this.empty().each((function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)}))}),null,e,arguments.length)},append:function(){return Pe(this,arguments,(function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Oe(this,e).appendChild(e)}))},prepend:function(){return Pe(this,arguments,(function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Oe(this,e);t.insertBefore(e,t.firstChild)}}))},before:function(){return Pe(this,arguments,(function(e){this.parentNode&&this.parentNode.insertBefore(e,this)}))},after:function(){return Pe(this,arguments,(function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)}))},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(E.cleanData(we(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map((function(){return E.clone(this,e,t)}))},html:function(e){return z(this,(function(e){var t=this[0]||{},r=0,n=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Le.test(e)&&!ye[(ve.exec(e)||["",""])[1].toLowerCase()]){e=E.htmlPrefilter(e);try{for(;r=0&&(l+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-l-a-.5))||0),l}function it(e,t,r){var n=Ue(e),i=(!m.boxSizingReliable()||r)&&"border-box"===E.css(e,"boxSizing",!1,n),o=i,s=We(e,t,n),a="offset"+t[0].toUpperCase()+t.slice(1);if(He.test(s)){if(!r)return s;s="auto"}return(!m.boxSizingReliable()&&i||!m.reliableTrDimensions()&&L(e,"tr")||"auto"===s||!parseFloat(s)&&"inline"===E.css(e,"display",!1,n))&&e.getClientRects().length&&(i="border-box"===E.css(e,"boxSizing",!1,n),(o=a in e)&&(s=e[a])),(s=parseFloat(s)||0)+nt(e,t,r||(i?"border":"content"),o,n,s)+"px"}function ot(e,t,r,n,i){return new ot.prototype.init(e,t,r,n,i)}E.extend({cssHooks:{opacity:{get:function(e,t){if(t){var r=We(e,"opacity");return""===r?"1":r}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,r,n){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,s,a=X(t),l=Me.test(t),c=e.style;if(l||(t=Qe(a)),s=E.cssHooks[t]||E.cssHooks[a],void 0===r)return s&&"get"in s&&void 0!==(i=s.get(e,!1,n))?i:c[t];"string"===(o=typeof r)&&(i=ie.exec(r))&&i[1]&&(r=ue(e,t,i),o="number"),null!=r&&r==r&&("number"!==o||l||(r+=i&&i[3]||(E.cssNumber[a]?"":"px")),m.clearCloneStyle||""!==r||0!==t.indexOf("background")||(c[t]="inherit"),s&&"set"in s&&void 0===(r=s.set(e,r,n))||(l?c.setProperty(t,r):c[t]=r))}},css:function(e,t,r,n){var i,o,s,a=X(t);return Me.test(t)||(t=Qe(a)),(s=E.cssHooks[t]||E.cssHooks[a])&&"get"in s&&(i=s.get(e,!0,r)),void 0===i&&(i=We(e,t,n)),"normal"===i&&t in tt&&(i=tt[t]),""===r||r?(o=parseFloat(i),!0===r||isFinite(o)?o||0:i):i}}),E.each(["height","width"],(function(e,t){E.cssHooks[t]={get:function(e,r,n){if(r)return!Ze.test(E.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?it(e,t,n):Ve(e,et,(function(){return it(e,t,n)}))},set:function(e,r,n){var i,o=Ue(e),s=!m.scrollboxSize()&&"absolute"===o.position,a=(s||n)&&"border-box"===E.css(e,"boxSizing",!1,o),l=n?nt(e,t,n,a,o):0;return a&&s&&(l-=Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-parseFloat(o[t])-nt(e,t,"border",!1,o)-.5)),l&&(i=ie.exec(r))&&"px"!==(i[3]||"px")&&(e.style[t]=r,r=E.css(e,t)),rt(0,r,l)}}})),E.cssHooks.marginLeft=Ye(m.reliableMarginLeft,(function(e,t){if(t)return(parseFloat(We(e,"marginLeft"))||e.getBoundingClientRect().left-Ve(e,{marginLeft:0},(function(){return e.getBoundingClientRect().left})))+"px"})),E.each({margin:"",padding:"",border:"Width"},(function(e,t){E.cssHooks[e+t]={expand:function(r){for(var n=0,i={},o="string"==typeof r?r.split(" "):[r];n<4;n++)i[e+oe[n]+t]=o[n]||o[n-2]||o[0];return i}},"margin"!==e&&(E.cssHooks[e+t].set=rt)})),E.fn.extend({css:function(e,t){return z(this,(function(e,t,r){var n,i,o={},s=0;if(Array.isArray(t)){for(n=Ue(e),i=t.length;s1)}}),E.Tween=ot,ot.prototype={constructor:ot,init:function(e,t,r,n,i,o){this.elem=e,this.prop=r,this.easing=i||E.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=n,this.unit=o||(E.cssNumber[r]?"":"px")},cur:function(){var e=ot.propHooks[this.prop];return e&&e.get?e.get(this):ot.propHooks._default.get(this)},run:function(e){var t,r=ot.propHooks[this.prop];return this.options.duration?this.pos=t=E.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),r&&r.set?r.set(this):ot.propHooks._default.set(this),this}},ot.prototype.init.prototype=ot.prototype,ot.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=E.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){E.fx.step[e.prop]?E.fx.step[e.prop](e):1!==e.elem.nodeType||!E.cssHooks[e.prop]&&null==e.elem.style[Qe(e.prop)]?e.elem[e.prop]=e.now:E.style(e.elem,e.prop,e.now+e.unit)}}},ot.propHooks.scrollTop=ot.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},E.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},E.fx=ot.prototype.init,E.fx.step={};var st,at,lt=/^(?:toggle|show|hide)$/,ct=/queueHooks$/;function ut(){at&&(!1===y.hidden&&n.requestAnimationFrame?n.requestAnimationFrame(ut):n.setTimeout(ut,E.fx.interval),E.fx.tick())}function pt(){return n.setTimeout((function(){st=void 0})),st=Date.now()}function ft(e,t){var r,n=0,i={height:e};for(t=t?1:0;n<4;n+=2-t)i["margin"+(r=oe[n])]=i["padding"+r]=e;return t&&(i.opacity=i.width=e),i}function dt(e,t,r){for(var n,i=(ht.tweeners[t]||[]).concat(ht.tweeners["*"]),o=0,s=i.length;o1)},removeAttr:function(e){return this.each((function(){E.removeAttr(this,e)}))}}),E.extend({attr:function(e,t,r){var n,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return void 0===e.getAttribute?E.prop(e,t,r):(1===o&&E.isXMLDoc(e)||(i=E.attrHooks[t.toLowerCase()]||(E.expr.match.bool.test(t)?gt:void 0)),void 0!==r?null===r?void E.removeAttr(e,t):i&&"set"in i&&void 0!==(n=i.set(e,r,t))?n:(e.setAttribute(t,r+""),r):i&&"get"in i&&null!==(n=i.get(e,t))?n:null==(n=E.find.attr(e,t))?void 0:n)},attrHooks:{type:{set:function(e,t){if(!m.radioValue&&"radio"===t&&L(e,"input")){var r=e.value;return e.setAttribute("type",t),r&&(e.value=r),t}}}},removeAttr:function(e,t){var r,n=0,i=t&&t.match(P);if(i&&1===e.nodeType)for(;r=i[n++];)e.removeAttribute(r)}}),gt={set:function(e,t,r){return!1===t?E.removeAttr(e,r):e.setAttribute(r,r),r}},E.each(E.expr.match.bool.source.match(/\w+/g),(function(e,t){var r=mt[t]||E.find.attr;mt[t]=function(e,t,n){var i,o,s=t.toLowerCase();return n||(o=mt[s],mt[s]=i,i=null!=r(e,t,n)?s:null,mt[s]=o),i}}));var vt=/^(?:input|select|textarea|button)$/i,bt=/^(?:a|area)$/i;function yt(e){return(e.match(P)||[]).join(" ")}function wt(e){return e.getAttribute&&e.getAttribute("class")||""}function _t(e){return Array.isArray(e)?e:"string"==typeof e&&e.match(P)||[]}E.fn.extend({prop:function(e,t){return z(this,E.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each((function(){delete this[E.propFix[e]||e]}))}}),E.extend({prop:function(e,t,r){var n,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&E.isXMLDoc(e)||(t=E.propFix[t]||t,i=E.propHooks[t]),void 0!==r?i&&"set"in i&&void 0!==(n=i.set(e,r,t))?n:e[t]=r:i&&"get"in i&&null!==(n=i.get(e,t))?n:e[t]},propHooks:{tabIndex:{get:function(e){var t=E.find.attr(e,"tabindex");return t?parseInt(t,10):vt.test(e.nodeName)||bt.test(e.nodeName)&&e.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),m.optSelected||(E.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),E.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],(function(){E.propFix[this.toLowerCase()]=this})),E.fn.extend({addClass:function(e){var t,r,n,i,o,s;return v(e)?this.each((function(t){E(this).addClass(e.call(this,t,wt(this)))})):(t=_t(e)).length?this.each((function(){if(n=wt(this),r=1===this.nodeType&&" "+yt(n)+" "){for(o=0;o-1;)r=r.replace(" "+i+" "," ");s=yt(r),n!==s&&this.setAttribute("class",s)}})):this:this.attr("class","")},toggleClass:function(e,t){var r,n,i,o,s=typeof e,a="string"===s||Array.isArray(e);return v(e)?this.each((function(r){E(this).toggleClass(e.call(this,r,wt(this),t),t)})):"boolean"==typeof t&&a?t?this.addClass(e):this.removeClass(e):(r=_t(e),this.each((function(){if(a)for(o=E(this),i=0;i-1)return!0;return!1}});var xt=/\r/g;E.fn.extend({val:function(e){var t,r,n,i=this[0];return arguments.length?(n=v(e),this.each((function(r){var i;1===this.nodeType&&(null==(i=n?e.call(this,r,E(this).val()):e)?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=E.map(i,(function(e){return null==e?"":e+""}))),(t=E.valHooks[this.type]||E.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))}))):i?(t=E.valHooks[i.type]||E.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(r=t.get(i,"value"))?r:"string"==typeof(r=i.value)?r.replace(xt,""):null==r?"":r:void 0}}),E.extend({valHooks:{option:{get:function(e){var t=E.find.attr(e,"value");return null!=t?t:yt(E.text(e))}},select:{get:function(e){var t,r,n,i=e.options,o=e.selectedIndex,s="select-one"===e.type,a=s?null:[],l=s?o+1:i.length;for(n=o<0?l:s?o:0;n-1)&&(r=!0);return r||(e.selectedIndex=-1),o}}}}),E.each(["radio","checkbox"],(function(){E.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=E.inArray(E(e).val(),t)>-1}},m.checkOn||(E.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})})),m.focusin="onfocusin"in n;var At=/^(?:focusinfocus|focusoutblur)$/,Et=function(e){e.stopPropagation()};E.extend(E.event,{trigger:function(e,t,r,i){var o,s,a,l,c,u,p,f,h=[r||y],g=d.call(e,"type")?e.type:e,m=d.call(e,"namespace")?e.namespace.split("."):[];if(s=f=a=r=r||y,3!==r.nodeType&&8!==r.nodeType&&!At.test(g+E.event.triggered)&&(g.indexOf(".")>-1&&(m=g.split("."),g=m.shift(),m.sort()),c=g.indexOf(":")<0&&"on"+g,(e=e[E.expando]?e:new E.Event(g,"object"==typeof e&&e)).isTrigger=i?2:3,e.namespace=m.join("."),e.rnamespace=e.namespace?new RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,e.result=void 0,e.target||(e.target=r),t=null==t?[e]:E.makeArray(t,[e]),p=E.event.special[g]||{},i||!p.trigger||!1!==p.trigger.apply(r,t))){if(!i&&!p.noBubble&&!b(r)){for(l=p.delegateType||g,At.test(l+g)||(s=s.parentNode);s;s=s.parentNode)h.push(s),a=s;a===(r.ownerDocument||y)&&h.push(a.defaultView||a.parentWindow||n)}for(o=0;(s=h[o++])&&!e.isPropagationStopped();)f=s,e.type=o>1?l:p.bindType||g,(u=(Q.get(s,"events")||Object.create(null))[e.type]&&Q.get(s,"handle"))&&u.apply(s,t),(u=c&&s[c])&&u.apply&&K(s)&&(e.result=u.apply(s,t),!1===e.result&&e.preventDefault());return e.type=g,i||e.isDefaultPrevented()||p._default&&!1!==p._default.apply(h.pop(),t)||!K(r)||c&&v(r[g])&&!b(r)&&((a=r[c])&&(r[c]=null),E.event.triggered=g,e.isPropagationStopped()&&f.addEventListener(g,Et),r[g](),e.isPropagationStopped()&&f.removeEventListener(g,Et),E.event.triggered=void 0,a&&(r[c]=a)),e.result}},simulate:function(e,t,r){var n=E.extend(new E.Event,r,{type:e,isSimulated:!0});E.event.trigger(n,null,t)}}),E.fn.extend({trigger:function(e,t){return this.each((function(){E.event.trigger(e,t,this)}))},triggerHandler:function(e,t){var r=this[0];if(r)return E.event.trigger(e,t,r,!0)}}),m.focusin||E.each({focus:"focusin",blur:"focusout"},(function(e,t){var r=function(e){E.event.simulate(t,e.target,E.event.fix(e))};E.event.special[t]={setup:function(){var n=this.ownerDocument||this.document||this,i=Q.access(n,t);i||n.addEventListener(e,r,!0),Q.access(n,t,(i||0)+1)},teardown:function(){var n=this.ownerDocument||this.document||this,i=Q.access(n,t)-1;i?Q.access(n,t,i):(n.removeEventListener(e,r,!0),Q.remove(n,t))}}}));var Dt=n.location,Tt={guid:Date.now()},Ct=/\?/;E.parseXML=function(e){var t,r;if(!e||"string"!=typeof e)return null;try{t=(new n.DOMParser).parseFromString(e,"text/xml")}catch(e){}return r=t&&t.getElementsByTagName("parsererror")[0],t&&!r||E.error("Invalid XML: "+(r?E.map(r.childNodes,(function(e){return e.textContent})).join("\n"):e)),t};var qt=/\[\]$/,kt=/\r?\n/g,Lt=/^(?:submit|button|image|reset|file)$/i,St=/^(?:input|select|textarea|keygen)/i;function Nt(e,t,r,n){var i;if(Array.isArray(t))E.each(t,(function(t,i){r||qt.test(e)?n(e,i):Nt(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,r,n)}));else if(r||"object"!==x(t))n(e,t);else for(i in t)Nt(e+"["+i+"]",t[i],r,n)}E.param=function(e,t){var r,n=[],i=function(e,t){var r=v(t)?t():t;n[n.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==r?"":r)};if(null==e)return"";if(Array.isArray(e)||e.jquery&&!E.isPlainObject(e))E.each(e,(function(){i(this.name,this.value)}));else for(r in e)Nt(r,e[r],t,i);return n.join("&")},E.fn.extend({serialize:function(){return E.param(this.serializeArray())},serializeArray:function(){return this.map((function(){var e=E.prop(this,"elements");return e?E.makeArray(e):this})).filter((function(){var e=this.type;return this.name&&!E(this).is(":disabled")&&St.test(this.nodeName)&&!Lt.test(e)&&(this.checked||!me.test(e))})).map((function(e,t){var r=E(this).val();return null==r?null:Array.isArray(r)?E.map(r,(function(e){return{name:t.name,value:e.replace(kt,"\r\n")}})):{name:t.name,value:r.replace(kt,"\r\n")}})).get()}});var Ot=/%20/g,jt=/#.*$/,Rt=/([?&])_=[^&]*/,Bt=/^(.*?):[ \t]*([^\r\n]*)$/gm,It=/^(?:GET|HEAD)$/,Pt=/^\/\//,Ft={},Ht={},Mt="*/".concat("*"),Ut=y.createElement("a");function Vt(e){return function(t,r){"string"!=typeof t&&(r=t,t="*");var n,i=0,o=t.toLowerCase().match(P)||[];if(v(r))for(;n=o[i++];)"+"===n[0]?(n=n.slice(1)||"*",(e[n]=e[n]||[]).unshift(r)):(e[n]=e[n]||[]).push(r)}}function $t(e,t,r,n){var i={},o=e===Ht;function s(a){var l;return i[a]=!0,E.each(e[a]||[],(function(e,a){var c=a(t,r,n);return"string"!=typeof c||o||i[c]?o?!(l=c):void 0:(t.dataTypes.unshift(c),s(c),!1)})),l}return s(t.dataTypes[0])||!i["*"]&&s("*")}function zt(e,t){var r,n,i=E.ajaxSettings.flatOptions||{};for(r in t)void 0!==t[r]&&((i[r]?e:n||(n={}))[r]=t[r]);return n&&E.extend(!0,e,n),e}Ut.href=Dt.href,E.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Dt.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(Dt.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Mt,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":E.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?zt(zt(e,E.ajaxSettings),t):zt(E.ajaxSettings,e)},ajaxPrefilter:Vt(Ft),ajaxTransport:Vt(Ht),ajax:function(e,t){"object"==typeof e&&(t=e,e=void 0),t=t||{};var r,i,o,s,a,l,c,u,p,f,d=E.ajaxSetup({},t),h=d.context||d,g=d.context&&(h.nodeType||h.jquery)?E(h):E.event,m=E.Deferred(),v=E.Callbacks("once memory"),b=d.statusCode||{},w={},_={},x="canceled",A={readyState:0,getResponseHeader:function(e){var t;if(c){if(!s)for(s={};t=Bt.exec(o);)s[t[1].toLowerCase()+" "]=(s[t[1].toLowerCase()+" "]||[]).concat(t[2]);t=s[e.toLowerCase()+" "]}return null==t?null:t.join(", ")},getAllResponseHeaders:function(){return c?o:null},setRequestHeader:function(e,t){return null==c&&(e=_[e.toLowerCase()]=_[e.toLowerCase()]||e,w[e]=t),this},overrideMimeType:function(e){return null==c&&(d.mimeType=e),this},statusCode:function(e){var t;if(e)if(c)A.always(e[A.status]);else for(t in e)b[t]=[b[t],e[t]];return this},abort:function(e){var t=e||x;return r&&r.abort(t),D(0,t),this}};if(m.promise(A),d.url=((e||d.url||Dt.href)+"").replace(Pt,Dt.protocol+"//"),d.type=t.method||t.type||d.method||d.type,d.dataTypes=(d.dataType||"*").toLowerCase().match(P)||[""],null==d.crossDomain){l=y.createElement("a");try{l.href=d.url,l.href=l.href,d.crossDomain=Ut.protocol+"//"+Ut.host!=l.protocol+"//"+l.host}catch(e){d.crossDomain=!0}}if(d.data&&d.processData&&"string"!=typeof d.data&&(d.data=E.param(d.data,d.traditional)),$t(Ft,d,t,A),c)return A;for(p in(u=E.event&&d.global)&&0==E.active++&&E.event.trigger("ajaxStart"),d.type=d.type.toUpperCase(),d.hasContent=!It.test(d.type),i=d.url.replace(jt,""),d.hasContent?d.data&&d.processData&&0===(d.contentType||"").indexOf("application/x-www-form-urlencoded")&&(d.data=d.data.replace(Ot,"+")):(f=d.url.slice(i.length),d.data&&(d.processData||"string"==typeof d.data)&&(i+=(Ct.test(i)?"&":"?")+d.data,delete d.data),!1===d.cache&&(i=i.replace(Rt,"$1"),f=(Ct.test(i)?"&":"?")+"_="+Tt.guid+++f),d.url=i+f),d.ifModified&&(E.lastModified[i]&&A.setRequestHeader("If-Modified-Since",E.lastModified[i]),E.etag[i]&&A.setRequestHeader("If-None-Match",E.etag[i])),(d.data&&d.hasContent&&!1!==d.contentType||t.contentType)&&A.setRequestHeader("Content-Type",d.contentType),A.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+("*"!==d.dataTypes[0]?", "+Mt+"; q=0.01":""):d.accepts["*"]),d.headers)A.setRequestHeader(p,d.headers[p]);if(d.beforeSend&&(!1===d.beforeSend.call(h,A,d)||c))return A.abort();if(x="abort",v.add(d.complete),A.done(d.success),A.fail(d.error),r=$t(Ht,d,t,A)){if(A.readyState=1,u&&g.trigger("ajaxSend",[A,d]),c)return A;d.async&&d.timeout>0&&(a=n.setTimeout((function(){A.abort("timeout")}),d.timeout));try{c=!1,r.send(w,D)}catch(e){if(c)throw e;D(-1,e)}}else D(-1,"No Transport");function D(e,t,s,l){var p,f,y,w,_,x=t;c||(c=!0,a&&n.clearTimeout(a),r=void 0,o=l||"",A.readyState=e>0?4:0,p=e>=200&&e<300||304===e,s&&(w=function(e,t,r){for(var n,i,o,s,a=e.contents,l=e.dataTypes;"*"===l[0];)l.shift(),void 0===n&&(n=e.mimeType||t.getResponseHeader("Content-Type"));if(n)for(i in a)if(a[i]&&a[i].test(n)){l.unshift(i);break}if(l[0]in r)o=l[0];else{for(i in r){if(!l[0]||e.converters[i+" "+l[0]]){o=i;break}s||(s=i)}o=o||s}if(o)return o!==l[0]&&l.unshift(o),r[o]}(d,A,s)),!p&&E.inArray("script",d.dataTypes)>-1&&E.inArray("json",d.dataTypes)<0&&(d.converters["text script"]=function(){}),w=function(e,t,r,n){var i,o,s,a,l,c={},u=e.dataTypes.slice();if(u[1])for(s in e.converters)c[s.toLowerCase()]=e.converters[s];for(o=u.shift();o;)if(e.responseFields[o]&&(r[e.responseFields[o]]=t),!l&&n&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),l=o,o=u.shift())if("*"===o)o=l;else if("*"!==l&&l!==o){if(!(s=c[l+" "+o]||c["* "+o]))for(i in c)if((a=i.split(" "))[1]===o&&(s=c[l+" "+a[0]]||c["* "+a[0]])){!0===s?s=c[i]:!0!==c[i]&&(o=a[0],u.unshift(a[1]));break}if(!0!==s)if(s&&e.throws)t=s(t);else try{t=s(t)}catch(e){return{state:"parsererror",error:s?e:"No conversion from "+l+" to "+o}}}return{state:"success",data:t}}(d,w,A,p),p?(d.ifModified&&((_=A.getResponseHeader("Last-Modified"))&&(E.lastModified[i]=_),(_=A.getResponseHeader("etag"))&&(E.etag[i]=_)),204===e||"HEAD"===d.type?x="nocontent":304===e?x="notmodified":(x=w.state,f=w.data,p=!(y=w.error))):(y=x,!e&&x||(x="error",e<0&&(e=0))),A.status=e,A.statusText=(t||x)+"",p?m.resolveWith(h,[f,x,A]):m.rejectWith(h,[A,x,y]),A.statusCode(b),b=void 0,u&&g.trigger(p?"ajaxSuccess":"ajaxError",[A,d,p?f:y]),v.fireWith(h,[A,x]),u&&(g.trigger("ajaxComplete",[A,d]),--E.active||E.event.trigger("ajaxStop")))}return A},getJSON:function(e,t,r){return E.get(e,t,r,"json")},getScript:function(e,t){return E.get(e,void 0,t,"script")}}),E.each(["get","post"],(function(e,t){E[t]=function(e,r,n,i){return v(r)&&(i=i||n,n=r,r=void 0),E.ajax(E.extend({url:e,type:t,dataType:i,data:r,success:n},E.isPlainObject(e)&&e))}})),E.ajaxPrefilter((function(e){var t;for(t in e.headers)"content-type"===t.toLowerCase()&&(e.contentType=e.headers[t]||"")})),E._evalUrl=function(e,t,r){return E.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,converters:{"text script":function(){}},dataFilter:function(e){E.globalEval(e,t,r)}})},E.fn.extend({wrapAll:function(e){var t;return this[0]&&(v(e)&&(e=e.call(this[0])),t=E(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map((function(){for(var e=this;e.firstElementChild;)e=e.firstElementChild;return e})).append(this)),this},wrapInner:function(e){return v(e)?this.each((function(t){E(this).wrapInner(e.call(this,t))})):this.each((function(){var t=E(this),r=t.contents();r.length?r.wrapAll(e):t.append(e)}))},wrap:function(e){var t=v(e);return this.each((function(r){E(this).wrapAll(t?e.call(this,r):e)}))},unwrap:function(e){return this.parent(e).not("body").each((function(){E(this).replaceWith(this.childNodes)})),this}}),E.expr.pseudos.hidden=function(e){return!E.expr.pseudos.visible(e)},E.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},E.ajaxSettings.xhr=function(){try{return new n.XMLHttpRequest}catch(e){}};var Gt={0:200,1223:204},Wt=E.ajaxSettings.xhr();m.cors=!!Wt&&"withCredentials"in Wt,m.ajax=Wt=!!Wt,E.ajaxTransport((function(e){var t,r;if(m.cors||Wt&&!e.crossDomain)return{send:function(i,o){var s,a=e.xhr();if(a.open(e.type,e.url,e.async,e.username,e.password),e.xhrFields)for(s in e.xhrFields)a[s]=e.xhrFields[s];for(s in e.mimeType&&a.overrideMimeType&&a.overrideMimeType(e.mimeType),e.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest"),i)a.setRequestHeader(s,i[s]);t=function(e){return function(){t&&(t=r=a.onload=a.onerror=a.onabort=a.ontimeout=a.onreadystatechange=null,"abort"===e?a.abort():"error"===e?"number"!=typeof a.status?o(0,"error"):o(a.status,a.statusText):o(Gt[a.status]||a.status,a.statusText,"text"!==(a.responseType||"text")||"string"!=typeof a.responseText?{binary:a.response}:{text:a.responseText},a.getAllResponseHeaders()))}},a.onload=t(),r=a.onerror=a.ontimeout=t("error"),void 0!==a.onabort?a.onabort=r:a.onreadystatechange=function(){4===a.readyState&&n.setTimeout((function(){t&&r()}))},t=t("abort");try{a.send(e.hasContent&&e.data||null)}catch(e){if(t)throw e}},abort:function(){t&&t()}}})),E.ajaxPrefilter((function(e){e.crossDomain&&(e.contents.script=!1)})),E.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return E.globalEval(e),e}}}),E.ajaxPrefilter("script",(function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")})),E.ajaxTransport("script",(function(e){var t,r;if(e.crossDomain||e.scriptAttrs)return{send:function(n,i){t=E("