diff --git a/.cmake-format.py b/.cmake-format.py new file mode 100644 index 000000000..a9489146f --- /dev/null +++ b/.cmake-format.py @@ -0,0 +1,21 @@ +# If a statement is wrapped to more than one line, than dangle the closing +# parenthesis on it's own line. +dangle_parens = True +dangle_align = 'child' + +# If true, the parsers may infer whether or not an argument list is sortable +# (without annotation). +autosort = True + +# How wide to allow formatted cmake files +line_width = 100 + +additional_commands = { + "target_sources": { + "kwargs": { + "PUBLIC": "*", + "PRIVATE": "*", + "INTERFACE": "*", + } + }, +} diff --git a/.editorconfig b/.editorconfig index 8acdfc7e9..581051f83 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,3 +12,6 @@ indent_style = space indent_size = 4 tab_width = 4 max_line_length = 100 + +[lang/po/*.po] +end_of_line = crlf diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..70b033a98 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +lang/po/*.po text eol=crlf diff=po +lang/po/*.pot text diff=po diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 196d5756d..f84bbc9e2 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -6,14 +6,17 @@ labels: bug assignees: '' --- - + **Printer type** - [e.g. MK3S, MK3, MK2.5S, MK2.5, MK2S, MK2] -**Printer firmware version**- [e.g. 3.8.1, 3.8.1-RC1, ...] +**Printer firmware version** - [e.g. 3.8.1, 3.8.1-RC1, ...] -**MMU Upgrade** - [e.g. MMU2S, MMU2, MMU1] -**MMU upgrade firmware version [e.g. 1.0.6, 1.0.6-RC2, ...] +**MMU upgrade** - [e.g. MMU2S, MMU2, MMU1] +**MMU upgrade firmware version** - [e.g. 1.0.6, 1.0.6-RC2, ...] + +**SD card or USB/Octoprint** + Please let us know if you print via SD card or USB/Octoprint **Describe the bug** A clear and concise description of what the bug is. diff --git a/.github/ISSUE_TEMPLATE/community.md b/.github/ISSUE_TEMPLATE/community.md new file mode 100644 index 000000000..a2d69a2d3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/community.md @@ -0,0 +1,17 @@ +--- +name: Community +about: Related to "Community made" features +title: "[Community made] " +labels: community_made +assignees: '' + +--- + +Prusa Research will NOT follow up these issues! +The maintainers of the "Community made" feature should/will react. + +Please, before you create a new "Community made" ticket, please make sure you searched in open and closed issues and couldn't find anything that matches. + +**Which Community made feature do you want to address?** + +**What is your request/question/suggestion?** diff --git a/.github/travis/cmake-build.sh b/.github/travis/cmake-build.sh new file mode 100755 index 000000000..734e03d74 --- /dev/null +++ b/.github/travis/cmake-build.sh @@ -0,0 +1,10 @@ +#!/bin/sh +set -xe +rm -rf build +mkdir build +cd build +cmake .. \ + -DCMAKE_TOOLCHAIN_FILE="../cmake/AvrGcc.cmake" \ + -DCMAKE_BUILD_TYPE=Release \ + -G Ninja +ninja ALL_FIRMWARE diff --git a/.github/travis/cmake-lang.sh b/.github/travis/cmake-lang.sh new file mode 100755 index 000000000..38404d53b --- /dev/null +++ b/.github/travis/cmake-lang.sh @@ -0,0 +1,12 @@ +#!/bin/sh +set -xe +rm -rf build +mkdir build +cd build +cmake .. \ + -DCMAKE_TOOLCHAIN_FILE="../cmake/AvrGcc.cmake" \ + -DCMAKE_BUILD_TYPE=Release \ + -G Ninja + +# ignore all failures in order to show as much output as possible +ninja -k0 check_lang || true diff --git a/.github/travis/cmake-test.sh b/.github/travis/cmake-test.sh new file mode 100755 index 000000000..ec41633b3 --- /dev/null +++ b/.github/travis/cmake-test.sh @@ -0,0 +1,8 @@ +#!/bin/sh +set -xe +rm -rf build +mkdir build +cd build +cmake .. -G Ninja +ninja tests +ctest diff --git a/.github/travis/legacy-build.sh b/.github/travis/legacy-build.sh new file mode 100755 index 000000000..063ef62ec --- /dev/null +++ b/.github/travis/legacy-build.sh @@ -0,0 +1,22 @@ +#!/bin/sh +set -xe +cp Firmware/variants/1_75mm_MK3S-EINSy10a-E3Dv6full.h Firmware/Configuration_prusa.h +bash -x build.sh || { echo "1_75mm_MK3S-EINSy10a-E3Dv6full variant failed" && false; } +bash -x build.sh EN_FARM || { echo "1_75mm_MK3S-EINSy10a-E3Dv6full EN_FARM failed" && false; } +rm Firmware/Configuration_prusa.h +cp Firmware/variants/1_75mm_MK3-EINSy10a-E3Dv6full.h Firmware/Configuration_prusa.h +bash -x build.sh || { echo "1_75mm_MK3-EINSy10a-E3Dv6full variant failed" && false; } +bash -x build.sh EN_FARM || { echo "1_75mm_MK3-EINSy10a-E3Dv6full EN_FARM failed" && false; } +rm Firmware/Configuration_prusa.h +cp Firmware/variants/1_75mm_MK25S-RAMBo13a-E3Dv6full.h Firmware/Configuration_prusa.h +bash -x build.sh || { echo "1_75mm_MK25S-RAMBo13a-E3Dv6full variant failed" && false; } +rm Firmware/Configuration_prusa.h +cp Firmware/variants/1_75mm_MK25S-RAMBo10a-E3Dv6full.h Firmware/Configuration_prusa.h +bash -x build.sh || { echo "1_75mm_MK25S-RAMBo10a-E3Dv6full variant failed" && false; } +rm Firmware/Configuration_prusa.h +cp Firmware/variants/1_75mm_MK25-RAMBo13a-E3Dv6full.h Firmware/Configuration_prusa.h +bash -x build.sh || { echo "1_75mm_MK25-RAMBo13a-E3Dv6full variant failed" && false; } +rm Firmware/Configuration_prusa.h +cp Firmware/variants/1_75mm_MK25-RAMBo10a-E3Dv6full.h Firmware/Configuration_prusa.h +bash -x build.sh || { echo "1_75mm_MK25-RAMBo10a-E3Dv6full variant failed" && false; } +rm Firmware/Configuration_prusa.h diff --git a/.github/workflows/pr-size.sh b/.github/workflows/pr-size.sh new file mode 100755 index 000000000..8187151e2 --- /dev/null +++ b/.github/workflows/pr-size.sh @@ -0,0 +1,41 @@ +#!/bin/sh +MESSAGE=$1 +BASE_DIR=$2 +PR_DIR=$3 +shift 3 + +# this assumes we're running from the repository root +AVR_SIZE=$(echo .dependencies/avr-gcc-*/bin/avr-size) +test -x "$AVR_SIZE" || exit 2 + +avr_size() +{ + "$AVR_SIZE" --mcu=atmega2560 -C "$@" +} + +avr_flash() +{ + avr_size "$@" | sed -ne 's/^Program: *\([0-9]\+\).*/\1/p' +} + +avr_ram() +{ + avr_size "$@" | sed -ne 's/^Data: *\([0-9]\+\).*/\1/p' +} + +echo "This PR will consume:" > "$MESSAGE" +for TARGET in $@ +do + base_bin=$(echo ${BASE_DIR}/build_gen/*/$TARGET) + base_flash=$(avr_flash "$base_bin") + base_ram=$(avr_ram "$base_bin") + + pr_bin=$(echo ${PR_DIR}/build_gen/*/$TARGET) + pr_flash=$(avr_flash "$pr_bin") + pr_ram=$(avr_ram "$pr_bin") + + flash_d=$(($pr_flash - $base_flash)) + ram_d=$(($pr_ram - $base_ram)) + + echo "- \`$TARGET\`: ${flash_d}b of flash, ${ram_d}b of ram" >> "$MESSAGE" +done diff --git a/.github/workflows/pr-size.yml b/.github/workflows/pr-size.yml new file mode 100644 index 000000000..1440c2a9c --- /dev/null +++ b/.github/workflows/pr-size.yml @@ -0,0 +1,66 @@ +name: pr-size + +on: + pull_request: + branches: [ MK3, MK3_* ] + +env: + TARGETS: "MK3S-EINSy10a_ENGLISH MK3-EINSy10a_ENGLISH" + +jobs: + build: + runs-on: ubuntu-latest + + steps: + + # setup base required dependencies + - name: Setup dependencies + run: | + sudo apt-get install cmake ninja-build python3-pyelftools python3-regex python3-polib + + # build the PR branch + - name: Checkout PR + uses: actions/checkout@v3 + + - name: Setup build dependencies + run: | + ./utils/bootstrap.py + + - name: Build PR + run: | + rm -rf build-pr + mkdir build-pr + cd build-pr + cmake .. -DCMAKE_TOOLCHAIN_FILE="../cmake/AvrGcc.cmake" -DCMAKE_BUILD_TYPE=Release -G Ninja + ninja $TARGETS + + # save pr-size for later use + - name: Save pr-size from PR + run: | + cp -f ./.github/workflows/pr-size.sh build-pr + + # build the base branch + - name: Checkout base + uses: actions/checkout@v3 + with: + clean: false + ref: ${{ github.event.pull_request.base.ref }} + + - name: Build base + run: | + rm -rf build-base + mkdir build-base + cd build-base + cmake .. -DCMAKE_TOOLCHAIN_FILE="../cmake/AvrGcc.cmake" -DCMAKE_BUILD_TYPE=Release -G Ninja + ninja $TARGETS + + # extract/show build differences + - name: Calculate binary changes + run: | + rm -rf build-changes + ./build-pr/pr-size.sh build-changes build-base build-pr $TARGETS + + - name: Add PR Comment + uses: mshick/add-pr-comment@v2 + with: + message-path: build-changes diff --git a/.gitignore b/.gitignore index dec628d07..a027eb0e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,54 +1,31 @@ -.settings -.project -.cproject -Debug -Firmware/Configuration_prusa.h -Firmware/Doc -/Firmware/.vs/Firmware/v14 -/Firmware/__vm -/Firmware/Firmware.sln -/Firmware/Firmware.vcxproj -/Firmware/Firmware.vcxproj.filters -/Firmware/Firmware - Shortcut.lnk -/Firmware/variants/1_75mm_MK3-MMU-EINSy10a-E3Dv6full.h.bak -/Firmware/Marlin_main.cpp~RF12cfae7.TMP -/Firmware/variants/1_75mm_MK3-EINSy10a-E3Dv6full.h.bak -/html -/latex -/Doxyfile -/Firmware/builds/1_75mm_MK3-EINY04-E3Dv6full -/Firmware/Configuration_prusa.h.bak -/Firmware/Configuration_prusa_backup.h -/Firmware/ultralcd_implementation_hitachi_HD44780.h.bak -/Firmware/ultralcd.cpp.bak -/Firmware/temperature.cpp.bak -/Firmware/pins.h.bak -/Firmware/Marlin_main.cpp.bak -/Firmware/language_pl.h.bak -/Firmware/language_it.h.bak -/Firmware/language_es.h.bak -/Firmware/language_en.h.bak -/Firmware/language_de.h.bak -/Firmware/language_cz.h.bak -/Firmware/variants/1_75mm_MK2-MultiMaterial-RAMBo13a-E3Dv6full.h -/Firmware/variants/1_75mm_MK2-MultiMaterial-RAMBo10a-E3Dv6full.h -/Firmware/variants/1_75mm_MK2-EINY01-E3Dv6full.h.bak -/Firmware/variants/1_75mm_MK1-RAMBo13a-E3Dv6full.h -/Firmware/variants/1_75mm_MK1-RAMBo10a-E3Dv6full.h -/lang/*.bin -/lang/*.hex -/lang/*.dat -/lang/*.tmp -/lang/*.out -/lang/not_tran.txt -/lang/not_used.txt -/lang/progmem1.chr -/lang/progmem1.lss -/lang/progmem1.txt -/lang/progmem1.var -/lang/text.sym -/lang/textaddr.txt +# IDE data +/.settings +/.project +/.cproject + +# cmake +/build/ +/build_gen/ +/.dependencies +/compile_commands.json + +# Temporary configuration +/Firmware/Configuration_prusa.h + +# Temporary language files +/lang/po/*.mo +/lang/tmp/ +/lang/Firmware-intl.hex +/lang/Firmware-intl-en_*.hex +/lang/*.map + +# Temporary files and directories +*[~#] +*.tmp +*.bak +.DS_Store +__pycache__ + +# Generated files /build-env/ -/Firmware/Firmware.vcxproj -/Firmware/Configuration_prusa_bckp.h -/Firmware/variants/printers.h +/Firmware/Doc/ diff --git a/.travis.yml b/.travis.yml index 4d9039dc0..768584a49 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,35 +1,55 @@ -dist: trusty +dist: focal +language: minimal + +cache: + directories: + # cmake project dependencies + - .dependencies/ + # legacy PF-build dependencies + - ./../PF-build-env/ + before_install: - - sudo apt-get install -y ninja-build - # Arduino IDE adds a lot of noise caused by network traffic, trying to firewall it off + # Prepare the dependencies for the old build environment + - sudo apt-get install -y python3-polib python3-pyelftools python3-regex + + # Undo whatever *GARBAGE* travis is doing with python and restore the system version + - mkdir -p .dependencies/python3 + - ln -sf /usr/bin/python3 .dependencies/python3/python3 + - PATH=$PWD/.dependencies/python3:$PATH + + # Bootstrap cmake/ninja for the new build environment + - ./utils/bootstrap.py + - PATH=$(./utils/bootstrap.py --print-dependency-directory "cmake")/bin:$PATH + - PATH=$(./utils/bootstrap.py --print-dependency-directory "ninja"):$PATH + + # Arduino IDE adds a lot of noise caused by network traffic, firewall it off - sudo iptables -P INPUT DROP - sudo iptables -P FORWARD DROP - sudo iptables -P OUTPUT ACCEPT - sudo iptables -A INPUT -i lo -j ACCEPT - sudo iptables -A OUTPUT -o lo -j ACCEPT - sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT -script: - - bash -x test.sh - - cp Firmware/variants/1_75mm_MK3S-EINSy10a-E3Dv6full.h Firmware/Configuration_prusa.h - - bash -x build.sh || { echo "1_75mm_MK3S-EINSy10a-E3Dv6full variant failed" && false; } - - rm Firmware/Configuration_prusa.h - - cp Firmware/variants/1_75mm_MK3-EINSy10a-E3Dv6full.h Firmware/Configuration_prusa.h - - bash -x build.sh || { echo "1_75mm_MK3-EINSy10a-E3Dv6full variant failed" && false; } - - rm Firmware/Configuration_prusa.h - - cp Firmware/variants/1_75mm_MK25S-RAMBo13a-E3Dv6full.h Firmware/Configuration_prusa.h - - bash -x build.sh || { echo "1_75mm_MK25S-RAMBo13a-E3Dv6full variant failed" && false; } - - rm Firmware/Configuration_prusa.h - - cp Firmware/variants/1_75mm_MK25S-RAMBo10a-E3Dv6full.h Firmware/Configuration_prusa.h - - bash -x build.sh || { echo "1_75mm_MK25S-RAMBo10a-E3Dv6full variant failed" && false; } - - rm Firmware/Configuration_prusa.h - - cp Firmware/variants/1_75mm_MK25-RAMBo13a-E3Dv6full.h Firmware/Configuration_prusa.h - - bash -x build.sh || { echo "1_75mm_MK25-RAMBo13a-E3Dv6full variant failed" && false; } - - rm Firmware/Configuration_prusa.h - - cp Firmware/variants/1_75mm_MK25-RAMBo10a-E3Dv6full.h Firmware/Configuration_prusa.h - - bash -x build.sh || { echo "1_75mm_MK25-RAMBo10a-E3Dv6full variant failed" && false; } - - rm Firmware/Configuration_prusa.h - - cp Firmware/variants/1_75mm_MK2-RAMBo13a-E3Dv6full.h Firmware/Configuration_prusa.h - - bash -x build.sh || { echo "1_75mm_MK2-RAMBo13a-E3Dv6full variant failed" && false; } - - rm Firmware/Configuration_prusa.h - - cp Firmware/variants/1_75mm_MK2-RAMBo10a-E3Dv6full.h Firmware/Configuration_prusa.h - - bash -x build.sh || { echo "1_75mm_MK2-RAMBo10a-E3Dv6full variant failed" && false; } \ No newline at end of file + +jobs: + include: + # legacy build.sh environment + - stage: legacy + script: ./.github/travis/legacy-build.sh + + # cmake-based build + - stage: cmake + script: ./.github/travis/cmake-build.sh + + # cmake tests + - stage: tests + script: ./.github/travis/cmake-test.sh + + # language checks + - stage: lang + script: ./.github/travis/cmake-lang.sh + +stages: + - cmake + - lang + - legacy + - tests diff --git a/.vscode/cmake-kits.json b/.vscode/cmake-kits.json new file mode 100644 index 000000000..8e3bae6d6 --- /dev/null +++ b/.vscode/cmake-kits.json @@ -0,0 +1,10 @@ +[ + { + "name": "avr-gcc", + "toolchainFile": "${workspaceFolder}/cmake/AvrGcc.cmake", + "cmakeSettings": { + "CMAKE_MAKE_PROGRAM": "${workspaceFolder}/.dependencies/ninja-1.10.2/ninja", + "CMAKE_BUILD_TYPE": "Release" + } + } +] diff --git a/.vscode/cmake-variants.yaml b/.vscode/cmake-variants.yaml new file mode 100644 index 000000000..70f8df827 --- /dev/null +++ b/.vscode/cmake-variants.yaml @@ -0,0 +1,11 @@ +buildType: + default: debug + choices: + debug: + short: Debug + long: Emit debug information + buildType: Debug + release: + short: Release + long: Optimize generated code + buildType: Release diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..05a5dad05 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "cmake.configureOnOpen": true, + "cmake.copyCompileCommands": "${workspaceFolder}/compile_commands.json", + "cmake.cmakePath": "${workspaceFolder}/.dependencies/cmake-3.22.5/bin/cmake", + "cmake.generator": "Ninja", + "files.insertFinalNewline": true, + "files.associations": { + "xlocale": "cpp" + } +} diff --git a/CMakeLists.txt b/CMakeLists.txt index 4ca1d95be..5f4854434 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,24 +1,519 @@ -cmake_minimum_required(VERSION 3.1) +cmake_minimum_required(VERSION 3.19) +include(cmake/Utilities.cmake) +include(cmake/GetGitRevisionDescription.cmake) +include(cmake/ReproducibleBuild.cmake) -set (CMAKE_CXX_STANDARD 11) +set(PROJECT_VERSION_SUFFIX + "" + CACHE + STRING + "Full version suffix to be shown on the info screen in settings (e.g. full_version=4.0.3-BETA+1035.PR111.B4, suffix=-BETA+1035.PR111.B4). Defaults to '+..' if set to ''." + ) +set(PROJECT_VERSION_SUFFIX_SHORT + "" + CACHE + STRING + "Short version suffix to be shown on splash screen. Defaults to '+' if set to ''." + ) +set(BUILD_NUMBER + "" + CACHE STRING "Build number of the firmware. Resolved automatically if not specified." + ) -project(cmake_test) +include(cmake/ProjectVersion.cmake) +resolve_version_variables() -# Prepare "Catch" library for other executables -set(CATCH_INCLUDE_DIR Catch2) -add_library(Catch INTERFACE) -target_include_directories(Catch INTERFACE ${CATCH_INCLUDE_DIR}) +set(PROJECT_VERSION_FLAVOUR + "" + CACHE STRING "Firmware flavour to build - DEBUG, DEVEL, APLHA, BETA or RC" + ) +set(PROJECT_VERSION_FLAVOUR_REVISION + "" + CACHE STRING "Firmware flavour version, e.g. 1 for RC1, etc" + ) -# Make test executable -set(TEST_SOURCES - Tests/tests.cpp - Tests/Example_test.cpp - Tests/Timer_test.cpp - Tests/AutoDeplete_test.cpp - Tests/PrusaStatistics_test.cpp - Firmware/Timer.cpp - Firmware/AutoDeplete.cpp -) -add_executable(tests ${TEST_SOURCES}) -target_include_directories(tests PRIVATE Tests) -target_link_libraries(tests Catch) +if(NOT PROJECT_VERSION_FLAVOUR STREQUAL "") + set(PROJECT_VERSION "${PROJECT_VERSION}-${PROJECT_VERSION_FLAVOUR}") + add_compile_definitions(FW_FLAVOR=${PROJECT_VERSION_FLAVOUR}) + if(NOT PROJECT_VERSION_FLAVOUR_REVISION STREQUAL "") + set(PROJECT_VERSION "${PROJECT_VERSION}${PROJECT_VERSION_FLAVOUR_REVISION}") + add_compile_definitions(FW_FLAVERSION=${PROJECT_VERSION_FLAVOUR_REVISION}) + endif() +endif() + +# Inform user about the resolved settings +message(STATUS "Project version: ${PROJECT_VERSION}") +message( + STATUS "Project version with short suffix: ${PROJECT_VERSION}${PROJECT_VERSION_SUFFIX_SHORT}" + ) + +set(FN_PREFIX "FW${PROJECT_VERSION}${PROJECT_VERSION_SUFFIX_SHORT}") + +# Language configuration +set(MAIN_LANGUAGES + cs de es fr it pl + CACHE STRING "The list of 'main' languages to be included, in the correct order" + ) +set(COMMUNITY_LANGUAGES + nl + ro + hu + hr + sk + sv + no + CACHE STRING "The list of community languages to be included, in the correct order" + ) +set(SELECTED_LANGUAGES ${MAIN_LANGUAGES} ${COMMUNITY_LANGUAGES}) + +get_dependency_directory(prusa3dboards PRUSA_BOARDS_DIR) +project(Prusa-Firmware) +add_subdirectory(lib) + +# Get LANG_MAX_SIZE from sources +file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/Firmware/config.h MAX_SIZE_LINE + REGEX "^#define \+LANG_SIZE_RESERVED \+" + ) +string(REGEX MATCH "0x[0-9]+" MAX_SIZE_HEX "${MAX_SIZE_LINE}") +math(EXPR LANG_MAX_SIZE "${MAX_SIZE_HEX}" OUTPUT_FORMAT DECIMAL) +message("Language maximum size (from config.h): ${LANG_MAX_SIZE} bytes") + +# Ditto, this in xflash_layout.h but needs invocation of the preprocessor... :-/ +set(LANG_BIN_MAX 249856) + +# Check GCC Version +get_recommended_gcc_version(RECOMMENDED_TOOLCHAIN_VERSION) +if(CMAKE_CROSSCOMPILING AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL + ${RECOMMENDED_TOOLCHAIN_VERSION} + ) + message(WARNING "Recommended AVR toolchain is ${RECOMMENDED_TOOLCHAIN_VERSION}" + ", but you have ${CMAKE_CXX_COMPILER_VERSION}" + ) + +elseif(NOT CMAKE_CROSSCOMPILING AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + message( + WARNING + "Recommended compiler for host tools and unittests is GCC, you have ${CMAKE_CXX_COMPILER_ID}." + ) +endif() + +# append custom C/C++ flags +if(CUSTOM_COMPILE_OPTIONS) + string(REPLACE " " ";" CUSTOM_COMPILE_OPTIONS "${CUSTOM_COMPILE_OPTIONS}") + add_compile_options(${CUSTOM_COMPILE_OPTIONS}) +endif() + +# +# Global Compiler & Linker Configuration +# + +# enable warnings +add_compile_options(-Wall -Wextra -Wno-expansion-to-defined -Wsign-compare) + +# default standards for all targets +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +# support _DEBUG macro (some code uses to recognize debug builds) +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + add_compile_definitions(_DEBUG) +endif() + +# +# Firmware - get file lists. +# +set(FW_SOURCES + adc.cpp + backlight.cpp + BlinkM.cpp + bootapp.c + cardreader.cpp + cmdqueue.cpp + Configuration.cpp + ConfigurationStore.cpp + Dcodes.cpp + eeprom.cpp + fancheck.cpp + Filament_sensor.cpp + first_lay_cal.cpp + heatbed_pwm.cpp + la10compat.cpp + language.c + lcd.cpp + Marlin_main.cpp + MarlinSerial.cpp + menu.cpp + mesh_bed_calibration.cpp + mesh_bed_leveling.cpp + messages.cpp + mmu2.cpp + mmu2_crc.cpp + mmu2_error_converter.cpp + mmu2_fsensor.cpp + mmu2_log.cpp + mmu2_marlin1.cpp + mmu2_power.cpp + mmu2_progress_converter.cpp + mmu2_protocol.cpp + mmu2_protocol_logic.cpp + mmu2_reporting.cpp + mmu2_serial.cpp + motion_control.cpp + optiboot_xflash.cpp + pat9125.cpp + planner.cpp + Prusa_farm.cpp + qr_solve.cpp + rbuf.c + Sd2Card.cpp + SdBaseFile.cpp + SdFatUtil.cpp + SdFile.cpp + SdVolume.cpp + Servo.cpp + sm4.c + sound.cpp + speed_lookuptable.cpp + spi.c + SpoolJoin.cpp + stepper.cpp + swi2c.c + Tcodes.cpp + temperature.cpp + timer02.c + Timer.cpp + tmc2130.cpp + tone04.c + twi.cpp + uart2.c + ultralcd.cpp + util.cpp + vector_3.cpp + xflash.c + xflash_dump.cpp + xyzcal.cpp + ) +list(TRANSFORM FW_SOURCES PREPEND ${CMAKE_CURRENT_SOURCE_DIR}/Firmware/) + +set(AVR_SOURCES + wiring_digital.c + WInterrupts.c + wiring_pulse.c + hooks.c + wiring.c + wiring_analog.c + wiring_shift.c + CDC.cpp + PluggableUSB.cpp + HardwareSerial.cpp + HardwareSerial0.cpp + HardwareSerial1.cpp + HardwareSerial3.cpp + IPAddress.cpp + HardwareSerial2.cpp + Print.cpp + Stream.cpp + Tone.cpp + USBCore.cpp + WMath.cpp + WString.cpp + abi.cpp + main.cpp + ) +list(TRANSFORM AVR_SOURCES PREPEND ${PRUSA_BOARDS_DIR}/cores/prusa_einsy_rambo/) + +# +# Target configuration +# +if(CMAKE_CROSSCOMPILING) + # TODO: get date from the last git commit to set as epoch + set_source_epoch(0) + + # default optimization flags + set(CMAKE_CXX_FLAGS_DEBUG "-Og -g") + set(CMAKE_CXX_FLAGS_RELEASE "-Os -g -DNDEBUG") + set(CMAKE_C_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG}) + set(CMAKE_C_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE}) + + # mcu and target-related settings + add_compile_options( + -mmcu=atmega2560 -DF_CPU=16000000L -DARDUINO=10819 -DARDUINO_AVR_PRUSA_EINSY_RAMBO + -DARDUINO_ARCH_AVR + ) + add_link_options(-mmcu=atmega2560 -Wl,-u,vfprintf -lprintf_flt -lm) + + # disable some C++ language features + add_compile_options($<$:-fno-threadsafe-statics>) + add_compile_options($<$:-fno-rtti>) + + # disable exceptions + add_compile_options($<$:-fno-exceptions>) + add_compile_options($<$:-fno-unwind-tables>) + + # split and gc sections + add_compile_options(-ffunction-sections -fdata-sections) + add_link_options(-ffunction-sections -fdata-sections -Wl,--gc-sections) + + # LTO (with custom options) + add_compile_options(-flto -fno-fat-lto-objects) + add_link_options(-flto) + + # Create this target before we apply the GC options + add_library(avr_core STATIC ${AVR_SOURCES}) + set_reproducible_target(avr_core) + target_include_directories( + avr_core PRIVATE ${PRUSA_BOARDS_DIR}/cores/prusa_einsy_rambo/ + ${PRUSA_BOARDS_DIR}/variants/prusa_einsy_rambo/ + ) +endif() + +# Meta targets to build absolutely everything +add_custom_target(ALL_FIRMWARE) +add_custom_target(ALL_ENGLISH) +add_custom_target(ALL_MULTILANG) +add_dependencies(ALL_FIRMWARE ALL_ENGLISH ALL_MULTILANG) +set_target_properties(ALL_MULTILANG PROPERTIES EXCLUDE_FROM_ALL FALSE) + +function(add_base_binary variant_name) + add_executable(${variant_name} ${FW_SOURCES} ${FW_HEADERS} ${VARIANT_CFG_DST}) + set_target_properties(${variant_name} PROPERTIES EXCLUDE_FROM_ALL TRUE) + set_reproducible_target(${variant_name}) + + target_include_directories( + ${variant_name} + PRIVATE ${PRUSA_BOARDS_DIR}/cores/prusa_einsy_rambo/ + ${PRUSA_BOARDS_DIR}/variants/prusa_einsy_rambo/ ${CMAKE_SOURCE_DIR}/Firmware + ) + + target_link_libraries(${variant_name} avr_core) + + # configure linker script + set(LINKER_SCRIPT ${PRUSA_BOARDS_DIR}/ldscripts/avr6.xn) + target_link_options(${variant_name} PUBLIC -Wl,-T,${LINKER_SCRIPT}) + + # limit the text section to 248K (256K - 8k reserved for the bootloader) + target_link_options(${variant_name} PUBLIC -Wl,--defsym=__TEXT_REGION_LENGTH__=248K) + + # produce ASM listing. Note we also specify the .map as a byproduct so it gets cleaned because + # link_options doesn't have a "generated outputs" feature. + add_custom_command( + TARGET ${variant_name} + POST_BUILD + COMMAND ${CMAKE_OBJDUMP} --prefix ${CMAKE_SOURCE_DIR} -CSd ${variant_name} > ${variant_name}.asm + BYPRODUCTS ${variant_name}.asm ${variant_name}.map + ) + + # inform about the firmware's size in terminal + add_custom_command( + TARGET ${variant_name} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E echo_append "${variant_name} " + COMMAND ${CMAKE_SIZE_UTIL} -C --mcu=atmega2560 ${variant_name} + ) + report_size(${variant_name}) + + # generate linker map file + target_link_options( + ${variant_name} PUBLIC -Wl,-Map=${CMAKE_CURRENT_BINARY_DIR}/${variant_name}.map + ) + + target_compile_definitions(${variant_name} PRIVATE CMAKE_CONTROL) +endfunction() + +function(fw_add_variant variant_name) + set(variant_header "variants/${variant_name}.h") + string(REPLACE "1_75mm_" "" variant_name "${variant_name}") + string(REPLACE "-E3Dv6full" "" variant_name "${variant_name}") + + # Single-language build + set(FW_EN "${variant_name}_ENGLISH") + set(FW_HEX ${CMAKE_BINARY_DIR}/${FN_PREFIX}-${FW_EN}.hex) + + add_base_binary(${FW_EN}) + target_compile_definitions(${FW_EN} PUBLIC LANG_MODE=0 FW_VARIANT="${variant_header}") + add_custom_command( + TARGET ${FW_EN} + POST_BUILD + COMMAND ${CMAKE_OBJCOPY} -O ihex ${FW_EN} ${FW_EN}.hex + COMMAND ${CMAKE_COMMAND} -E create_hardlink ${FW_EN}.hex ${FW_HEX} + BYPRODUCTS ${FW_EN}.hex ${FW_HEX} + COMMENT "Generating ${FW_EN}.hex" + ) + add_dependencies(ALL_ENGLISH ${FW_EN}) + + # Multi-language build/s + set(FW_LANG_BASE "${variant_name}_lang_base") + set(FW_LANG_PATCH "${variant_name}_lang_patch") + add_base_binary(${FW_LANG_BASE}) + target_compile_definitions(${FW_LANG_BASE} PUBLIC LANG_MODE=1 FW_VARIANT="${variant_header}") + + # Construct language map + set(LANG_TMP_DIR lang) + set(LANG_MAP ${LANG_TMP_DIR}/${variant_name}_lang.map) + + add_custom_command( + OUTPUT ${LANG_MAP} + COMMAND ${CMAKE_OBJCOPY} -O binary ${FW_LANG_BASE} ${FW_LANG_PATCH}.bin + COMMAND "${Python3_EXECUTABLE}" ${CMAKE_SOURCE_DIR}/lang/lang-map.py ${FW_LANG_BASE} ${FW_LANG_PATCH}.bin > ${LANG_MAP} + COMMAND ${CMAKE_OBJCOPY} -I binary -O ihex ${FW_LANG_PATCH}.bin ${FW_LANG_PATCH}.hex + DEPENDS ${FW_LANG_BASE} + BYPRODUCTS ${FW_LANG_PATCH}.bin ${FW_LANG_PATCH}.hex + COMMENT "Generating ${variant_name} language map" + ) + + # Base targets for language checks + add_custom_target(check_lang_${variant_name}) + add_dependencies(check_lang check_lang_${variant_name}) + + # Build language catalogs + set(LANG_BINS "") + foreach(LANG IN LISTS SELECTED_LANGUAGES) + set(LANG_BIN ${LANG_TMP_DIR}/${variant_name}_${LANG}.bin) + set(PO_FILE "${CMAKE_SOURCE_DIR}/lang/po/Firmware_${LANG}.po") + + # Full language checks + add_custom_target( + check_lang_${variant_name}_${LANG} + COMMENT "Checking ${variant_name} language ${LANG}" + COMMAND ${CMAKE_SOURCE_DIR}/lang/lang-check.py --map ${LANG_MAP} ${PO_FILE} + DEPENDS ${LANG_MAP} ${PO_FILE} + USES_TERMINAL + ) + add_dependencies(check_lang_${variant_name} check_lang_${variant_name}_${LANG}) + add_dependencies(check_lang_${LANG} check_lang_${variant_name}_${LANG}) + + add_custom_command( + OUTPUT ${LANG_BIN} + # Check po file for errors _only_ + COMMAND "${Python3_EXECUTABLE}" ${CMAKE_SOURCE_DIR}/lang/lang-check.py --errors-only --map ${LANG_MAP} ${PO_FILE} + # Build the catalog + COMMAND "${Python3_EXECUTABLE}" ${CMAKE_SOURCE_DIR}/lang/lang-build.py ${LANG_MAP} ${PO_FILE} ${LANG_BIN} + # Check bin size + COMMAND ${CMAKE_COMMAND} -DLANG_MAX_SIZE=${LANG_MAX_SIZE} -DLANG_FILE=${LANG_BIN} -P + ${PROJECT_CMAKE_DIR}/Check_lang_size.cmake + DEPENDS ${LANG_MAP} ${PO_FILE} + COMMENT "Generating ${variant_name}_${LANG}.bin" + ) + list(APPEND LANG_BINS ${LANG_BIN}) + endforeach() + + string(FIND ${variant_name} "MK3" HAS_XFLASH) + if(${HAS_XFLASH} GREATER_EQUAL 0) + # X-Flash based build (catalogs appended to patched binary) + set(FW_LANG_FINAL "${variant_name}_MULTILANG") + set(LANG_HEX ${CMAKE_BINARY_DIR}/${FN_PREFIX}-${FW_LANG_FINAL}.hex) + set(LANG_CATBIN ${LANG_TMP_DIR}/${variant_name}_cat.bin) + set(LANG_CATHEX ${LANG_TMP_DIR}/${variant_name}_cat.hex) + + add_custom_command( + OUTPUT ${LANG_CATBIN} + COMMAND ${CMAKE_COMMAND} -E cat ${LANG_BINS} > ${LANG_CATBIN} + DEPENDS ${LANG_BINS} + COMMENT "Merging language catalogs" + ) + #[[ + #add_custom_command(OUTPUT ${LANG_FINAL_BIN} + # COMMAND ${CMAKE_COMMAND} -DLANG_MAX_SIZE=${LANG_BIN_MAX} -DLANG_FILE=${LANG_FINAL_BIN} + # -P ${PROJECT_CMAKE_DIR}/Check_final_lang_bin_size.cmake + # APPEND) + #]] + add_custom_command( + OUTPUT ${LANG_CATHEX} + COMMAND ${CMAKE_OBJCOPY} -I binary -O ihex ${LANG_CATBIN} ${LANG_CATHEX} + DEPENDS ${LANG_CATBIN} + COMMENT "Generating Hex for language data" + ) + + add_custom_command( + OUTPUT ${FW_LANG_FINAL}.hex + COMMAND ${CMAKE_COMMAND} -E cat ${FW_LANG_PATCH}.hex ${LANG_CATHEX} > ${FW_LANG_FINAL}.hex + COMMAND ${CMAKE_COMMAND} -E create_hardlink ${FW_LANG_FINAL}.hex ${LANG_HEX} + BYPRODUCTS ${LANG_HEX} + DEPENDS ${FW_LANG_PATCH}.hex ${LANG_CATHEX} + COMMENT "Generating final ${FW_LANG_FINAL}.hex" + ) + + add_custom_target(${FW_LANG_FINAL} DEPENDS ${FW_LANG_FINAL}.hex) + add_dependencies(ALL_MULTILANG ${FW_LANG_FINAL}) + else() + set(ALL_VARIANT_HEXES "") + # Non-xflash, e.g. MK2.5 + foreach(LANG IN LISTS SELECTED_LANGUAGES) + set(FW_LANG_FINAL ${variant_name}-en_${LANG}) + set(LANG_HEX ${CMAKE_BINARY_DIR}/${FN_PREFIX}-${FW_LANG_FINAL}.hex) + set(LANG_BIN ${LANG_TMP_DIR}/${variant_name}_${LANG}.bin) + + # Patched binary with pre-baked secondary language + add_custom_command( + OUTPUT ${FW_LANG_FINAL}.bin + COMMAND ${CMAKE_OBJCOPY} -O binary ${FW_LANG_BASE} ${FW_LANG_FINAL}.bin + COMMAND ${CMAKE_SOURCE_DIR}/lang/lang-patchsec.py ${FW_LANG_BASE} ${LANG_BIN} + ${FW_LANG_FINAL}.bin + DEPENDS ${FW_LANG_BASE} ${LANG_BIN} + COMMENT "Generating ${FW_LANG_FINAL}.bin" + ) + + # Final hex files + add_custom_command( + OUTPUT ${FW_LANG_FINAL}.hex + COMMAND ${CMAKE_OBJCOPY} -I binary -O ihex ${FW_LANG_FINAL}.bin ${FW_LANG_FINAL}.hex + COMMAND ${CMAKE_COMMAND} -E create_hardlink ${FW_LANG_FINAL}.hex ${LANG_HEX} + BYPRODUCTS ${LANG_HEX} + DEPENDS ${FW_LANG_FINAL}.bin + COMMENT "Creating ${FW_LANG_FINAL}.hex" + ) + + add_custom_target(${FW_LANG_FINAL} DEPENDS ${FW_LANG_FINAL}.hex) + list(APPEND ALL_VARIANT_HEXES ${FW_LANG_FINAL}) + endforeach() + add_custom_target("${variant_name}-All-Languages" DEPENDS ${ALL_VARIANT_HEXES}) + add_dependencies(ALL_MULTILANG "${variant_name}-All-Languages") + endif() +endfunction() + +if(CMAKE_CROSSCOMPILING) + + # Main target for language checks + add_custom_target(check_lang) + foreach(LANG IN LISTS SELECTED_LANGUAGES) + add_custom_target(check_lang_${LANG}) + add_dependencies(check_lang check_lang_${LANG}) + endforeach() + + # build a list of all supported variants + file( + GLOB ALL_VARIANTS + RELATIVE ${PROJECT_SOURCE_DIR}/Firmware/variants + ${PROJECT_SOURCE_DIR}/Firmware/variants/*.h + ) + list(TRANSFORM ALL_VARIANTS REPLACE "\.h$" "") + set(FW_VARIANTS + ${ALL_VARIANTS} + CACHE STRING "Firmware variants to be built" + ) + + foreach(THIS_VAR IN LISTS FW_VARIANTS) + if(NOT ${THIS_VAR} IN_LIST ALL_VARIANTS) + message(FATAL_ERROR "Variant ${THIS_VAR} does not exist") + endif() + + message("Variant added: ${THIS_VAR}") + string(REPLACE "-E3Dv6full" "" DIR_NAME "${THIS_VAR}") + string(REPLACE "1_75mm_" "" DIR_NAME "${DIR_NAME}") + + # Generate a file in a subfolder so that we can organize things a little more neatly in VS code + file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/build_gen/${DIR_NAME}) + file(WRITE ${CMAKE_CURRENT_SOURCE_DIR}/build_gen/${DIR_NAME}/CMakeLists.txt + "project(${DIR_NAME})\nfw_add_variant(${THIS_VAR})" + ) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/build_gen/${DIR_NAME}) + endforeach(THIS_VAR IN LISTS FW_VARIANTS) +endif() + +# +# Tests +# +if(NOT CMAKE_CROSSCOMPILING) + enable_testing() + add_subdirectory(tests) +endif() diff --git a/Catch2/catch.hpp b/Catch2/catch.hpp deleted file mode 100644 index 28448ddb2..000000000 --- a/Catch2/catch.hpp +++ /dev/null @@ -1,13287 +0,0 @@ -/* - * Catch v2.2.3 - * Generated: 2018-06-06 23:11:57.601416 - * ---------------------------------------------------------- - * This file has been merged from multiple headers. Please don't edit it directly - * Copyright (c) 2018 Two Blue Cubes Ltd. All rights reserved. - * - * Distributed under the Boost Software License, Version 1.0. (See accompanying - * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) - */ -#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED -#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED -// start catch.hpp - - -#define CATCH_VERSION_MAJOR 2 -#define CATCH_VERSION_MINOR 2 -#define CATCH_VERSION_PATCH 3 - -#ifdef __clang__ -# pragma clang system_header -#elif defined __GNUC__ -# pragma GCC system_header -#endif - -// start catch_suppress_warnings.h - -#ifdef __clang__ -# ifdef __ICC // icpc defines the __clang__ macro -# pragma warning(push) -# pragma warning(disable: 161 1682) -# else // __ICC -# pragma clang diagnostic ignored "-Wunused-variable" -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wpadded" -# pragma clang diagnostic ignored "-Wswitch-enum" -# pragma clang diagnostic ignored "-Wcovered-switch-default" -# endif -#elif defined __GNUC__ -# pragma GCC diagnostic ignored "-Wparentheses" -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wunused-variable" -# pragma GCC diagnostic ignored "-Wpadded" -#endif -// end catch_suppress_warnings.h -#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) -# define CATCH_IMPL -# define CATCH_CONFIG_ALL_PARTS -#endif - -// In the impl file, we want to have access to all parts of the headers -// Can also be used to sanely support PCHs -#if defined(CATCH_CONFIG_ALL_PARTS) -# define CATCH_CONFIG_EXTERNAL_INTERFACES -# if defined(CATCH_CONFIG_DISABLE_MATCHERS) -# undef CATCH_CONFIG_DISABLE_MATCHERS -# endif -# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER -#endif - -#if !defined(CATCH_CONFIG_IMPL_ONLY) -// start catch_platform.h - -#ifdef __APPLE__ -# include -# if TARGET_OS_OSX == 1 -# define CATCH_PLATFORM_MAC -# elif TARGET_OS_IPHONE == 1 -# define CATCH_PLATFORM_IPHONE -# endif - -#elif defined(linux) || defined(__linux) || defined(__linux__) -# define CATCH_PLATFORM_LINUX - -#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) -# define CATCH_PLATFORM_WINDOWS -#endif - -// end catch_platform.h - -#ifdef CATCH_IMPL -# ifndef CLARA_CONFIG_MAIN -# define CLARA_CONFIG_MAIN_NOT_DEFINED -# define CLARA_CONFIG_MAIN -# endif -#endif - -// start catch_user_interfaces.h - -namespace Catch { - unsigned int rngSeed(); -} - -// end catch_user_interfaces.h -// start catch_tag_alias_autoregistrar.h - -// start catch_common.h - -// start catch_compiler_capabilities.h - -// Detect a number of compiler features - by compiler -// The following features are defined: -// -// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? -// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? -// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? -// **************** -// Note to maintainers: if new toggles are added please document them -// in configuration.md, too -// **************** - -// In general each macro has a _NO_ form -// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. -// Many features, at point of detection, define an _INTERNAL_ macro, so they -// can be combined, en-mass, with the _NO_ forms later. - -#ifdef __cplusplus - -# if __cplusplus >= 201402L -# define CATCH_CPP14_OR_GREATER -# endif - -# if __cplusplus >= 201703L -# define CATCH_CPP17_OR_GREATER -# endif - -#endif - -#if defined(CATCH_CPP17_OR_GREATER) -# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS -#endif - -#ifdef __clang__ - -# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - _Pragma( "clang diagnostic push" ) \ - _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ - _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") -# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ - _Pragma( "clang diagnostic pop" ) - -# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ - _Pragma( "clang diagnostic push" ) \ - _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) -# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ - _Pragma( "clang diagnostic pop" ) - -#endif // __clang__ - -//////////////////////////////////////////////////////////////////////////////// -// Assume that non-Windows platforms support posix signals by default -#if !defined(CATCH_PLATFORM_WINDOWS) - #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS -#endif - -//////////////////////////////////////////////////////////////////////////////// -// We know some environments not to support full POSIX signals -#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) - #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS -#endif - -#ifdef __OS400__ -# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS -# define CATCH_CONFIG_COLOUR_NONE -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Android somehow still does not support std::to_string -#if defined(__ANDROID__) -# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Not all Windows environments support SEH properly -#if defined(__MINGW32__) -# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Cygwin -#ifdef __CYGWIN__ - -// Required for some versions of Cygwin to declare gettimeofday -// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin -# define _BSD_SOURCE - -#endif // __CYGWIN__ - -//////////////////////////////////////////////////////////////////////////////// -// Visual C++ -#ifdef _MSC_VER - -# if _MSC_VER >= 1900 // Visual Studio 2015 or newer -# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS -# endif - -// Universal Windows platform does not support SEH -// Or console colours (or console at all...) -# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) -# define CATCH_CONFIG_COLOUR_NONE -# else -# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH -# endif - -#endif // _MSC_VER - -//////////////////////////////////////////////////////////////////////////////// - -// DJGPP -#ifdef __DJGPP__ -# define CATCH_INTERNAL_CONFIG_NO_WCHAR -#endif // __DJGPP__ - -//////////////////////////////////////////////////////////////////////////////// - -// Use of __COUNTER__ is suppressed during code analysis in -// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly -// handled by it. -// Otherwise all supported compilers support COUNTER macro, -// but user still might want to turn it off -#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) - #define CATCH_INTERNAL_CONFIG_COUNTER -#endif - -#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) -# define CATCH_CONFIG_COUNTER -#endif -#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) -# define CATCH_CONFIG_WINDOWS_SEH -#endif -// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. -#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) -# define CATCH_CONFIG_POSIX_SIGNALS -#endif -// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. -#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) -# define CATCH_CONFIG_WCHAR -#endif - -#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) -# define CATCH_CONFIG_CPP11_TO_STRING -#endif - -#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) -# define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS -#endif - -#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS -# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS -#endif -#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS -# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS -#endif - -// end catch_compiler_capabilities.h -#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line -#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) -#ifdef CATCH_CONFIG_COUNTER -# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) -#else -# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) -#endif - -#include -#include -#include - -namespace Catch { - - struct CaseSensitive { enum Choice { - Yes, - No - }; }; - - class NonCopyable { - NonCopyable( NonCopyable const& ) = delete; - NonCopyable( NonCopyable && ) = delete; - NonCopyable& operator = ( NonCopyable const& ) = delete; - NonCopyable& operator = ( NonCopyable && ) = delete; - - protected: - NonCopyable(); - virtual ~NonCopyable(); - }; - - struct SourceLineInfo { - - SourceLineInfo() = delete; - SourceLineInfo( char const* _file, std::size_t _line ) noexcept - : file( _file ), - line( _line ) - {} - - SourceLineInfo( SourceLineInfo const& other ) = default; - SourceLineInfo( SourceLineInfo && ) = default; - SourceLineInfo& operator = ( SourceLineInfo const& ) = default; - SourceLineInfo& operator = ( SourceLineInfo && ) = default; - - bool empty() const noexcept; - bool operator == ( SourceLineInfo const& other ) const noexcept; - bool operator < ( SourceLineInfo const& other ) const noexcept; - - char const* file; - std::size_t line; - }; - - std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); - - // Use this in variadic streaming macros to allow - // >> +StreamEndStop - // as well as - // >> stuff +StreamEndStop - struct StreamEndStop { - std::string operator+() const; - }; - template - T const& operator + ( T const& value, StreamEndStop ) { - return value; - } -} - -#define CATCH_INTERNAL_LINEINFO \ - ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) - -// end catch_common.h -namespace Catch { - - struct RegistrarForTagAliases { - RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); - }; - -} // end namespace Catch - -#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ - CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS - -// end catch_tag_alias_autoregistrar.h -// start catch_test_registry.h - -// start catch_interfaces_testcase.h - -#include -#include - -namespace Catch { - - class TestSpec; - - struct ITestInvoker { - virtual void invoke () const = 0; - virtual ~ITestInvoker(); - }; - - using ITestCasePtr = std::shared_ptr; - - class TestCase; - struct IConfig; - - struct ITestCaseRegistry { - virtual ~ITestCaseRegistry(); - virtual std::vector const& getAllTests() const = 0; - virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; - }; - - bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); - std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); - std::vector const& getAllTestCasesSorted( IConfig const& config ); - -} - -// end catch_interfaces_testcase.h -// start catch_stringref.h - -#include -#include -#include - -namespace Catch { - - class StringData; - - /// A non-owning string class (similar to the forthcoming std::string_view) - /// Note that, because a StringRef may be a substring of another string, - /// it may not be null terminated. c_str() must return a null terminated - /// string, however, and so the StringRef will internally take ownership - /// (taking a copy), if necessary. In theory this ownership is not externally - /// visible - but it does mean (substring) StringRefs should not be shared between - /// threads. - class StringRef { - public: - using size_type = std::size_t; - - private: - friend struct StringRefTestAccess; - - char const* m_start; - size_type m_size; - - char* m_data = nullptr; - - void takeOwnership(); - - static constexpr char const* const s_empty = ""; - - public: // construction/ assignment - StringRef() noexcept - : StringRef( s_empty, 0 ) - {} - - StringRef( StringRef const& other ) noexcept - : m_start( other.m_start ), - m_size( other.m_size ) - {} - - StringRef( StringRef&& other ) noexcept - : m_start( other.m_start ), - m_size( other.m_size ), - m_data( other.m_data ) - { - other.m_data = nullptr; - } - - StringRef( char const* rawChars ) noexcept; - - StringRef( char const* rawChars, size_type size ) noexcept - : m_start( rawChars ), - m_size( size ) - {} - - StringRef( std::string const& stdString ) noexcept - : m_start( stdString.c_str() ), - m_size( stdString.size() ) - {} - - ~StringRef() noexcept { - delete[] m_data; - } - - auto operator = ( StringRef const &other ) noexcept -> StringRef& { - delete[] m_data; - m_data = nullptr; - m_start = other.m_start; - m_size = other.m_size; - return *this; - } - - operator std::string() const; - - void swap( StringRef& other ) noexcept; - - public: // operators - auto operator == ( StringRef const& other ) const noexcept -> bool; - auto operator != ( StringRef const& other ) const noexcept -> bool; - - auto operator[] ( size_type index ) const noexcept -> char; - - public: // named queries - auto empty() const noexcept -> bool { - return m_size == 0; - } - auto size() const noexcept -> size_type { - return m_size; - } - - auto numberOfCharacters() const noexcept -> size_type; - auto c_str() const -> char const*; - - public: // substrings and searches - auto substr( size_type start, size_type size ) const noexcept -> StringRef; - - // Returns the current start pointer. - // Note that the pointer can change when if the StringRef is a substring - auto currentData() const noexcept -> char const*; - - private: // ownership queries - may not be consistent between calls - auto isOwned() const noexcept -> bool; - auto isSubstring() const noexcept -> bool; - }; - - auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string; - auto operator + ( StringRef const& lhs, char const* rhs ) -> std::string; - auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string; - - auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; - auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; - - inline auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { - return StringRef( rawChars, size ); - } - -} // namespace Catch - -// end catch_stringref.h -namespace Catch { - -template -class TestInvokerAsMethod : public ITestInvoker { - void (C::*m_testAsMethod)(); -public: - TestInvokerAsMethod( void (C::*testAsMethod)() ) noexcept : m_testAsMethod( testAsMethod ) {} - - void invoke() const override { - C obj; - (obj.*m_testAsMethod)(); - } -}; - -auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker*; - -template -auto makeTestInvoker( void (C::*testAsMethod)() ) noexcept -> ITestInvoker* { - return new(std::nothrow) TestInvokerAsMethod( testAsMethod ); -} - -struct NameAndTags { - NameAndTags( StringRef const& name_ = StringRef(), StringRef const& tags_ = StringRef() ) noexcept; - StringRef name; - StringRef tags; -}; - -struct AutoReg : NonCopyable { - AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept; - ~AutoReg(); -}; - -} // end namespace Catch - -#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) -#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ -#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ -#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF - -#if defined(CATCH_CONFIG_DISABLE) - #define INTERNAL_CATCH_TESTCASE_NO_REGISTRATION( TestName, ... ) \ - static void TestName() - #define INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION( TestName, ClassName, ... ) \ - namespace{ \ - struct TestName : INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF ClassName) { \ - void test(); \ - }; \ - } \ - void TestName::test() - -#endif - - /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \ - static void TestName(); \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &TestName ), CATCH_INTERNAL_LINEINFO, "", Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ - CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ - static void TestName() - #define INTERNAL_CATCH_TESTCASE( ... ) \ - INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ ) - - /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &QualifiedMethod ), CATCH_INTERNAL_LINEINFO, "&" #QualifiedMethod, Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ - CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS - - /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ \ - struct TestName : INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF ClassName) { \ - void test(); \ - }; \ - Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ - } \ - CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ - void TestName::test() - #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \ - INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ ) - - /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( Function ), CATCH_INTERNAL_LINEINFO, "", Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ - CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS - -// end catch_test_registry.h -// start catch_capture.hpp - -// start catch_assertionhandler.h - -// start catch_assertioninfo.h - -// start catch_result_type.h - -namespace Catch { - - // ResultWas::OfType enum - struct ResultWas { enum OfType { - Unknown = -1, - Ok = 0, - Info = 1, - Warning = 2, - - FailureBit = 0x10, - - ExpressionFailed = FailureBit | 1, - ExplicitFailure = FailureBit | 2, - - Exception = 0x100 | FailureBit, - - ThrewException = Exception | 1, - DidntThrowException = Exception | 2, - - FatalErrorCondition = 0x200 | FailureBit - - }; }; - - bool isOk( ResultWas::OfType resultType ); - bool isJustInfo( int flags ); - - // ResultDisposition::Flags enum - struct ResultDisposition { enum Flags { - Normal = 0x01, - - ContinueOnFailure = 0x02, // Failures fail test, but execution continues - FalseTest = 0x04, // Prefix expression with ! - SuppressFail = 0x08 // Failures are reported but do not fail the test - }; }; - - ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ); - - bool shouldContinueOnFailure( int flags ); - inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } - bool shouldSuppressFailure( int flags ); - -} // end namespace Catch - -// end catch_result_type.h -namespace Catch { - - struct AssertionInfo - { - StringRef macroName; - SourceLineInfo lineInfo; - StringRef capturedExpression; - ResultDisposition::Flags resultDisposition; - - // We want to delete this constructor but a compiler bug in 4.8 means - // the struct is then treated as non-aggregate - //AssertionInfo() = delete; - }; - -} // end namespace Catch - -// end catch_assertioninfo.h -// start catch_decomposer.h - -// start catch_tostring.h - -#include -#include -#include -#include -// start catch_stream.h - -#include -#include -#include - -namespace Catch { - - std::ostream& cout(); - std::ostream& cerr(); - std::ostream& clog(); - - class StringRef; - - struct IStream { - virtual ~IStream(); - virtual std::ostream& stream() const = 0; - }; - - auto makeStream( StringRef const &filename ) -> IStream const*; - - class ReusableStringStream { - std::size_t m_index; - std::ostream* m_oss; - public: - ReusableStringStream(); - ~ReusableStringStream(); - - auto str() const -> std::string; - - template - auto operator << ( T const& value ) -> ReusableStringStream& { - *m_oss << value; - return *this; - } - auto get() -> std::ostream& { return *m_oss; } - - static void cleanup(); - }; -} - -// end catch_stream.h - -#ifdef __OBJC__ -// start catch_objc_arc.hpp - -#import - -#ifdef __has_feature -#define CATCH_ARC_ENABLED __has_feature(objc_arc) -#else -#define CATCH_ARC_ENABLED 0 -#endif - -void arcSafeRelease( NSObject* obj ); -id performOptionalSelector( id obj, SEL sel ); - -#if !CATCH_ARC_ENABLED -inline void arcSafeRelease( NSObject* obj ) { - [obj release]; -} -inline id performOptionalSelector( id obj, SEL sel ) { - if( [obj respondsToSelector: sel] ) - return [obj performSelector: sel]; - return nil; -} -#define CATCH_UNSAFE_UNRETAINED -#define CATCH_ARC_STRONG -#else -inline void arcSafeRelease( NSObject* ){} -inline id performOptionalSelector( id obj, SEL sel ) { -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" -#endif - if( [obj respondsToSelector: sel] ) - return [obj performSelector: sel]; -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - return nil; -} -#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained -#define CATCH_ARC_STRONG __strong -#endif - -// end catch_objc_arc.hpp -#endif - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable:4180) // We attempt to stream a function (address) by const&, which MSVC complains about but is harmless -#endif - -// We need a dummy global operator<< so we can bring it into Catch namespace later -struct Catch_global_namespace_dummy {}; -std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); - -namespace Catch { - // Bring in operator<< from global namespace into Catch namespace - using ::operator<<; - - namespace Detail { - - extern const std::string unprintableString; - - std::string rawMemoryToString( const void *object, std::size_t size ); - - template - std::string rawMemoryToString( const T& object ) { - return rawMemoryToString( &object, sizeof(object) ); - } - - template - class IsStreamInsertable { - template - static auto test(int) - -> decltype(std::declval() << std::declval(), std::true_type()); - - template - static auto test(...)->std::false_type; - - public: - static const bool value = decltype(test(0))::value; - }; - - template - std::string convertUnknownEnumToString( E e ); - - template - typename std::enable_if< - !std::is_enum::value && !std::is_base_of::value, - std::string>::type convertUnstreamable( T const& ) { - return Detail::unprintableString; - } - template - typename std::enable_if< - !std::is_enum::value && std::is_base_of::value, - std::string>::type convertUnstreamable(T const& ex) { - return ex.what(); - } - - template - typename std::enable_if< - std::is_enum::value - , std::string>::type convertUnstreamable( T const& value ) { - return convertUnknownEnumToString( value ); - } - -#if defined(_MANAGED) - //! Convert a CLR string to a utf8 std::string - template - std::string clrReferenceToString( T^ ref ) { - if (ref == nullptr) - return std::string("null"); - auto bytes = System::Text::Encoding::UTF8->GetBytes(ref->ToString()); - cli::pin_ptr p = &bytes[0]; - return std::string(reinterpret_cast(p), bytes->Length); - } -#endif - - } // namespace Detail - - // If we decide for C++14, change these to enable_if_ts - template - struct StringMaker { - template - static - typename std::enable_if<::Catch::Detail::IsStreamInsertable::value, std::string>::type - convert(const Fake& value) { - ReusableStringStream rss; - // NB: call using the function-like syntax to avoid ambiguity with - // user-defined templated operator<< under clang. - rss.operator<<(value); - return rss.str(); - } - - template - static - typename std::enable_if::value, std::string>::type - convert( const Fake& value ) { -#if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER) - return Detail::convertUnstreamable(value); -#else - return CATCH_CONFIG_FALLBACK_STRINGIFIER(value); -#endif - } - }; - - namespace Detail { - - // This function dispatches all stringification requests inside of Catch. - // Should be preferably called fully qualified, like ::Catch::Detail::stringify - template - std::string stringify(const T& e) { - return ::Catch::StringMaker::type>::type>::convert(e); - } - - template - std::string convertUnknownEnumToString( E e ) { - return ::Catch::Detail::stringify(static_cast::type>(e)); - } - -#if defined(_MANAGED) - template - std::string stringify( T^ e ) { - return ::Catch::StringMaker::convert(e); - } -#endif - - } // namespace Detail - - // Some predefined specializations - - template<> - struct StringMaker { - static std::string convert(const std::string& str); - }; -#ifdef CATCH_CONFIG_WCHAR - template<> - struct StringMaker { - static std::string convert(const std::wstring& wstr); - }; -#endif - - template<> - struct StringMaker { - static std::string convert(char const * str); - }; - template<> - struct StringMaker { - static std::string convert(char * str); - }; - -#ifdef CATCH_CONFIG_WCHAR - template<> - struct StringMaker { - static std::string convert(wchar_t const * str); - }; - template<> - struct StringMaker { - static std::string convert(wchar_t * str); - }; -#endif - - // TBD: Should we use `strnlen` to ensure that we don't go out of the buffer, - // while keeping string semantics? - template - struct StringMaker { - static std::string convert(char const* str) { - return ::Catch::Detail::stringify(std::string{ str }); - } - }; - template - struct StringMaker { - static std::string convert(signed char const* str) { - return ::Catch::Detail::stringify(std::string{ reinterpret_cast(str) }); - } - }; - template - struct StringMaker { - static std::string convert(unsigned char const* str) { - return ::Catch::Detail::stringify(std::string{ reinterpret_cast(str) }); - } - }; - - template<> - struct StringMaker { - static std::string convert(int value); - }; - template<> - struct StringMaker { - static std::string convert(long value); - }; - template<> - struct StringMaker { - static std::string convert(long long value); - }; - template<> - struct StringMaker { - static std::string convert(unsigned int value); - }; - template<> - struct StringMaker { - static std::string convert(unsigned long value); - }; - template<> - struct StringMaker { - static std::string convert(unsigned long long value); - }; - - template<> - struct StringMaker { - static std::string convert(bool b); - }; - - template<> - struct StringMaker { - static std::string convert(char c); - }; - template<> - struct StringMaker { - static std::string convert(signed char c); - }; - template<> - struct StringMaker { - static std::string convert(unsigned char c); - }; - - template<> - struct StringMaker { - static std::string convert(std::nullptr_t); - }; - - template<> - struct StringMaker { - static std::string convert(float value); - }; - template<> - struct StringMaker { - static std::string convert(double value); - }; - - template - struct StringMaker { - template - static std::string convert(U* p) { - if (p) { - return ::Catch::Detail::rawMemoryToString(p); - } else { - return "nullptr"; - } - } - }; - - template - struct StringMaker { - static std::string convert(R C::* p) { - if (p) { - return ::Catch::Detail::rawMemoryToString(p); - } else { - return "nullptr"; - } - } - }; - -#if defined(_MANAGED) - template - struct StringMaker { - static std::string convert( T^ ref ) { - return ::Catch::Detail::clrReferenceToString(ref); - } - }; -#endif - - namespace Detail { - template - std::string rangeToString(InputIterator first, InputIterator last) { - ReusableStringStream rss; - rss << "{ "; - if (first != last) { - rss << ::Catch::Detail::stringify(*first); - for (++first; first != last; ++first) - rss << ", " << ::Catch::Detail::stringify(*first); - } - rss << " }"; - return rss.str(); - } - } - -#ifdef __OBJC__ - template<> - struct StringMaker { - static std::string convert(NSString * nsstring) { - if (!nsstring) - return "nil"; - return std::string("@") + [nsstring UTF8String]; - } - }; - template<> - struct StringMaker { - static std::string convert(NSObject* nsObject) { - return ::Catch::Detail::stringify([nsObject description]); - } - - }; - namespace Detail { - inline std::string stringify( NSString* nsstring ) { - return StringMaker::convert( nsstring ); - } - - } // namespace Detail -#endif // __OBJC__ - -} // namespace Catch - -////////////////////////////////////////////////////// -// Separate std-lib types stringification, so it can be selectively enabled -// This means that we do not bring in - -#if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS) -# define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER -# define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER -# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER -#endif - -// Separate std::pair specialization -#if defined(CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER) -#include -namespace Catch { - template - struct StringMaker > { - static std::string convert(const std::pair& pair) { - ReusableStringStream rss; - rss << "{ " - << ::Catch::Detail::stringify(pair.first) - << ", " - << ::Catch::Detail::stringify(pair.second) - << " }"; - return rss.str(); - } - }; -} -#endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER - -// Separate std::tuple specialization -#if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER) -#include -namespace Catch { - namespace Detail { - template< - typename Tuple, - std::size_t N = 0, - bool = (N < std::tuple_size::value) - > - struct TupleElementPrinter { - static void print(const Tuple& tuple, std::ostream& os) { - os << (N ? ", " : " ") - << ::Catch::Detail::stringify(std::get(tuple)); - TupleElementPrinter::print(tuple, os); - } - }; - - template< - typename Tuple, - std::size_t N - > - struct TupleElementPrinter { - static void print(const Tuple&, std::ostream&) {} - }; - - } - - template - struct StringMaker> { - static std::string convert(const std::tuple& tuple) { - ReusableStringStream rss; - rss << '{'; - Detail::TupleElementPrinter>::print(tuple, rss.get()); - rss << " }"; - return rss.str(); - } - }; -} -#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER - -namespace Catch { - struct not_this_one {}; // Tag type for detecting which begin/ end are being selected - - // Import begin/ end from std here so they are considered alongside the fallback (...) overloads in this namespace - using std::begin; - using std::end; - - not_this_one begin( ... ); - not_this_one end( ... ); - - template - struct is_range { - static const bool value = - !std::is_same())), not_this_one>::value && - !std::is_same())), not_this_one>::value; - }; - -#if defined(_MANAGED) // Managed types are never ranges - template - struct is_range { - static const bool value = false; - }; -#endif - - template - std::string rangeToString( Range const& range ) { - return ::Catch::Detail::rangeToString( begin( range ), end( range ) ); - } - - // Handle vector specially - template - std::string rangeToString( std::vector const& v ) { - ReusableStringStream rss; - rss << "{ "; - bool first = true; - for( bool b : v ) { - if( first ) - first = false; - else - rss << ", "; - rss << ::Catch::Detail::stringify( b ); - } - rss << " }"; - return rss.str(); - } - - template - struct StringMaker::value && !::Catch::Detail::IsStreamInsertable::value>::type> { - static std::string convert( R const& range ) { - return rangeToString( range ); - } - }; - - template - struct StringMaker { - static std::string convert(T const(&arr)[SZ]) { - return rangeToString(arr); - } - }; - -} // namespace Catch - -// Separate std::chrono::duration specialization -#if defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) -#include -#include -#include - -namespace Catch { - -template -struct ratio_string { - static std::string symbol(); -}; - -template -std::string ratio_string::symbol() { - Catch::ReusableStringStream rss; - rss << '[' << Ratio::num << '/' - << Ratio::den << ']'; - return rss.str(); -} -template <> -struct ratio_string { - static std::string symbol(); -}; -template <> -struct ratio_string { - static std::string symbol(); -}; -template <> -struct ratio_string { - static std::string symbol(); -}; -template <> -struct ratio_string { - static std::string symbol(); -}; -template <> -struct ratio_string { - static std::string symbol(); -}; -template <> -struct ratio_string { - static std::string symbol(); -}; - - //////////// - // std::chrono::duration specializations - template - struct StringMaker> { - static std::string convert(std::chrono::duration const& duration) { - ReusableStringStream rss; - rss << duration.count() << ' ' << ratio_string::symbol() << 's'; - return rss.str(); - } - }; - template - struct StringMaker>> { - static std::string convert(std::chrono::duration> const& duration) { - ReusableStringStream rss; - rss << duration.count() << " s"; - return rss.str(); - } - }; - template - struct StringMaker>> { - static std::string convert(std::chrono::duration> const& duration) { - ReusableStringStream rss; - rss << duration.count() << " m"; - return rss.str(); - } - }; - template - struct StringMaker>> { - static std::string convert(std::chrono::duration> const& duration) { - ReusableStringStream rss; - rss << duration.count() << " h"; - return rss.str(); - } - }; - - //////////// - // std::chrono::time_point specialization - // Generic time_point cannot be specialized, only std::chrono::time_point - template - struct StringMaker> { - static std::string convert(std::chrono::time_point const& time_point) { - return ::Catch::Detail::stringify(time_point.time_since_epoch()) + " since epoch"; - } - }; - // std::chrono::time_point specialization - template - struct StringMaker> { - static std::string convert(std::chrono::time_point const& time_point) { - auto converted = std::chrono::system_clock::to_time_t(time_point); - -#ifdef _MSC_VER - std::tm timeInfo = {}; - gmtime_s(&timeInfo, &converted); -#else - std::tm* timeInfo = std::gmtime(&converted); -#endif - - auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); - char timeStamp[timeStampSize]; - const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; - -#ifdef _MSC_VER - std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); -#else - std::strftime(timeStamp, timeStampSize, fmt, timeInfo); -#endif - return std::string(timeStamp); - } - }; -} -#endif // CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -// end catch_tostring.h -#include - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable:4389) // '==' : signed/unsigned mismatch -#pragma warning(disable:4018) // more "signed/unsigned mismatch" -#pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform) -#pragma warning(disable:4180) // qualifier applied to function type has no meaning -#endif - -namespace Catch { - - struct ITransientExpression { - auto isBinaryExpression() const -> bool { return m_isBinaryExpression; } - auto getResult() const -> bool { return m_result; } - virtual void streamReconstructedExpression( std::ostream &os ) const = 0; - - ITransientExpression( bool isBinaryExpression, bool result ) - : m_isBinaryExpression( isBinaryExpression ), - m_result( result ) - {} - - // We don't actually need a virtual destructor, but many static analysers - // complain if it's not here :-( - virtual ~ITransientExpression(); - - bool m_isBinaryExpression; - bool m_result; - - }; - - void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ); - - template - class BinaryExpr : public ITransientExpression { - LhsT m_lhs; - StringRef m_op; - RhsT m_rhs; - - void streamReconstructedExpression( std::ostream &os ) const override { - formatReconstructedExpression - ( os, Catch::Detail::stringify( m_lhs ), m_op, Catch::Detail::stringify( m_rhs ) ); - } - - public: - BinaryExpr( bool comparisonResult, LhsT lhs, StringRef op, RhsT rhs ) - : ITransientExpression{ true, comparisonResult }, - m_lhs( lhs ), - m_op( op ), - m_rhs( rhs ) - {} - }; - - template - class UnaryExpr : public ITransientExpression { - LhsT m_lhs; - - void streamReconstructedExpression( std::ostream &os ) const override { - os << Catch::Detail::stringify( m_lhs ); - } - - public: - explicit UnaryExpr( LhsT lhs ) - : ITransientExpression{ false, lhs ? true : false }, - m_lhs( lhs ) - {} - }; - - // Specialised comparison functions to handle equality comparisons between ints and pointers (NULL deduces as an int) - template - auto compareEqual( LhsT const& lhs, RhsT const& rhs ) -> bool { return static_cast(lhs == rhs); } - template - auto compareEqual( T* const& lhs, int rhs ) -> bool { return lhs == reinterpret_cast( rhs ); } - template - auto compareEqual( T* const& lhs, long rhs ) -> bool { return lhs == reinterpret_cast( rhs ); } - template - auto compareEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) == rhs; } - template - auto compareEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) == rhs; } - - template - auto compareNotEqual( LhsT const& lhs, RhsT&& rhs ) -> bool { return static_cast(lhs != rhs); } - template - auto compareNotEqual( T* const& lhs, int rhs ) -> bool { return lhs != reinterpret_cast( rhs ); } - template - auto compareNotEqual( T* const& lhs, long rhs ) -> bool { return lhs != reinterpret_cast( rhs ); } - template - auto compareNotEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) != rhs; } - template - auto compareNotEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast( lhs ) != rhs; } - - template - class ExprLhs { - LhsT m_lhs; - public: - explicit ExprLhs( LhsT lhs ) : m_lhs( lhs ) {} - - template - auto operator == ( RhsT const& rhs ) -> BinaryExpr const { - return { compareEqual( m_lhs, rhs ), m_lhs, "==", rhs }; - } - auto operator == ( bool rhs ) -> BinaryExpr const { - return { m_lhs == rhs, m_lhs, "==", rhs }; - } - - template - auto operator != ( RhsT const& rhs ) -> BinaryExpr const { - return { compareNotEqual( m_lhs, rhs ), m_lhs, "!=", rhs }; - } - auto operator != ( bool rhs ) -> BinaryExpr const { - return { m_lhs != rhs, m_lhs, "!=", rhs }; - } - - template - auto operator > ( RhsT const& rhs ) -> BinaryExpr const { - return { static_cast(m_lhs > rhs), m_lhs, ">", rhs }; - } - template - auto operator < ( RhsT const& rhs ) -> BinaryExpr const { - return { static_cast(m_lhs < rhs), m_lhs, "<", rhs }; - } - template - auto operator >= ( RhsT const& rhs ) -> BinaryExpr const { - return { static_cast(m_lhs >= rhs), m_lhs, ">=", rhs }; - } - template - auto operator <= ( RhsT const& rhs ) -> BinaryExpr const { - return { static_cast(m_lhs <= rhs), m_lhs, "<=", rhs }; - } - - auto makeUnaryExpr() const -> UnaryExpr { - return UnaryExpr{ m_lhs }; - } - }; - - void handleExpression( ITransientExpression const& expr ); - - template - void handleExpression( ExprLhs const& expr ) { - handleExpression( expr.makeUnaryExpr() ); - } - - struct Decomposer { - template - auto operator <= ( T const& lhs ) -> ExprLhs { - return ExprLhs{ lhs }; - } - - auto operator <=( bool value ) -> ExprLhs { - return ExprLhs{ value }; - } - }; - -} // end namespace Catch - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -// end catch_decomposer.h -// start catch_interfaces_capture.h - -#include - -namespace Catch { - - class AssertionResult; - struct AssertionInfo; - struct SectionInfo; - struct SectionEndInfo; - struct MessageInfo; - struct Counts; - struct BenchmarkInfo; - struct BenchmarkStats; - struct AssertionReaction; - - struct ITransientExpression; - - struct IResultCapture { - - virtual ~IResultCapture(); - - virtual bool sectionStarted( SectionInfo const& sectionInfo, - Counts& assertions ) = 0; - virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0; - virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0; - - virtual void benchmarkStarting( BenchmarkInfo const& info ) = 0; - virtual void benchmarkEnded( BenchmarkStats const& stats ) = 0; - - virtual void pushScopedMessage( MessageInfo const& message ) = 0; - virtual void popScopedMessage( MessageInfo const& message ) = 0; - - virtual void handleFatalErrorCondition( StringRef message ) = 0; - - virtual void handleExpr - ( AssertionInfo const& info, - ITransientExpression const& expr, - AssertionReaction& reaction ) = 0; - virtual void handleMessage - ( AssertionInfo const& info, - ResultWas::OfType resultType, - StringRef const& message, - AssertionReaction& reaction ) = 0; - virtual void handleUnexpectedExceptionNotThrown - ( AssertionInfo const& info, - AssertionReaction& reaction ) = 0; - virtual void handleUnexpectedInflightException - ( AssertionInfo const& info, - std::string const& message, - AssertionReaction& reaction ) = 0; - virtual void handleIncomplete - ( AssertionInfo const& info ) = 0; - virtual void handleNonExpr - ( AssertionInfo const &info, - ResultWas::OfType resultType, - AssertionReaction &reaction ) = 0; - - virtual bool lastAssertionPassed() = 0; - virtual void assertionPassed() = 0; - - // Deprecated, do not use: - virtual std::string getCurrentTestName() const = 0; - virtual const AssertionResult* getLastResult() const = 0; - virtual void exceptionEarlyReported() = 0; - }; - - IResultCapture& getResultCapture(); -} - -// end catch_interfaces_capture.h -namespace Catch { - - struct TestFailureException{}; - struct AssertionResultData; - struct IResultCapture; - class RunContext; - - class LazyExpression { - friend class AssertionHandler; - friend struct AssertionStats; - friend class RunContext; - - ITransientExpression const* m_transientExpression = nullptr; - bool m_isNegated; - public: - LazyExpression( bool isNegated ); - LazyExpression( LazyExpression const& other ); - LazyExpression& operator = ( LazyExpression const& ) = delete; - - explicit operator bool() const; - - friend auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream&; - }; - - struct AssertionReaction { - bool shouldDebugBreak = false; - bool shouldThrow = false; - }; - - class AssertionHandler { - AssertionInfo m_assertionInfo; - AssertionReaction m_reaction; - bool m_completed = false; - IResultCapture& m_resultCapture; - - public: - AssertionHandler - ( StringRef macroName, - SourceLineInfo const& lineInfo, - StringRef capturedExpression, - ResultDisposition::Flags resultDisposition ); - ~AssertionHandler() { - if ( !m_completed ) { - m_resultCapture.handleIncomplete( m_assertionInfo ); - } - } - - template - void handleExpr( ExprLhs const& expr ) { - handleExpr( expr.makeUnaryExpr() ); - } - void handleExpr( ITransientExpression const& expr ); - - void handleMessage(ResultWas::OfType resultType, StringRef const& message); - - void handleExceptionThrownAsExpected(); - void handleUnexpectedExceptionNotThrown(); - void handleExceptionNotThrownAsExpected(); - void handleThrowingCallSkipped(); - void handleUnexpectedInflightException(); - - void complete(); - void setCompleted(); - - // query - auto allowThrows() const -> bool; - }; - - void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef matcherString ); - -} // namespace Catch - -// end catch_assertionhandler.h -// start catch_message.h - -#include - -namespace Catch { - - struct MessageInfo { - MessageInfo( std::string const& _macroName, - SourceLineInfo const& _lineInfo, - ResultWas::OfType _type ); - - std::string macroName; - std::string message; - SourceLineInfo lineInfo; - ResultWas::OfType type; - unsigned int sequence; - - bool operator == ( MessageInfo const& other ) const; - bool operator < ( MessageInfo const& other ) const; - private: - static unsigned int globalCount; - }; - - struct MessageStream { - - template - MessageStream& operator << ( T const& value ) { - m_stream << value; - return *this; - } - - ReusableStringStream m_stream; - }; - - struct MessageBuilder : MessageStream { - MessageBuilder( std::string const& macroName, - SourceLineInfo const& lineInfo, - ResultWas::OfType type ); - - template - MessageBuilder& operator << ( T const& value ) { - m_stream << value; - return *this; - } - - MessageInfo m_info; - }; - - class ScopedMessage { - public: - explicit ScopedMessage( MessageBuilder const& builder ); - ~ScopedMessage(); - - MessageInfo m_info; - }; - -} // end namespace Catch - -// end catch_message.h -#if !defined(CATCH_CONFIG_DISABLE) - -#if !defined(CATCH_CONFIG_DISABLE_STRINGIFICATION) - #define CATCH_INTERNAL_STRINGIFY(...) #__VA_ARGS__ -#else - #define CATCH_INTERNAL_STRINGIFY(...) "Disabled by CATCH_CONFIG_DISABLE_STRINGIFICATION" -#endif - -#if defined(CATCH_CONFIG_FAST_COMPILE) - -/////////////////////////////////////////////////////////////////////////////// -// Another way to speed-up compilation is to omit local try-catch for REQUIRE* -// macros. -#define INTERNAL_CATCH_TRY -#define INTERNAL_CATCH_CATCH( capturer ) - -#else // CATCH_CONFIG_FAST_COMPILE - -#define INTERNAL_CATCH_TRY try -#define INTERNAL_CATCH_CATCH( handler ) catch(...) { handler.handleUnexpectedInflightException(); } - -#endif - -#define INTERNAL_CATCH_REACT( handler ) handler.complete(); - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_TEST( macroName, resultDisposition, ... ) \ - do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ - INTERNAL_CATCH_TRY { \ - CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ - catchAssertionHandler.handleExpr( Catch::Decomposer() <= __VA_ARGS__ ); \ - CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ - } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ - INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( (void)0, false && static_cast( !!(__VA_ARGS__) ) ) // the expression here is never evaluated at runtime but it forces the compiler to give it a look - // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_IF( macroName, resultDisposition, ... ) \ - INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ - if( Catch::getResultCapture().lastAssertionPassed() ) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_ELSE( macroName, resultDisposition, ... ) \ - INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ - if( !Catch::getResultCapture().lastAssertionPassed() ) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, ... ) \ - do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ - try { \ - static_cast(__VA_ARGS__); \ - catchAssertionHandler.handleExceptionNotThrownAsExpected(); \ - } \ - catch( ... ) { \ - catchAssertionHandler.handleUnexpectedInflightException(); \ - } \ - INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( false ) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_THROWS( macroName, resultDisposition, ... ) \ - do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \ - if( catchAssertionHandler.allowThrows() ) \ - try { \ - static_cast(__VA_ARGS__); \ - catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ - } \ - catch( ... ) { \ - catchAssertionHandler.handleExceptionThrownAsExpected(); \ - } \ - else \ - catchAssertionHandler.handleThrowingCallSkipped(); \ - INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( false ) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \ - do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr) ", " CATCH_INTERNAL_STRINGIFY(exceptionType), resultDisposition ); \ - if( catchAssertionHandler.allowThrows() ) \ - try { \ - static_cast(expr); \ - catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ - } \ - catch( exceptionType const& ) { \ - catchAssertionHandler.handleExceptionThrownAsExpected(); \ - } \ - catch( ... ) { \ - catchAssertionHandler.handleUnexpectedInflightException(); \ - } \ - else \ - catchAssertionHandler.handleThrowingCallSkipped(); \ - INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( false ) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \ - do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ - catchAssertionHandler.handleMessage( messageType, ( Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop() ).m_stream.str() ); \ - INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( false ) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_INFO( macroName, log ) \ - Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage )( Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ); - -/////////////////////////////////////////////////////////////////////////////// -// Although this is matcher-based, it can be used with just a string -#define INTERNAL_CATCH_THROWS_STR_MATCHES( macroName, resultDisposition, matcher, ... ) \ - do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ - if( catchAssertionHandler.allowThrows() ) \ - try { \ - static_cast(__VA_ARGS__); \ - catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ - } \ - catch( ... ) { \ - Catch::handleExceptionMatchExpr( catchAssertionHandler, matcher, #matcher ); \ - } \ - else \ - catchAssertionHandler.handleThrowingCallSkipped(); \ - INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( false ) - -#endif // CATCH_CONFIG_DISABLE - -// end catch_capture.hpp -// start catch_section.h - -// start catch_section_info.h - -// start catch_totals.h - -#include - -namespace Catch { - - struct Counts { - Counts operator - ( Counts const& other ) const; - Counts& operator += ( Counts const& other ); - - std::size_t total() const; - bool allPassed() const; - bool allOk() const; - - std::size_t passed = 0; - std::size_t failed = 0; - std::size_t failedButOk = 0; - }; - - struct Totals { - - Totals operator - ( Totals const& other ) const; - Totals& operator += ( Totals const& other ); - - Totals delta( Totals const& prevTotals ) const; - - int error = 0; - Counts assertions; - Counts testCases; - }; -} - -// end catch_totals.h -#include - -namespace Catch { - - struct SectionInfo { - SectionInfo - ( SourceLineInfo const& _lineInfo, - std::string const& _name, - std::string const& _description = std::string() ); - - std::string name; - std::string description; - SourceLineInfo lineInfo; - }; - - struct SectionEndInfo { - SectionEndInfo( SectionInfo const& _sectionInfo, Counts const& _prevAssertions, double _durationInSeconds ); - - SectionInfo sectionInfo; - Counts prevAssertions; - double durationInSeconds; - }; - -} // end namespace Catch - -// end catch_section_info.h -// start catch_timer.h - -#include - -namespace Catch { - - auto getCurrentNanosecondsSinceEpoch() -> uint64_t; - auto getEstimatedClockResolution() -> uint64_t; - - class Timer { - uint64_t m_nanoseconds = 0; - public: - void start(); - auto getElapsedNanoseconds() const -> uint64_t; - auto getElapsedMicroseconds() const -> uint64_t; - auto getElapsedMilliseconds() const -> unsigned int; - auto getElapsedSeconds() const -> double; - }; - -} // namespace Catch - -// end catch_timer.h -#include - -namespace Catch { - - class Section : NonCopyable { - public: - Section( SectionInfo const& info ); - ~Section(); - - // This indicates whether the section should be executed or not - explicit operator bool() const; - - private: - SectionInfo m_info; - - std::string m_name; - Counts m_assertions; - bool m_sectionIncluded; - Timer m_timer; - }; - -} // end namespace Catch - - #define INTERNAL_CATCH_SECTION( ... ) \ - if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) - -// end catch_section.h -// start catch_benchmark.h - -#include -#include - -namespace Catch { - - class BenchmarkLooper { - - std::string m_name; - std::size_t m_count = 0; - std::size_t m_iterationsToRun = 1; - uint64_t m_resolution; - Timer m_timer; - - static auto getResolution() -> uint64_t; - public: - // Keep most of this inline as it's on the code path that is being timed - BenchmarkLooper( StringRef name ) - : m_name( name ), - m_resolution( getResolution() ) - { - reportStart(); - m_timer.start(); - } - - explicit operator bool() { - if( m_count < m_iterationsToRun ) - return true; - return needsMoreIterations(); - } - - void increment() { - ++m_count; - } - - void reportStart(); - auto needsMoreIterations() -> bool; - }; - -} // end namespace Catch - -#define BENCHMARK( name ) \ - for( Catch::BenchmarkLooper looper( name ); looper; looper.increment() ) - -// end catch_benchmark.h -// start catch_interfaces_exception.h - -// start catch_interfaces_registry_hub.h - -#include -#include - -namespace Catch { - - class TestCase; - struct ITestCaseRegistry; - struct IExceptionTranslatorRegistry; - struct IExceptionTranslator; - struct IReporterRegistry; - struct IReporterFactory; - struct ITagAliasRegistry; - class StartupExceptionRegistry; - - using IReporterFactoryPtr = std::shared_ptr; - - struct IRegistryHub { - virtual ~IRegistryHub(); - - virtual IReporterRegistry const& getReporterRegistry() const = 0; - virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; - virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0; - - virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0; - - virtual StartupExceptionRegistry const& getStartupExceptionRegistry() const = 0; - }; - - struct IMutableRegistryHub { - virtual ~IMutableRegistryHub(); - virtual void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) = 0; - virtual void registerListener( IReporterFactoryPtr const& factory ) = 0; - virtual void registerTest( TestCase const& testInfo ) = 0; - virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; - virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0; - virtual void registerStartupException() noexcept = 0; - }; - - IRegistryHub& getRegistryHub(); - IMutableRegistryHub& getMutableRegistryHub(); - void cleanUp(); - std::string translateActiveException(); - -} - -// end catch_interfaces_registry_hub.h -#if defined(CATCH_CONFIG_DISABLE) - #define INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( translatorName, signature) \ - static std::string translatorName( signature ) -#endif - -#include -#include -#include - -namespace Catch { - using exceptionTranslateFunction = std::string(*)(); - - struct IExceptionTranslator; - using ExceptionTranslators = std::vector>; - - struct IExceptionTranslator { - virtual ~IExceptionTranslator(); - virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0; - }; - - struct IExceptionTranslatorRegistry { - virtual ~IExceptionTranslatorRegistry(); - - virtual std::string translateActiveException() const = 0; - }; - - class ExceptionTranslatorRegistrar { - template - class ExceptionTranslator : public IExceptionTranslator { - public: - - ExceptionTranslator( std::string(*translateFunction)( T& ) ) - : m_translateFunction( translateFunction ) - {} - - std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const override { - try { - if( it == itEnd ) - std::rethrow_exception(std::current_exception()); - else - return (*it)->translate( it+1, itEnd ); - } - catch( T& ex ) { - return m_translateFunction( ex ); - } - } - - protected: - std::string(*m_translateFunction)( T& ); - }; - - public: - template - ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) { - getMutableRegistryHub().registerTranslator - ( new ExceptionTranslator( translateFunction ) ); - } - }; -} - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \ - static std::string translatorName( signature ); \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); } \ - CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ - static std::string translatorName( signature ) - -#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) - -// end catch_interfaces_exception.h -// start catch_approx.h - -#include -#include - -namespace Catch { -namespace Detail { - - class Approx { - private: - bool equalityComparisonImpl(double other) const; - - public: - explicit Approx ( double value ); - - static Approx custom(); - - template ::value>::type> - Approx operator()( T const& value ) { - Approx approx( static_cast(value) ); - approx.epsilon( m_epsilon ); - approx.margin( m_margin ); - approx.scale( m_scale ); - return approx; - } - - template ::value>::type> - explicit Approx( T const& value ): Approx(static_cast(value)) - {} - - template ::value>::type> - friend bool operator == ( const T& lhs, Approx const& rhs ) { - auto lhs_v = static_cast(lhs); - return rhs.equalityComparisonImpl(lhs_v); - } - - template ::value>::type> - friend bool operator == ( Approx const& lhs, const T& rhs ) { - return operator==( rhs, lhs ); - } - - template ::value>::type> - friend bool operator != ( T const& lhs, Approx const& rhs ) { - return !operator==( lhs, rhs ); - } - - template ::value>::type> - friend bool operator != ( Approx const& lhs, T const& rhs ) { - return !operator==( rhs, lhs ); - } - - template ::value>::type> - friend bool operator <= ( T const& lhs, Approx const& rhs ) { - return static_cast(lhs) < rhs.m_value || lhs == rhs; - } - - template ::value>::type> - friend bool operator <= ( Approx const& lhs, T const& rhs ) { - return lhs.m_value < static_cast(rhs) || lhs == rhs; - } - - template ::value>::type> - friend bool operator >= ( T const& lhs, Approx const& rhs ) { - return static_cast(lhs) > rhs.m_value || lhs == rhs; - } - - template ::value>::type> - friend bool operator >= ( Approx const& lhs, T const& rhs ) { - return lhs.m_value > static_cast(rhs) || lhs == rhs; - } - - template ::value>::type> - Approx& epsilon( T const& newEpsilon ) { - double epsilonAsDouble = static_cast(newEpsilon); - if( epsilonAsDouble < 0 || epsilonAsDouble > 1.0 ) { - throw std::domain_error - ( "Invalid Approx::epsilon: " + - Catch::Detail::stringify( epsilonAsDouble ) + - ", Approx::epsilon has to be between 0 and 1" ); - } - m_epsilon = epsilonAsDouble; - return *this; - } - - template ::value>::type> - Approx& margin( T const& newMargin ) { - double marginAsDouble = static_cast(newMargin); - if( marginAsDouble < 0 ) { - throw std::domain_error - ( "Invalid Approx::margin: " + - Catch::Detail::stringify( marginAsDouble ) + - ", Approx::Margin has to be non-negative." ); - - } - m_margin = marginAsDouble; - return *this; - } - - template ::value>::type> - Approx& scale( T const& newScale ) { - m_scale = static_cast(newScale); - return *this; - } - - std::string toString() const; - - private: - double m_epsilon; - double m_margin; - double m_scale; - double m_value; - }; -} - -template<> -struct StringMaker { - static std::string convert(Catch::Detail::Approx const& value); -}; - -} // end namespace Catch - -// end catch_approx.h -// start catch_string_manip.h - -#include -#include - -namespace Catch { - - bool startsWith( std::string const& s, std::string const& prefix ); - bool startsWith( std::string const& s, char prefix ); - bool endsWith( std::string const& s, std::string const& suffix ); - bool endsWith( std::string const& s, char suffix ); - bool contains( std::string const& s, std::string const& infix ); - void toLowerInPlace( std::string& s ); - std::string toLower( std::string const& s ); - std::string trim( std::string const& str ); - bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); - - struct pluralise { - pluralise( std::size_t count, std::string const& label ); - - friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); - - std::size_t m_count; - std::string m_label; - }; -} - -// end catch_string_manip.h -#ifndef CATCH_CONFIG_DISABLE_MATCHERS -// start catch_capture_matchers.h - -// start catch_matchers.h - -#include -#include - -namespace Catch { -namespace Matchers { - namespace Impl { - - template struct MatchAllOf; - template struct MatchAnyOf; - template struct MatchNotOf; - - class MatcherUntypedBase { - public: - MatcherUntypedBase() = default; - MatcherUntypedBase ( MatcherUntypedBase const& ) = default; - MatcherUntypedBase& operator = ( MatcherUntypedBase const& ) = delete; - std::string toString() const; - - protected: - virtual ~MatcherUntypedBase(); - virtual std::string describe() const = 0; - mutable std::string m_cachedToString; - }; - - template - struct MatcherMethod { - virtual bool match( ObjectT const& arg ) const = 0; - }; - template - struct MatcherMethod { - virtual bool match( PtrT* arg ) const = 0; - }; - - template - struct MatcherBase : MatcherUntypedBase, MatcherMethod { - - MatchAllOf operator && ( MatcherBase const& other ) const; - MatchAnyOf operator || ( MatcherBase const& other ) const; - MatchNotOf operator ! () const; - }; - - template - struct MatchAllOf : MatcherBase { - bool match( ArgT const& arg ) const override { - for( auto matcher : m_matchers ) { - if (!matcher->match(arg)) - return false; - } - return true; - } - std::string describe() const override { - std::string description; - description.reserve( 4 + m_matchers.size()*32 ); - description += "( "; - bool first = true; - for( auto matcher : m_matchers ) { - if( first ) - first = false; - else - description += " and "; - description += matcher->toString(); - } - description += " )"; - return description; - } - - MatchAllOf& operator && ( MatcherBase const& other ) { - m_matchers.push_back( &other ); - return *this; - } - - std::vector const*> m_matchers; - }; - template - struct MatchAnyOf : MatcherBase { - - bool match( ArgT const& arg ) const override { - for( auto matcher : m_matchers ) { - if (matcher->match(arg)) - return true; - } - return false; - } - std::string describe() const override { - std::string description; - description.reserve( 4 + m_matchers.size()*32 ); - description += "( "; - bool first = true; - for( auto matcher : m_matchers ) { - if( first ) - first = false; - else - description += " or "; - description += matcher->toString(); - } - description += " )"; - return description; - } - - MatchAnyOf& operator || ( MatcherBase const& other ) { - m_matchers.push_back( &other ); - return *this; - } - - std::vector const*> m_matchers; - }; - - template - struct MatchNotOf : MatcherBase { - - MatchNotOf( MatcherBase const& underlyingMatcher ) : m_underlyingMatcher( underlyingMatcher ) {} - - bool match( ArgT const& arg ) const override { - return !m_underlyingMatcher.match( arg ); - } - - std::string describe() const override { - return "not " + m_underlyingMatcher.toString(); - } - MatcherBase const& m_underlyingMatcher; - }; - - template - MatchAllOf MatcherBase::operator && ( MatcherBase const& other ) const { - return MatchAllOf() && *this && other; - } - template - MatchAnyOf MatcherBase::operator || ( MatcherBase const& other ) const { - return MatchAnyOf() || *this || other; - } - template - MatchNotOf MatcherBase::operator ! () const { - return MatchNotOf( *this ); - } - - } // namespace Impl - -} // namespace Matchers - -using namespace Matchers; -using Matchers::Impl::MatcherBase; - -} // namespace Catch - -// end catch_matchers.h -// start catch_matchers_floating.h - -#include -#include - -namespace Catch { -namespace Matchers { - - namespace Floating { - - enum class FloatingPointKind : uint8_t; - - struct WithinAbsMatcher : MatcherBase { - WithinAbsMatcher(double target, double margin); - bool match(double const& matchee) const override; - std::string describe() const override; - private: - double m_target; - double m_margin; - }; - - struct WithinUlpsMatcher : MatcherBase { - WithinUlpsMatcher(double target, int ulps, FloatingPointKind baseType); - bool match(double const& matchee) const override; - std::string describe() const override; - private: - double m_target; - int m_ulps; - FloatingPointKind m_type; - }; - - } // namespace Floating - - // The following functions create the actual matcher objects. - // This allows the types to be inferred - Floating::WithinUlpsMatcher WithinULP(double target, int maxUlpDiff); - Floating::WithinUlpsMatcher WithinULP(float target, int maxUlpDiff); - Floating::WithinAbsMatcher WithinAbs(double target, double margin); - -} // namespace Matchers -} // namespace Catch - -// end catch_matchers_floating.h -// start catch_matchers_generic.hpp - -#include -#include - -namespace Catch { -namespace Matchers { -namespace Generic { - -namespace Detail { - std::string finalizeDescription(const std::string& desc); -} - -template -class PredicateMatcher : public MatcherBase { - std::function m_predicate; - std::string m_description; -public: - - PredicateMatcher(std::function const& elem, std::string const& descr) - :m_predicate(std::move(elem)), - m_description(Detail::finalizeDescription(descr)) - {} - - bool match( T const& item ) const override { - return m_predicate(item); - } - - std::string describe() const override { - return m_description; - } -}; - -} // namespace Generic - - // The following functions create the actual matcher objects. - // The user has to explicitly specify type to the function, because - // infering std::function is hard (but possible) and - // requires a lot of TMP. - template - Generic::PredicateMatcher Predicate(std::function const& predicate, std::string const& description = "") { - return Generic::PredicateMatcher(predicate, description); - } - -} // namespace Matchers -} // namespace Catch - -// end catch_matchers_generic.hpp -// start catch_matchers_string.h - -#include - -namespace Catch { -namespace Matchers { - - namespace StdString { - - struct CasedString - { - CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ); - std::string adjustString( std::string const& str ) const; - std::string caseSensitivitySuffix() const; - - CaseSensitive::Choice m_caseSensitivity; - std::string m_str; - }; - - struct StringMatcherBase : MatcherBase { - StringMatcherBase( std::string const& operation, CasedString const& comparator ); - std::string describe() const override; - - CasedString m_comparator; - std::string m_operation; - }; - - struct EqualsMatcher : StringMatcherBase { - EqualsMatcher( CasedString const& comparator ); - bool match( std::string const& source ) const override; - }; - struct ContainsMatcher : StringMatcherBase { - ContainsMatcher( CasedString const& comparator ); - bool match( std::string const& source ) const override; - }; - struct StartsWithMatcher : StringMatcherBase { - StartsWithMatcher( CasedString const& comparator ); - bool match( std::string const& source ) const override; - }; - struct EndsWithMatcher : StringMatcherBase { - EndsWithMatcher( CasedString const& comparator ); - bool match( std::string const& source ) const override; - }; - - struct RegexMatcher : MatcherBase { - RegexMatcher( std::string regex, CaseSensitive::Choice caseSensitivity ); - bool match( std::string const& matchee ) const override; - std::string describe() const override; - - private: - std::string m_regex; - CaseSensitive::Choice m_caseSensitivity; - }; - - } // namespace StdString - - // The following functions create the actual matcher objects. - // This allows the types to be inferred - - StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); - StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); - StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); - StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); - StdString::RegexMatcher Matches( std::string const& regex, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); - -} // namespace Matchers -} // namespace Catch - -// end catch_matchers_string.h -// start catch_matchers_vector.h - -#include - -namespace Catch { -namespace Matchers { - - namespace Vector { - namespace Detail { - template - size_t count(InputIterator first, InputIterator last, T const& item) { - size_t cnt = 0; - for (; first != last; ++first) { - if (*first == item) { - ++cnt; - } - } - return cnt; - } - template - bool contains(InputIterator first, InputIterator last, T const& item) { - for (; first != last; ++first) { - if (*first == item) { - return true; - } - } - return false; - } - } - - template - struct ContainsElementMatcher : MatcherBase> { - - ContainsElementMatcher(T const &comparator) : m_comparator( comparator) {} - - bool match(std::vector const &v) const override { - for (auto const& el : v) { - if (el == m_comparator) { - return true; - } - } - return false; - } - - std::string describe() const override { - return "Contains: " + ::Catch::Detail::stringify( m_comparator ); - } - - T const& m_comparator; - }; - - template - struct ContainsMatcher : MatcherBase> { - - ContainsMatcher(std::vector const &comparator) : m_comparator( comparator ) {} - - bool match(std::vector const &v) const override { - // !TBD: see note in EqualsMatcher - if (m_comparator.size() > v.size()) - return false; - for (auto const& comparator : m_comparator) { - auto present = false; - for (const auto& el : v) { - if (el == comparator) { - present = true; - break; - } - } - if (!present) { - return false; - } - } - return true; - } - std::string describe() const override { - return "Contains: " + ::Catch::Detail::stringify( m_comparator ); - } - - std::vector const& m_comparator; - }; - - template - struct EqualsMatcher : MatcherBase> { - - EqualsMatcher(std::vector const &comparator) : m_comparator( comparator ) {} - - bool match(std::vector const &v) const override { - // !TBD: This currently works if all elements can be compared using != - // - a more general approach would be via a compare template that defaults - // to using !=. but could be specialised for, e.g. std::vector etc - // - then just call that directly - if (m_comparator.size() != v.size()) - return false; - for (std::size_t i = 0; i < v.size(); ++i) - if (m_comparator[i] != v[i]) - return false; - return true; - } - std::string describe() const override { - return "Equals: " + ::Catch::Detail::stringify( m_comparator ); - } - std::vector const& m_comparator; - }; - - template - struct UnorderedEqualsMatcher : MatcherBase> { - UnorderedEqualsMatcher(std::vector const& target) : m_target(target) {} - bool match(std::vector const& vec) const override { - // Note: This is a reimplementation of std::is_permutation, - // because I don't want to include inside the common path - if (m_target.size() != vec.size()) { - return false; - } - auto lfirst = m_target.begin(), llast = m_target.end(); - auto rfirst = vec.begin(), rlast = vec.end(); - // Cut common prefix to optimize checking of permuted parts - while (lfirst != llast && *lfirst != *rfirst) { - ++lfirst; ++rfirst; - } - if (lfirst == llast) { - return true; - } - - for (auto mid = lfirst; mid != llast; ++mid) { - // Skip already counted items - if (Detail::contains(lfirst, mid, *mid)) { - continue; - } - size_t num_vec = Detail::count(rfirst, rlast, *mid); - if (num_vec == 0 || Detail::count(lfirst, llast, *mid) != num_vec) { - return false; - } - } - - return true; - } - - std::string describe() const override { - return "UnorderedEquals: " + ::Catch::Detail::stringify(m_target); - } - private: - std::vector const& m_target; - }; - - } // namespace Vector - - // The following functions create the actual matcher objects. - // This allows the types to be inferred - - template - Vector::ContainsMatcher Contains( std::vector const& comparator ) { - return Vector::ContainsMatcher( comparator ); - } - - template - Vector::ContainsElementMatcher VectorContains( T const& comparator ) { - return Vector::ContainsElementMatcher( comparator ); - } - - template - Vector::EqualsMatcher Equals( std::vector const& comparator ) { - return Vector::EqualsMatcher( comparator ); - } - - template - Vector::UnorderedEqualsMatcher UnorderedEquals(std::vector const& target) { - return Vector::UnorderedEqualsMatcher(target); - } - -} // namespace Matchers -} // namespace Catch - -// end catch_matchers_vector.h -namespace Catch { - - template - class MatchExpr : public ITransientExpression { - ArgT const& m_arg; - MatcherT m_matcher; - StringRef m_matcherString; - public: - MatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef matcherString ) - : ITransientExpression{ true, matcher.match( arg ) }, - m_arg( arg ), - m_matcher( matcher ), - m_matcherString( matcherString ) - {} - - void streamReconstructedExpression( std::ostream &os ) const override { - auto matcherAsString = m_matcher.toString(); - os << Catch::Detail::stringify( m_arg ) << ' '; - if( matcherAsString == Detail::unprintableString ) - os << m_matcherString; - else - os << matcherAsString; - } - }; - - using StringMatcher = Matchers::Impl::MatcherBase; - - void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef matcherString ); - - template - auto makeMatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef matcherString ) -> MatchExpr { - return MatchExpr( arg, matcher, matcherString ); - } - -} // namespace Catch - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CHECK_THAT( macroName, matcher, resultDisposition, arg ) \ - do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(arg) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ - INTERNAL_CATCH_TRY { \ - catchAssertionHandler.handleExpr( Catch::makeMatchExpr( arg, matcher, #matcher ) ); \ - } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ - INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( false ) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_THROWS_MATCHES( macroName, exceptionType, resultDisposition, matcher, ... ) \ - do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(exceptionType) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ - if( catchAssertionHandler.allowThrows() ) \ - try { \ - static_cast(__VA_ARGS__ ); \ - catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ - } \ - catch( exceptionType const& ex ) { \ - catchAssertionHandler.handleExpr( Catch::makeMatchExpr( ex, matcher, #matcher ) ); \ - } \ - catch( ... ) { \ - catchAssertionHandler.handleUnexpectedInflightException(); \ - } \ - else \ - catchAssertionHandler.handleThrowingCallSkipped(); \ - INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( false ) - -// end catch_capture_matchers.h -#endif - -// These files are included here so the single_include script doesn't put them -// in the conditionally compiled sections -// start catch_test_case_info.h - -#include -#include -#include - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wpadded" -#endif - -namespace Catch { - - struct ITestInvoker; - - struct TestCaseInfo { - enum SpecialProperties{ - None = 0, - IsHidden = 1 << 1, - ShouldFail = 1 << 2, - MayFail = 1 << 3, - Throws = 1 << 4, - NonPortable = 1 << 5, - Benchmark = 1 << 6 - }; - - TestCaseInfo( std::string const& _name, - std::string const& _className, - std::string const& _description, - std::vector const& _tags, - SourceLineInfo const& _lineInfo ); - - friend void setTags( TestCaseInfo& testCaseInfo, std::vector tags ); - - bool isHidden() const; - bool throws() const; - bool okToFail() const; - bool expectedToFail() const; - - std::string tagsAsString() const; - - std::string name; - std::string className; - std::string description; - std::vector tags; - std::vector lcaseTags; - SourceLineInfo lineInfo; - SpecialProperties properties; - }; - - class TestCase : public TestCaseInfo { - public: - - TestCase( ITestInvoker* testCase, TestCaseInfo&& info ); - - TestCase withName( std::string const& _newName ) const; - - void invoke() const; - - TestCaseInfo const& getTestCaseInfo() const; - - bool operator == ( TestCase const& other ) const; - bool operator < ( TestCase const& other ) const; - - private: - std::shared_ptr test; - }; - - TestCase makeTestCase( ITestInvoker* testCase, - std::string const& className, - NameAndTags const& nameAndTags, - SourceLineInfo const& lineInfo ); -} - -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - -// end catch_test_case_info.h -// start catch_interfaces_runner.h - -namespace Catch { - - struct IRunner { - virtual ~IRunner(); - virtual bool aborting() const = 0; - }; -} - -// end catch_interfaces_runner.h - -#ifdef __OBJC__ -// start catch_objc.hpp - -#import - -#include - -// NB. Any general catch headers included here must be included -// in catch.hpp first to make sure they are included by the single -// header for non obj-usage - -/////////////////////////////////////////////////////////////////////////////// -// This protocol is really only here for (self) documenting purposes, since -// all its methods are optional. -@protocol OcFixture - -@optional - --(void) setUp; --(void) tearDown; - -@end - -namespace Catch { - - class OcMethod : public ITestInvoker { - - public: - OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {} - - virtual void invoke() const { - id obj = [[m_cls alloc] init]; - - performOptionalSelector( obj, @selector(setUp) ); - performOptionalSelector( obj, m_sel ); - performOptionalSelector( obj, @selector(tearDown) ); - - arcSafeRelease( obj ); - } - private: - virtual ~OcMethod() {} - - Class m_cls; - SEL m_sel; - }; - - namespace Detail{ - - inline std::string getAnnotation( Class cls, - std::string const& annotationName, - std::string const& testCaseName ) { - NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()]; - SEL sel = NSSelectorFromString( selStr ); - arcSafeRelease( selStr ); - id value = performOptionalSelector( cls, sel ); - if( value ) - return [(NSString*)value UTF8String]; - return ""; - } - } - - inline std::size_t registerTestMethods() { - std::size_t noTestMethods = 0; - int noClasses = objc_getClassList( nullptr, 0 ); - - Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); - objc_getClassList( classes, noClasses ); - - for( int c = 0; c < noClasses; c++ ) { - Class cls = classes[c]; - { - u_int count; - Method* methods = class_copyMethodList( cls, &count ); - for( u_int m = 0; m < count ; m++ ) { - SEL selector = method_getName(methods[m]); - std::string methodName = sel_getName(selector); - if( startsWith( methodName, "Catch_TestCase_" ) ) { - std::string testCaseName = methodName.substr( 15 ); - std::string name = Detail::getAnnotation( cls, "Name", testCaseName ); - std::string desc = Detail::getAnnotation( cls, "Description", testCaseName ); - const char* className = class_getName( cls ); - - getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, name.c_str(), desc.c_str(), SourceLineInfo("",0) ) ); - noTestMethods++; - } - } - free(methods); - } - } - return noTestMethods; - } - -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) - - namespace Matchers { - namespace Impl { - namespace NSStringMatchers { - - struct StringHolder : MatcherBase{ - StringHolder( NSString* substr ) : m_substr( [substr copy] ){} - StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){} - StringHolder() { - arcSafeRelease( m_substr ); - } - - bool match( NSString* arg ) const override { - return false; - } - - NSString* CATCH_ARC_STRONG m_substr; - }; - - struct Equals : StringHolder { - Equals( NSString* substr ) : StringHolder( substr ){} - - bool match( NSString* str ) const override { - return (str != nil || m_substr == nil ) && - [str isEqualToString:m_substr]; - } - - std::string describe() const override { - return "equals string: " + Catch::Detail::stringify( m_substr ); - } - }; - - struct Contains : StringHolder { - Contains( NSString* substr ) : StringHolder( substr ){} - - bool match( NSString* str ) const { - return (str != nil || m_substr == nil ) && - [str rangeOfString:m_substr].location != NSNotFound; - } - - std::string describe() const override { - return "contains string: " + Catch::Detail::stringify( m_substr ); - } - }; - - struct StartsWith : StringHolder { - StartsWith( NSString* substr ) : StringHolder( substr ){} - - bool match( NSString* str ) const override { - return (str != nil || m_substr == nil ) && - [str rangeOfString:m_substr].location == 0; - } - - std::string describe() const override { - return "starts with: " + Catch::Detail::stringify( m_substr ); - } - }; - struct EndsWith : StringHolder { - EndsWith( NSString* substr ) : StringHolder( substr ){} - - bool match( NSString* str ) const override { - return (str != nil || m_substr == nil ) && - [str rangeOfString:m_substr].location == [str length] - [m_substr length]; - } - - std::string describe() const override { - return "ends with: " + Catch::Detail::stringify( m_substr ); - } - }; - - } // namespace NSStringMatchers - } // namespace Impl - - inline Impl::NSStringMatchers::Equals - Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); } - - inline Impl::NSStringMatchers::Contains - Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); } - - inline Impl::NSStringMatchers::StartsWith - StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); } - - inline Impl::NSStringMatchers::EndsWith - EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); } - - } // namespace Matchers - - using namespace Matchers; - -#endif // CATCH_CONFIG_DISABLE_MATCHERS - -} // namespace Catch - -/////////////////////////////////////////////////////////////////////////////// -#define OC_MAKE_UNIQUE_NAME( root, uniqueSuffix ) root##uniqueSuffix -#define OC_TEST_CASE2( name, desc, uniqueSuffix ) \ -+(NSString*) OC_MAKE_UNIQUE_NAME( Catch_Name_test_, uniqueSuffix ) \ -{ \ -return @ name; \ -} \ -+(NSString*) OC_MAKE_UNIQUE_NAME( Catch_Description_test_, uniqueSuffix ) \ -{ \ -return @ desc; \ -} \ --(void) OC_MAKE_UNIQUE_NAME( Catch_TestCase_test_, uniqueSuffix ) - -#define OC_TEST_CASE( name, desc ) OC_TEST_CASE2( name, desc, __LINE__ ) - -// end catch_objc.hpp -#endif - -#ifdef CATCH_CONFIG_EXTERNAL_INTERFACES -// start catch_external_interfaces.h - -// start catch_reporter_bases.hpp - -// start catch_interfaces_reporter.h - -// start catch_config.hpp - -// start catch_test_spec_parser.h - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wpadded" -#endif - -// start catch_test_spec.h - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wpadded" -#endif - -// start catch_wildcard_pattern.h - -namespace Catch -{ - class WildcardPattern { - enum WildcardPosition { - NoWildcard = 0, - WildcardAtStart = 1, - WildcardAtEnd = 2, - WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd - }; - - public: - - WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity ); - virtual ~WildcardPattern() = default; - virtual bool matches( std::string const& str ) const; - - private: - std::string adjustCase( std::string const& str ) const; - CaseSensitive::Choice m_caseSensitivity; - WildcardPosition m_wildcard = NoWildcard; - std::string m_pattern; - }; -} - -// end catch_wildcard_pattern.h -#include -#include -#include - -namespace Catch { - - class TestSpec { - struct Pattern { - virtual ~Pattern(); - virtual bool matches( TestCaseInfo const& testCase ) const = 0; - }; - using PatternPtr = std::shared_ptr; - - class NamePattern : public Pattern { - public: - NamePattern( std::string const& name ); - virtual ~NamePattern(); - virtual bool matches( TestCaseInfo const& testCase ) const override; - private: - WildcardPattern m_wildcardPattern; - }; - - class TagPattern : public Pattern { - public: - TagPattern( std::string const& tag ); - virtual ~TagPattern(); - virtual bool matches( TestCaseInfo const& testCase ) const override; - private: - std::string m_tag; - }; - - class ExcludedPattern : public Pattern { - public: - ExcludedPattern( PatternPtr const& underlyingPattern ); - virtual ~ExcludedPattern(); - virtual bool matches( TestCaseInfo const& testCase ) const override; - private: - PatternPtr m_underlyingPattern; - }; - - struct Filter { - std::vector m_patterns; - - bool matches( TestCaseInfo const& testCase ) const; - }; - - public: - bool hasFilters() const; - bool matches( TestCaseInfo const& testCase ) const; - - private: - std::vector m_filters; - - friend class TestSpecParser; - }; -} - -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - -// end catch_test_spec.h -// start catch_interfaces_tag_alias_registry.h - -#include - -namespace Catch { - - struct TagAlias; - - struct ITagAliasRegistry { - virtual ~ITagAliasRegistry(); - // Nullptr if not present - virtual TagAlias const* find( std::string const& alias ) const = 0; - virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; - - static ITagAliasRegistry const& get(); - }; - -} // end namespace Catch - -// end catch_interfaces_tag_alias_registry.h -namespace Catch { - - class TestSpecParser { - enum Mode{ None, Name, QuotedName, Tag, EscapedName }; - Mode m_mode = None; - bool m_exclusion = false; - std::size_t m_start = std::string::npos, m_pos = 0; - std::string m_arg; - std::vector m_escapeChars; - TestSpec::Filter m_currentFilter; - TestSpec m_testSpec; - ITagAliasRegistry const* m_tagAliases = nullptr; - - public: - TestSpecParser( ITagAliasRegistry const& tagAliases ); - - TestSpecParser& parse( std::string const& arg ); - TestSpec testSpec(); - - private: - void visitChar( char c ); - void startNewMode( Mode mode, std::size_t start ); - void escape(); - std::string subString() const; - - template - void addPattern() { - std::string token = subString(); - for( std::size_t i = 0; i < m_escapeChars.size(); ++i ) - token = token.substr( 0, m_escapeChars[i]-m_start-i ) + token.substr( m_escapeChars[i]-m_start-i+1 ); - m_escapeChars.clear(); - if( startsWith( token, "exclude:" ) ) { - m_exclusion = true; - token = token.substr( 8 ); - } - if( !token.empty() ) { - TestSpec::PatternPtr pattern = std::make_shared( token ); - if( m_exclusion ) - pattern = std::make_shared( pattern ); - m_currentFilter.m_patterns.push_back( pattern ); - } - m_exclusion = false; - m_mode = None; - } - - void addFilter(); - }; - TestSpec parseTestSpec( std::string const& arg ); - -} // namespace Catch - -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - -// end catch_test_spec_parser.h -// start catch_interfaces_config.h - -#include -#include -#include -#include - -namespace Catch { - - enum class Verbosity { - Quiet = 0, - Normal, - High - }; - - struct WarnAbout { enum What { - Nothing = 0x00, - NoAssertions = 0x01, - NoTests = 0x02 - }; }; - - struct ShowDurations { enum OrNot { - DefaultForReporter, - Always, - Never - }; }; - struct RunTests { enum InWhatOrder { - InDeclarationOrder, - InLexicographicalOrder, - InRandomOrder - }; }; - struct UseColour { enum YesOrNo { - Auto, - Yes, - No - }; }; - struct WaitForKeypress { enum When { - Never, - BeforeStart = 1, - BeforeExit = 2, - BeforeStartAndExit = BeforeStart | BeforeExit - }; }; - - class TestSpec; - - struct IConfig : NonCopyable { - - virtual ~IConfig(); - - virtual bool allowThrows() const = 0; - virtual std::ostream& stream() const = 0; - virtual std::string name() const = 0; - virtual bool includeSuccessfulResults() const = 0; - virtual bool shouldDebugBreak() const = 0; - virtual bool warnAboutMissingAssertions() const = 0; - virtual bool warnAboutNoTests() const = 0; - virtual int abortAfter() const = 0; - virtual bool showInvisibles() const = 0; - virtual ShowDurations::OrNot showDurations() const = 0; - virtual TestSpec const& testSpec() const = 0; - virtual bool hasTestFilters() const = 0; - virtual RunTests::InWhatOrder runOrder() const = 0; - virtual unsigned int rngSeed() const = 0; - virtual int benchmarkResolutionMultiple() const = 0; - virtual UseColour::YesOrNo useColour() const = 0; - virtual std::vector const& getSectionsToRun() const = 0; - virtual Verbosity verbosity() const = 0; - }; - - using IConfigPtr = std::shared_ptr; -} - -// end catch_interfaces_config.h -// Libstdc++ doesn't like incomplete classes for unique_ptr - -#include -#include -#include - -#ifndef CATCH_CONFIG_CONSOLE_WIDTH -#define CATCH_CONFIG_CONSOLE_WIDTH 80 -#endif - -namespace Catch { - - struct IStream; - - struct ConfigData { - bool listTests = false; - bool listTags = false; - bool listReporters = false; - bool listTestNamesOnly = false; - - bool showSuccessfulTests = false; - bool shouldDebugBreak = false; - bool noThrow = false; - bool showHelp = false; - bool showInvisibles = false; - bool filenamesAsTags = false; - bool libIdentify = false; - - int abortAfter = -1; - unsigned int rngSeed = 0; - int benchmarkResolutionMultiple = 100; - - Verbosity verbosity = Verbosity::Normal; - WarnAbout::What warnings = WarnAbout::Nothing; - ShowDurations::OrNot showDurations = ShowDurations::DefaultForReporter; - RunTests::InWhatOrder runOrder = RunTests::InDeclarationOrder; - UseColour::YesOrNo useColour = UseColour::Auto; - WaitForKeypress::When waitForKeypress = WaitForKeypress::Never; - - std::string outputFilename; - std::string name; - std::string processName; -#ifndef CATCH_CONFIG_DEFAULT_REPORTER -#define CATCH_CONFIG_DEFAULT_REPORTER "console" -#endif - std::string reporterName = CATCH_CONFIG_DEFAULT_REPORTER; -#undef CATCH_CONFIG_DEFAULT_REPORTER - - std::vector testsOrTags; - std::vector sectionsToRun; - }; - - class Config : public IConfig { - public: - - Config() = default; - Config( ConfigData const& data ); - virtual ~Config() = default; - - std::string const& getFilename() const; - - bool listTests() const; - bool listTestNamesOnly() const; - bool listTags() const; - bool listReporters() const; - - std::string getProcessName() const; - std::string const& getReporterName() const; - - std::vector const& getTestsOrTags() const; - std::vector const& getSectionsToRun() const override; - - virtual TestSpec const& testSpec() const override; - bool hasTestFilters() const override; - - bool showHelp() const; - - // IConfig interface - bool allowThrows() const override; - std::ostream& stream() const override; - std::string name() const override; - bool includeSuccessfulResults() const override; - bool warnAboutMissingAssertions() const override; - bool warnAboutNoTests() const override; - ShowDurations::OrNot showDurations() const override; - RunTests::InWhatOrder runOrder() const override; - unsigned int rngSeed() const override; - int benchmarkResolutionMultiple() const override; - UseColour::YesOrNo useColour() const override; - bool shouldDebugBreak() const override; - int abortAfter() const override; - bool showInvisibles() const override; - Verbosity verbosity() const override; - - private: - - IStream const* openStream(); - ConfigData m_data; - - std::unique_ptr m_stream; - TestSpec m_testSpec; - bool m_hasTestFilters = false; - }; - -} // end namespace Catch - -// end catch_config.hpp -// start catch_assertionresult.h - -#include - -namespace Catch { - - struct AssertionResultData - { - AssertionResultData() = delete; - - AssertionResultData( ResultWas::OfType _resultType, LazyExpression const& _lazyExpression ); - - std::string message; - mutable std::string reconstructedExpression; - LazyExpression lazyExpression; - ResultWas::OfType resultType; - - std::string reconstructExpression() const; - }; - - class AssertionResult { - public: - AssertionResult() = delete; - AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); - - bool isOk() const; - bool succeeded() const; - ResultWas::OfType getResultType() const; - bool hasExpression() const; - bool hasMessage() const; - std::string getExpression() const; - std::string getExpressionInMacro() const; - bool hasExpandedExpression() const; - std::string getExpandedExpression() const; - std::string getMessage() const; - SourceLineInfo getSourceInfo() const; - StringRef getTestMacroName() const; - - //protected: - AssertionInfo m_info; - AssertionResultData m_resultData; - }; - -} // end namespace Catch - -// end catch_assertionresult.h -// start catch_option.hpp - -namespace Catch { - - // An optional type - template - class Option { - public: - Option() : nullableValue( nullptr ) {} - Option( T const& _value ) - : nullableValue( new( storage ) T( _value ) ) - {} - Option( Option const& _other ) - : nullableValue( _other ? new( storage ) T( *_other ) : nullptr ) - {} - - ~Option() { - reset(); - } - - Option& operator= ( Option const& _other ) { - if( &_other != this ) { - reset(); - if( _other ) - nullableValue = new( storage ) T( *_other ); - } - return *this; - } - Option& operator = ( T const& _value ) { - reset(); - nullableValue = new( storage ) T( _value ); - return *this; - } - - void reset() { - if( nullableValue ) - nullableValue->~T(); - nullableValue = nullptr; - } - - T& operator*() { return *nullableValue; } - T const& operator*() const { return *nullableValue; } - T* operator->() { return nullableValue; } - const T* operator->() const { return nullableValue; } - - T valueOr( T const& defaultValue ) const { - return nullableValue ? *nullableValue : defaultValue; - } - - bool some() const { return nullableValue != nullptr; } - bool none() const { return nullableValue == nullptr; } - - bool operator !() const { return nullableValue == nullptr; } - explicit operator bool() const { - return some(); - } - - private: - T *nullableValue; - alignas(alignof(T)) char storage[sizeof(T)]; - }; - -} // end namespace Catch - -// end catch_option.hpp -#include -#include -#include -#include -#include - -namespace Catch { - - struct ReporterConfig { - explicit ReporterConfig( IConfigPtr const& _fullConfig ); - - ReporterConfig( IConfigPtr const& _fullConfig, std::ostream& _stream ); - - std::ostream& stream() const; - IConfigPtr fullConfig() const; - - private: - std::ostream* m_stream; - IConfigPtr m_fullConfig; - }; - - struct ReporterPreferences { - bool shouldRedirectStdOut = false; - }; - - template - struct LazyStat : Option { - LazyStat& operator=( T const& _value ) { - Option::operator=( _value ); - used = false; - return *this; - } - void reset() { - Option::reset(); - used = false; - } - bool used = false; - }; - - struct TestRunInfo { - TestRunInfo( std::string const& _name ); - std::string name; - }; - struct GroupInfo { - GroupInfo( std::string const& _name, - std::size_t _groupIndex, - std::size_t _groupsCount ); - - std::string name; - std::size_t groupIndex; - std::size_t groupsCounts; - }; - - struct AssertionStats { - AssertionStats( AssertionResult const& _assertionResult, - std::vector const& _infoMessages, - Totals const& _totals ); - - AssertionStats( AssertionStats const& ) = default; - AssertionStats( AssertionStats && ) = default; - AssertionStats& operator = ( AssertionStats const& ) = default; - AssertionStats& operator = ( AssertionStats && ) = default; - virtual ~AssertionStats(); - - AssertionResult assertionResult; - std::vector infoMessages; - Totals totals; - }; - - struct SectionStats { - SectionStats( SectionInfo const& _sectionInfo, - Counts const& _assertions, - double _durationInSeconds, - bool _missingAssertions ); - SectionStats( SectionStats const& ) = default; - SectionStats( SectionStats && ) = default; - SectionStats& operator = ( SectionStats const& ) = default; - SectionStats& operator = ( SectionStats && ) = default; - virtual ~SectionStats(); - - SectionInfo sectionInfo; - Counts assertions; - double durationInSeconds; - bool missingAssertions; - }; - - struct TestCaseStats { - TestCaseStats( TestCaseInfo const& _testInfo, - Totals const& _totals, - std::string const& _stdOut, - std::string const& _stdErr, - bool _aborting ); - - TestCaseStats( TestCaseStats const& ) = default; - TestCaseStats( TestCaseStats && ) = default; - TestCaseStats& operator = ( TestCaseStats const& ) = default; - TestCaseStats& operator = ( TestCaseStats && ) = default; - virtual ~TestCaseStats(); - - TestCaseInfo testInfo; - Totals totals; - std::string stdOut; - std::string stdErr; - bool aborting; - }; - - struct TestGroupStats { - TestGroupStats( GroupInfo const& _groupInfo, - Totals const& _totals, - bool _aborting ); - TestGroupStats( GroupInfo const& _groupInfo ); - - TestGroupStats( TestGroupStats const& ) = default; - TestGroupStats( TestGroupStats && ) = default; - TestGroupStats& operator = ( TestGroupStats const& ) = default; - TestGroupStats& operator = ( TestGroupStats && ) = default; - virtual ~TestGroupStats(); - - GroupInfo groupInfo; - Totals totals; - bool aborting; - }; - - struct TestRunStats { - TestRunStats( TestRunInfo const& _runInfo, - Totals const& _totals, - bool _aborting ); - - TestRunStats( TestRunStats const& ) = default; - TestRunStats( TestRunStats && ) = default; - TestRunStats& operator = ( TestRunStats const& ) = default; - TestRunStats& operator = ( TestRunStats && ) = default; - virtual ~TestRunStats(); - - TestRunInfo runInfo; - Totals totals; - bool aborting; - }; - - struct BenchmarkInfo { - std::string name; - }; - struct BenchmarkStats { - BenchmarkInfo info; - std::size_t iterations; - uint64_t elapsedTimeInNanoseconds; - }; - - struct IStreamingReporter { - virtual ~IStreamingReporter() = default; - - // Implementing class must also provide the following static methods: - // static std::string getDescription(); - // static std::set getSupportedVerbosities() - - virtual ReporterPreferences getPreferences() const = 0; - - virtual void noMatchingTestCases( std::string const& spec ) = 0; - - virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; - virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; - - virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; - virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; - - // *** experimental *** - virtual void benchmarkStarting( BenchmarkInfo const& ) {} - - virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; - - // The return value indicates if the messages buffer should be cleared: - virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; - - // *** experimental *** - virtual void benchmarkEnded( BenchmarkStats const& ) {} - - virtual void sectionEnded( SectionStats const& sectionStats ) = 0; - virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; - virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; - virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; - - virtual void skipTest( TestCaseInfo const& testInfo ) = 0; - - // Default empty implementation provided - virtual void fatalErrorEncountered( StringRef name ); - - virtual bool isMulti() const; - }; - using IStreamingReporterPtr = std::unique_ptr; - - struct IReporterFactory { - virtual ~IReporterFactory(); - virtual IStreamingReporterPtr create( ReporterConfig const& config ) const = 0; - virtual std::string getDescription() const = 0; - }; - using IReporterFactoryPtr = std::shared_ptr; - - struct IReporterRegistry { - using FactoryMap = std::map; - using Listeners = std::vector; - - virtual ~IReporterRegistry(); - virtual IStreamingReporterPtr create( std::string const& name, IConfigPtr const& config ) const = 0; - virtual FactoryMap const& getFactories() const = 0; - virtual Listeners const& getListeners() const = 0; - }; - -} // end namespace Catch - -// end catch_interfaces_reporter.h -#include -#include -#include -#include -#include -#include -#include - -namespace Catch { - void prepareExpandedExpression(AssertionResult& result); - - // Returns double formatted as %.3f (format expected on output) - std::string getFormattedDuration( double duration ); - - template - struct StreamingReporterBase : IStreamingReporter { - - StreamingReporterBase( ReporterConfig const& _config ) - : m_config( _config.fullConfig() ), - stream( _config.stream() ) - { - m_reporterPrefs.shouldRedirectStdOut = false; - if( !DerivedT::getSupportedVerbosities().count( m_config->verbosity() ) ) - throw std::domain_error( "Verbosity level not supported by this reporter" ); - } - - ReporterPreferences getPreferences() const override { - return m_reporterPrefs; - } - - static std::set getSupportedVerbosities() { - return { Verbosity::Normal }; - } - - ~StreamingReporterBase() override = default; - - void noMatchingTestCases(std::string const&) override {} - - void testRunStarting(TestRunInfo const& _testRunInfo) override { - currentTestRunInfo = _testRunInfo; - } - void testGroupStarting(GroupInfo const& _groupInfo) override { - currentGroupInfo = _groupInfo; - } - - void testCaseStarting(TestCaseInfo const& _testInfo) override { - currentTestCaseInfo = _testInfo; - } - void sectionStarting(SectionInfo const& _sectionInfo) override { - m_sectionStack.push_back(_sectionInfo); - } - - void sectionEnded(SectionStats const& /* _sectionStats */) override { - m_sectionStack.pop_back(); - } - void testCaseEnded(TestCaseStats const& /* _testCaseStats */) override { - currentTestCaseInfo.reset(); - } - void testGroupEnded(TestGroupStats const& /* _testGroupStats */) override { - currentGroupInfo.reset(); - } - void testRunEnded(TestRunStats const& /* _testRunStats */) override { - currentTestCaseInfo.reset(); - currentGroupInfo.reset(); - currentTestRunInfo.reset(); - } - - void skipTest(TestCaseInfo const&) override { - // Don't do anything with this by default. - // It can optionally be overridden in the derived class. - } - - IConfigPtr m_config; - std::ostream& stream; - - LazyStat currentTestRunInfo; - LazyStat currentGroupInfo; - LazyStat currentTestCaseInfo; - - std::vector m_sectionStack; - ReporterPreferences m_reporterPrefs; - }; - - template - struct CumulativeReporterBase : IStreamingReporter { - template - struct Node { - explicit Node( T const& _value ) : value( _value ) {} - virtual ~Node() {} - - using ChildNodes = std::vector>; - T value; - ChildNodes children; - }; - struct SectionNode { - explicit SectionNode(SectionStats const& _stats) : stats(_stats) {} - virtual ~SectionNode() = default; - - bool operator == (SectionNode const& other) const { - return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; - } - bool operator == (std::shared_ptr const& other) const { - return operator==(*other); - } - - SectionStats stats; - using ChildSections = std::vector>; - using Assertions = std::vector; - ChildSections childSections; - Assertions assertions; - std::string stdOut; - std::string stdErr; - }; - - struct BySectionInfo { - BySectionInfo( SectionInfo const& other ) : m_other( other ) {} - BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} - bool operator() (std::shared_ptr const& node) const { - return ((node->stats.sectionInfo.name == m_other.name) && - (node->stats.sectionInfo.lineInfo == m_other.lineInfo)); - } - void operator=(BySectionInfo const&) = delete; - - private: - SectionInfo const& m_other; - }; - - using TestCaseNode = Node; - using TestGroupNode = Node; - using TestRunNode = Node; - - CumulativeReporterBase( ReporterConfig const& _config ) - : m_config( _config.fullConfig() ), - stream( _config.stream() ) - { - m_reporterPrefs.shouldRedirectStdOut = false; - if( !DerivedT::getSupportedVerbosities().count( m_config->verbosity() ) ) - throw std::domain_error( "Verbosity level not supported by this reporter" ); - } - ~CumulativeReporterBase() override = default; - - ReporterPreferences getPreferences() const override { - return m_reporterPrefs; - } - - static std::set getSupportedVerbosities() { - return { Verbosity::Normal }; - } - - void testRunStarting( TestRunInfo const& ) override {} - void testGroupStarting( GroupInfo const& ) override {} - - void testCaseStarting( TestCaseInfo const& ) override {} - - void sectionStarting( SectionInfo const& sectionInfo ) override { - SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); - std::shared_ptr node; - if( m_sectionStack.empty() ) { - if( !m_rootSection ) - m_rootSection = std::make_shared( incompleteStats ); - node = m_rootSection; - } - else { - SectionNode& parentNode = *m_sectionStack.back(); - auto it = - std::find_if( parentNode.childSections.begin(), - parentNode.childSections.end(), - BySectionInfo( sectionInfo ) ); - if( it == parentNode.childSections.end() ) { - node = std::make_shared( incompleteStats ); - parentNode.childSections.push_back( node ); - } - else - node = *it; - } - m_sectionStack.push_back( node ); - m_deepestSection = std::move(node); - } - - void assertionStarting(AssertionInfo const&) override {} - - bool assertionEnded(AssertionStats const& assertionStats) override { - assert(!m_sectionStack.empty()); - // AssertionResult holds a pointer to a temporary DecomposedExpression, - // which getExpandedExpression() calls to build the expression string. - // Our section stack copy of the assertionResult will likely outlive the - // temporary, so it must be expanded or discarded now to avoid calling - // a destroyed object later. - prepareExpandedExpression(const_cast( assertionStats.assertionResult ) ); - SectionNode& sectionNode = *m_sectionStack.back(); - sectionNode.assertions.push_back(assertionStats); - return true; - } - void sectionEnded(SectionStats const& sectionStats) override { - assert(!m_sectionStack.empty()); - SectionNode& node = *m_sectionStack.back(); - node.stats = sectionStats; - m_sectionStack.pop_back(); - } - void testCaseEnded(TestCaseStats const& testCaseStats) override { - auto node = std::make_shared(testCaseStats); - assert(m_sectionStack.size() == 0); - node->children.push_back(m_rootSection); - m_testCases.push_back(node); - m_rootSection.reset(); - - assert(m_deepestSection); - m_deepestSection->stdOut = testCaseStats.stdOut; - m_deepestSection->stdErr = testCaseStats.stdErr; - } - void testGroupEnded(TestGroupStats const& testGroupStats) override { - auto node = std::make_shared(testGroupStats); - node->children.swap(m_testCases); - m_testGroups.push_back(node); - } - void testRunEnded(TestRunStats const& testRunStats) override { - auto node = std::make_shared(testRunStats); - node->children.swap(m_testGroups); - m_testRuns.push_back(node); - testRunEndedCumulative(); - } - virtual void testRunEndedCumulative() = 0; - - void skipTest(TestCaseInfo const&) override {} - - IConfigPtr m_config; - std::ostream& stream; - std::vector m_assertions; - std::vector>> m_sections; - std::vector> m_testCases; - std::vector> m_testGroups; - - std::vector> m_testRuns; - - std::shared_ptr m_rootSection; - std::shared_ptr m_deepestSection; - std::vector> m_sectionStack; - ReporterPreferences m_reporterPrefs; - }; - - template - char const* getLineOfChars() { - static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; - if( !*line ) { - std::memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); - line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; - } - return line; - } - - struct TestEventListenerBase : StreamingReporterBase { - TestEventListenerBase( ReporterConfig const& _config ); - - void assertionStarting(AssertionInfo const&) override; - bool assertionEnded(AssertionStats const&) override; - }; - -} // end namespace Catch - -// end catch_reporter_bases.hpp -// start catch_console_colour.h - -namespace Catch { - - struct Colour { - enum Code { - None = 0, - - White, - Red, - Green, - Blue, - Cyan, - Yellow, - Grey, - - Bright = 0x10, - - BrightRed = Bright | Red, - BrightGreen = Bright | Green, - LightGrey = Bright | Grey, - BrightWhite = Bright | White, - BrightYellow = Bright | Yellow, - - // By intention - FileName = LightGrey, - Warning = BrightYellow, - ResultError = BrightRed, - ResultSuccess = BrightGreen, - ResultExpectedFailure = Warning, - - Error = BrightRed, - Success = Green, - - OriginalExpression = Cyan, - ReconstructedExpression = BrightYellow, - - SecondaryText = LightGrey, - Headers = White - }; - - // Use constructed object for RAII guard - Colour( Code _colourCode ); - Colour( Colour&& other ) noexcept; - Colour& operator=( Colour&& other ) noexcept; - ~Colour(); - - // Use static method for one-shot changes - static void use( Code _colourCode ); - - private: - bool m_moved = false; - }; - - std::ostream& operator << ( std::ostream& os, Colour const& ); - -} // end namespace Catch - -// end catch_console_colour.h -// start catch_reporter_registrars.hpp - - -namespace Catch { - - template - class ReporterRegistrar { - - class ReporterFactory : public IReporterFactory { - - virtual IStreamingReporterPtr create( ReporterConfig const& config ) const override { - return std::unique_ptr( new T( config ) ); - } - - virtual std::string getDescription() const override { - return T::getDescription(); - } - }; - - public: - - explicit ReporterRegistrar( std::string const& name ) { - getMutableRegistryHub().registerReporter( name, std::make_shared() ); - } - }; - - template - class ListenerRegistrar { - - class ListenerFactory : public IReporterFactory { - - virtual IStreamingReporterPtr create( ReporterConfig const& config ) const override { - return std::unique_ptr( new T( config ) ); - } - virtual std::string getDescription() const override { - return std::string(); - } - }; - - public: - - ListenerRegistrar() { - getMutableRegistryHub().registerListener( std::make_shared() ); - } - }; -} - -#if !defined(CATCH_CONFIG_DISABLE) - -#define CATCH_REGISTER_REPORTER( name, reporterType ) \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::ReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } \ - CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS - -#define CATCH_REGISTER_LISTENER( listenerType ) \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::ListenerRegistrar catch_internal_RegistrarFor##listenerType; } \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS -#else // CATCH_CONFIG_DISABLE - -#define CATCH_REGISTER_REPORTER(name, reporterType) -#define CATCH_REGISTER_LISTENER(listenerType) - -#endif // CATCH_CONFIG_DISABLE - -// end catch_reporter_registrars.hpp -// Allow users to base their work off existing reporters -// start catch_reporter_compact.h - -namespace Catch { - - struct CompactReporter : StreamingReporterBase { - - using StreamingReporterBase::StreamingReporterBase; - - ~CompactReporter() override; - - static std::string getDescription(); - - ReporterPreferences getPreferences() const override; - - void noMatchingTestCases(std::string const& spec) override; - - void assertionStarting(AssertionInfo const&) override; - - bool assertionEnded(AssertionStats const& _assertionStats) override; - - void sectionEnded(SectionStats const& _sectionStats) override; - - void testRunEnded(TestRunStats const& _testRunStats) override; - - }; - -} // end namespace Catch - -// end catch_reporter_compact.h -// start catch_reporter_console.h - -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch - // Note that 4062 (not all labels are handled - // and default is missing) is enabled -#endif - -namespace Catch { - // Fwd decls - struct SummaryColumn; - class TablePrinter; - - struct ConsoleReporter : StreamingReporterBase { - std::unique_ptr m_tablePrinter; - - ConsoleReporter(ReporterConfig const& config); - ~ConsoleReporter() override; - static std::string getDescription(); - - void noMatchingTestCases(std::string const& spec) override; - - void assertionStarting(AssertionInfo const&) override; - - bool assertionEnded(AssertionStats const& _assertionStats) override; - - void sectionStarting(SectionInfo const& _sectionInfo) override; - void sectionEnded(SectionStats const& _sectionStats) override; - - void benchmarkStarting(BenchmarkInfo const& info) override; - void benchmarkEnded(BenchmarkStats const& stats) override; - - void testCaseEnded(TestCaseStats const& _testCaseStats) override; - void testGroupEnded(TestGroupStats const& _testGroupStats) override; - void testRunEnded(TestRunStats const& _testRunStats) override; - - private: - - void lazyPrint(); - - void lazyPrintWithoutClosingBenchmarkTable(); - void lazyPrintRunInfo(); - void lazyPrintGroupInfo(); - void printTestCaseAndSectionHeader(); - - void printClosedHeader(std::string const& _name); - void printOpenHeader(std::string const& _name); - - // if string has a : in first line will set indent to follow it on - // subsequent lines - void printHeaderString(std::string const& _string, std::size_t indent = 0); - - void printTotals(Totals const& totals); - void printSummaryRow(std::string const& label, std::vector const& cols, std::size_t row); - - void printTotalsDivider(Totals const& totals); - void printSummaryDivider(); - - private: - bool m_headerPrinted = false; - }; - -} // end namespace Catch - -#if defined(_MSC_VER) -#pragma warning(pop) -#endif - -// end catch_reporter_console.h -// start catch_reporter_junit.h - -// start catch_xmlwriter.h - -#include - -namespace Catch { - - class XmlEncode { - public: - enum ForWhat { ForTextNodes, ForAttributes }; - - XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ); - - void encodeTo( std::ostream& os ) const; - - friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ); - - private: - std::string m_str; - ForWhat m_forWhat; - }; - - class XmlWriter { - public: - - class ScopedElement { - public: - ScopedElement( XmlWriter* writer ); - - ScopedElement( ScopedElement&& other ) noexcept; - ScopedElement& operator=( ScopedElement&& other ) noexcept; - - ~ScopedElement(); - - ScopedElement& writeText( std::string const& text, bool indent = true ); - - template - ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { - m_writer->writeAttribute( name, attribute ); - return *this; - } - - private: - mutable XmlWriter* m_writer = nullptr; - }; - - XmlWriter( std::ostream& os = Catch::cout() ); - ~XmlWriter(); - - XmlWriter( XmlWriter const& ) = delete; - XmlWriter& operator=( XmlWriter const& ) = delete; - - XmlWriter& startElement( std::string const& name ); - - ScopedElement scopedElement( std::string const& name ); - - XmlWriter& endElement(); - - XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ); - - XmlWriter& writeAttribute( std::string const& name, bool attribute ); - - template - XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { - ReusableStringStream rss; - rss << attribute; - return writeAttribute( name, rss.str() ); - } - - XmlWriter& writeText( std::string const& text, bool indent = true ); - - XmlWriter& writeComment( std::string const& text ); - - void writeStylesheetRef( std::string const& url ); - - XmlWriter& writeBlankLine(); - - void ensureTagClosed(); - - private: - - void writeDeclaration(); - - void newlineIfNecessary(); - - bool m_tagIsOpen = false; - bool m_needsNewline = false; - std::vector m_tags; - std::string m_indent; - std::ostream& m_os; - }; - -} - -// end catch_xmlwriter.h -namespace Catch { - - class JunitReporter : public CumulativeReporterBase { - public: - JunitReporter(ReporterConfig const& _config); - - ~JunitReporter() override; - - static std::string getDescription(); - - void noMatchingTestCases(std::string const& /*spec*/) override; - - void testRunStarting(TestRunInfo const& runInfo) override; - - void testGroupStarting(GroupInfo const& groupInfo) override; - - void testCaseStarting(TestCaseInfo const& testCaseInfo) override; - bool assertionEnded(AssertionStats const& assertionStats) override; - - void testCaseEnded(TestCaseStats const& testCaseStats) override; - - void testGroupEnded(TestGroupStats const& testGroupStats) override; - - void testRunEndedCumulative() override; - - void writeGroup(TestGroupNode const& groupNode, double suiteTime); - - void writeTestCase(TestCaseNode const& testCaseNode); - - void writeSection(std::string const& className, - std::string const& rootName, - SectionNode const& sectionNode); - - void writeAssertions(SectionNode const& sectionNode); - void writeAssertion(AssertionStats const& stats); - - XmlWriter xml; - Timer suiteTimer; - std::string stdOutForSuite; - std::string stdErrForSuite; - unsigned int unexpectedExceptions = 0; - bool m_okToFail = false; - }; - -} // end namespace Catch - -// end catch_reporter_junit.h -// start catch_reporter_xml.h - -namespace Catch { - class XmlReporter : public StreamingReporterBase { - public: - XmlReporter(ReporterConfig const& _config); - - ~XmlReporter() override; - - static std::string getDescription(); - - virtual std::string getStylesheetRef() const; - - void writeSourceInfo(SourceLineInfo const& sourceInfo); - - public: // StreamingReporterBase - - void noMatchingTestCases(std::string const& s) override; - - void testRunStarting(TestRunInfo const& testInfo) override; - - void testGroupStarting(GroupInfo const& groupInfo) override; - - void testCaseStarting(TestCaseInfo const& testInfo) override; - - void sectionStarting(SectionInfo const& sectionInfo) override; - - void assertionStarting(AssertionInfo const&) override; - - bool assertionEnded(AssertionStats const& assertionStats) override; - - void sectionEnded(SectionStats const& sectionStats) override; - - void testCaseEnded(TestCaseStats const& testCaseStats) override; - - void testGroupEnded(TestGroupStats const& testGroupStats) override; - - void testRunEnded(TestRunStats const& testRunStats) override; - - private: - Timer m_testCaseTimer; - XmlWriter m_xml; - int m_sectionDepth = 0; - }; - -} // end namespace Catch - -// end catch_reporter_xml.h - -// end catch_external_interfaces.h -#endif - -#endif // ! CATCH_CONFIG_IMPL_ONLY - -#ifdef CATCH_IMPL -// start catch_impl.hpp - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wweak-vtables" -#endif - -// Keep these here for external reporters -// start catch_test_case_tracker.h - -#include -#include -#include - -namespace Catch { -namespace TestCaseTracking { - - struct NameAndLocation { - std::string name; - SourceLineInfo location; - - NameAndLocation( std::string const& _name, SourceLineInfo const& _location ); - }; - - struct ITracker; - - using ITrackerPtr = std::shared_ptr; - - struct ITracker { - virtual ~ITracker(); - - // static queries - virtual NameAndLocation const& nameAndLocation() const = 0; - - // dynamic queries - virtual bool isComplete() const = 0; // Successfully completed or failed - virtual bool isSuccessfullyCompleted() const = 0; - virtual bool isOpen() const = 0; // Started but not complete - virtual bool hasChildren() const = 0; - - virtual ITracker& parent() = 0; - - // actions - virtual void close() = 0; // Successfully complete - virtual void fail() = 0; - virtual void markAsNeedingAnotherRun() = 0; - - virtual void addChild( ITrackerPtr const& child ) = 0; - virtual ITrackerPtr findChild( NameAndLocation const& nameAndLocation ) = 0; - virtual void openChild() = 0; - - // Debug/ checking - virtual bool isSectionTracker() const = 0; - virtual bool isIndexTracker() const = 0; - }; - - class TrackerContext { - - enum RunState { - NotStarted, - Executing, - CompletedCycle - }; - - ITrackerPtr m_rootTracker; - ITracker* m_currentTracker = nullptr; - RunState m_runState = NotStarted; - - public: - - static TrackerContext& instance(); - - ITracker& startRun(); - void endRun(); - - void startCycle(); - void completeCycle(); - - bool completedCycle() const; - ITracker& currentTracker(); - void setCurrentTracker( ITracker* tracker ); - }; - - class TrackerBase : public ITracker { - protected: - enum CycleState { - NotStarted, - Executing, - ExecutingChildren, - NeedsAnotherRun, - CompletedSuccessfully, - Failed - }; - - class TrackerHasName { - NameAndLocation m_nameAndLocation; - public: - TrackerHasName( NameAndLocation const& nameAndLocation ); - bool operator ()( ITrackerPtr const& tracker ) const; - }; - - using Children = std::vector; - NameAndLocation m_nameAndLocation; - TrackerContext& m_ctx; - ITracker* m_parent; - Children m_children; - CycleState m_runState = NotStarted; - - public: - TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ); - - NameAndLocation const& nameAndLocation() const override; - bool isComplete() const override; - bool isSuccessfullyCompleted() const override; - bool isOpen() const override; - bool hasChildren() const override; - - void addChild( ITrackerPtr const& child ) override; - - ITrackerPtr findChild( NameAndLocation const& nameAndLocation ) override; - ITracker& parent() override; - - void openChild() override; - - bool isSectionTracker() const override; - bool isIndexTracker() const override; - - void open(); - - void close() override; - void fail() override; - void markAsNeedingAnotherRun() override; - - private: - void moveToParent(); - void moveToThis(); - }; - - class SectionTracker : public TrackerBase { - std::vector m_filters; - public: - SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ); - - bool isSectionTracker() const override; - - static SectionTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ); - - void tryOpen(); - - void addInitialFilters( std::vector const& filters ); - void addNextFilters( std::vector const& filters ); - }; - - class IndexTracker : public TrackerBase { - int m_size; - int m_index = -1; - public: - IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size ); - - bool isIndexTracker() const override; - void close() override; - - static IndexTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ); - - int index() const; - - void moveNext(); - }; - -} // namespace TestCaseTracking - -using TestCaseTracking::ITracker; -using TestCaseTracking::TrackerContext; -using TestCaseTracking::SectionTracker; -using TestCaseTracking::IndexTracker; - -} // namespace Catch - -// end catch_test_case_tracker.h - -// start catch_leak_detector.h - -namespace Catch { - - struct LeakDetector { - LeakDetector(); - }; - -} -// end catch_leak_detector.h -// Cpp files will be included in the single-header file here -// start catch_approx.cpp - -#include -#include - -namespace { - -// Performs equivalent check of std::fabs(lhs - rhs) <= margin -// But without the subtraction to allow for INFINITY in comparison -bool marginComparison(double lhs, double rhs, double margin) { - return (lhs + margin >= rhs) && (rhs + margin >= lhs); -} - -} - -namespace Catch { -namespace Detail { - - Approx::Approx ( double value ) - : m_epsilon( std::numeric_limits::epsilon()*100 ), - m_margin( 0.0 ), - m_scale( 0.0 ), - m_value( value ) - {} - - Approx Approx::custom() { - return Approx( 0 ); - } - - std::string Approx::toString() const { - ReusableStringStream rss; - rss << "Approx( " << ::Catch::Detail::stringify( m_value ) << " )"; - return rss.str(); - } - - bool Approx::equalityComparisonImpl(const double other) const { - // First try with fixed margin, then compute margin based on epsilon, scale and Approx's value - // Thanks to Richard Harris for his help refining the scaled margin value - return marginComparison(m_value, other, m_margin) || marginComparison(m_value, other, m_epsilon * (m_scale + std::fabs(m_value))); - } - -} // end namespace Detail - -std::string StringMaker::convert(Catch::Detail::Approx const& value) { - return value.toString(); -} - -} // end namespace Catch -// end catch_approx.cpp -// start catch_assertionhandler.cpp - -// start catch_context.h - -#include - -namespace Catch { - - struct IResultCapture; - struct IRunner; - struct IConfig; - struct IMutableContext; - - using IConfigPtr = std::shared_ptr; - - struct IContext - { - virtual ~IContext(); - - virtual IResultCapture* getResultCapture() = 0; - virtual IRunner* getRunner() = 0; - virtual IConfigPtr const& getConfig() const = 0; - }; - - struct IMutableContext : IContext - { - virtual ~IMutableContext(); - virtual void setResultCapture( IResultCapture* resultCapture ) = 0; - virtual void setRunner( IRunner* runner ) = 0; - virtual void setConfig( IConfigPtr const& config ) = 0; - - private: - static IMutableContext *currentContext; - friend IMutableContext& getCurrentMutableContext(); - friend void cleanUpContext(); - static void createContext(); - }; - - inline IMutableContext& getCurrentMutableContext() - { - if( !IMutableContext::currentContext ) - IMutableContext::createContext(); - return *IMutableContext::currentContext; - } - - inline IContext& getCurrentContext() - { - return getCurrentMutableContext(); - } - - void cleanUpContext(); -} - -// end catch_context.h -// start catch_debugger.h - -namespace Catch { - bool isDebuggerActive(); -} - -#ifdef CATCH_PLATFORM_MAC - - #define CATCH_TRAP() __asm__("int $3\n" : : ) /* NOLINT */ - -#elif defined(CATCH_PLATFORM_LINUX) - // If we can use inline assembler, do it because this allows us to break - // directly at the location of the failing check instead of breaking inside - // raise() called from it, i.e. one stack frame below. - #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) - #define CATCH_TRAP() asm volatile ("int $3") /* NOLINT */ - #else // Fall back to the generic way. - #include - - #define CATCH_TRAP() raise(SIGTRAP) - #endif -#elif defined(_MSC_VER) - #define CATCH_TRAP() __debugbreak() -#elif defined(__MINGW32__) - extern "C" __declspec(dllimport) void __stdcall DebugBreak(); - #define CATCH_TRAP() DebugBreak() -#endif - -#ifdef CATCH_TRAP - #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } -#else - namespace Catch { - inline void doNothing() {} - } - #define CATCH_BREAK_INTO_DEBUGGER() Catch::doNothing() -#endif - -// end catch_debugger.h -// start catch_run_context.h - -// start catch_fatal_condition.h - -// start catch_windows_h_proxy.h - - -#if defined(CATCH_PLATFORM_WINDOWS) - -#if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) -# define CATCH_DEFINED_NOMINMAX -# define NOMINMAX -#endif -#if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) -# define CATCH_DEFINED_WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -#endif - -#ifdef __AFXDLL -#include -#else -#include -#endif - -#ifdef CATCH_DEFINED_NOMINMAX -# undef NOMINMAX -#endif -#ifdef CATCH_DEFINED_WIN32_LEAN_AND_MEAN -# undef WIN32_LEAN_AND_MEAN -#endif - -#endif // defined(CATCH_PLATFORM_WINDOWS) - -// end catch_windows_h_proxy.h -#if defined( CATCH_CONFIG_WINDOWS_SEH ) - -namespace Catch { - - struct FatalConditionHandler { - - static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo); - FatalConditionHandler(); - static void reset(); - ~FatalConditionHandler(); - - private: - static bool isSet; - static ULONG guaranteeSize; - static PVOID exceptionHandlerHandle; - }; - -} // namespace Catch - -#elif defined ( CATCH_CONFIG_POSIX_SIGNALS ) - -#include - -namespace Catch { - - struct FatalConditionHandler { - - static bool isSet; - static struct sigaction oldSigActions[]; - static stack_t oldSigStack; - static char altStackMem[]; - - static void handleSignal( int sig ); - - FatalConditionHandler(); - ~FatalConditionHandler(); - static void reset(); - }; - -} // namespace Catch - -#else - -namespace Catch { - struct FatalConditionHandler { - void reset(); - }; -} - -#endif - -// end catch_fatal_condition.h -#include - -namespace Catch { - - struct IMutableContext; - - /////////////////////////////////////////////////////////////////////////// - - class RunContext : public IResultCapture, public IRunner { - - public: - RunContext( RunContext const& ) = delete; - RunContext& operator =( RunContext const& ) = delete; - - explicit RunContext( IConfigPtr const& _config, IStreamingReporterPtr&& reporter ); - - ~RunContext() override; - - void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ); - void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ); - - Totals runTest(TestCase const& testCase); - - IConfigPtr config() const; - IStreamingReporter& reporter() const; - - public: // IResultCapture - - // Assertion handlers - void handleExpr - ( AssertionInfo const& info, - ITransientExpression const& expr, - AssertionReaction& reaction ) override; - void handleMessage - ( AssertionInfo const& info, - ResultWas::OfType resultType, - StringRef const& message, - AssertionReaction& reaction ) override; - void handleUnexpectedExceptionNotThrown - ( AssertionInfo const& info, - AssertionReaction& reaction ) override; - void handleUnexpectedInflightException - ( AssertionInfo const& info, - std::string const& message, - AssertionReaction& reaction ) override; - void handleIncomplete - ( AssertionInfo const& info ) override; - void handleNonExpr - ( AssertionInfo const &info, - ResultWas::OfType resultType, - AssertionReaction &reaction ) override; - - bool sectionStarted( SectionInfo const& sectionInfo, Counts& assertions ) override; - - void sectionEnded( SectionEndInfo const& endInfo ) override; - void sectionEndedEarly( SectionEndInfo const& endInfo ) override; - - void benchmarkStarting( BenchmarkInfo const& info ) override; - void benchmarkEnded( BenchmarkStats const& stats ) override; - - void pushScopedMessage( MessageInfo const& message ) override; - void popScopedMessage( MessageInfo const& message ) override; - - std::string getCurrentTestName() const override; - - const AssertionResult* getLastResult() const override; - - void exceptionEarlyReported() override; - - void handleFatalErrorCondition( StringRef message ) override; - - bool lastAssertionPassed() override; - - void assertionPassed() override; - - public: - // !TBD We need to do this another way! - bool aborting() const final; - - private: - - void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ); - void invokeActiveTestCase(); - - void resetAssertionInfo(); - bool testForMissingAssertions( Counts& assertions ); - - void assertionEnded( AssertionResult const& result ); - void reportExpr - ( AssertionInfo const &info, - ResultWas::OfType resultType, - ITransientExpression const *expr, - bool negated ); - - void populateReaction( AssertionReaction& reaction ); - - private: - - void handleUnfinishedSections(); - - TestRunInfo m_runInfo; - IMutableContext& m_context; - TestCase const* m_activeTestCase = nullptr; - ITracker* m_testCaseTracker; - Option m_lastResult; - - IConfigPtr m_config; - Totals m_totals; - IStreamingReporterPtr m_reporter; - std::vector m_messages; - AssertionInfo m_lastAssertionInfo; - std::vector m_unfinishedSections; - std::vector m_activeSections; - TrackerContext m_trackerContext; - bool m_lastAssertionPassed = false; - bool m_shouldReportUnexpected = true; - bool m_includeSuccessfulResults; - }; - -} // end namespace Catch - -// end catch_run_context.h -namespace Catch { - - auto operator <<( std::ostream& os, ITransientExpression const& expr ) -> std::ostream& { - expr.streamReconstructedExpression( os ); - return os; - } - - LazyExpression::LazyExpression( bool isNegated ) - : m_isNegated( isNegated ) - {} - - LazyExpression::LazyExpression( LazyExpression const& other ) : m_isNegated( other.m_isNegated ) {} - - LazyExpression::operator bool() const { - return m_transientExpression != nullptr; - } - - auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream& { - if( lazyExpr.m_isNegated ) - os << "!"; - - if( lazyExpr ) { - if( lazyExpr.m_isNegated && lazyExpr.m_transientExpression->isBinaryExpression() ) - os << "(" << *lazyExpr.m_transientExpression << ")"; - else - os << *lazyExpr.m_transientExpression; - } - else { - os << "{** error - unchecked empty expression requested **}"; - } - return os; - } - - AssertionHandler::AssertionHandler - ( StringRef macroName, - SourceLineInfo const& lineInfo, - StringRef capturedExpression, - ResultDisposition::Flags resultDisposition ) - : m_assertionInfo{ macroName, lineInfo, capturedExpression, resultDisposition }, - m_resultCapture( getResultCapture() ) - {} - - void AssertionHandler::handleExpr( ITransientExpression const& expr ) { - m_resultCapture.handleExpr( m_assertionInfo, expr, m_reaction ); - } - void AssertionHandler::handleMessage(ResultWas::OfType resultType, StringRef const& message) { - m_resultCapture.handleMessage( m_assertionInfo, resultType, message, m_reaction ); - } - - auto AssertionHandler::allowThrows() const -> bool { - return getCurrentContext().getConfig()->allowThrows(); - } - - void AssertionHandler::complete() { - setCompleted(); - if( m_reaction.shouldDebugBreak ) { - - // If you find your debugger stopping you here then go one level up on the - // call-stack for the code that caused it (typically a failed assertion) - - // (To go back to the test and change execution, jump over the throw, next) - CATCH_BREAK_INTO_DEBUGGER(); - } - if( m_reaction.shouldThrow ) - throw Catch::TestFailureException(); - } - void AssertionHandler::setCompleted() { - m_completed = true; - } - - void AssertionHandler::handleUnexpectedInflightException() { - m_resultCapture.handleUnexpectedInflightException( m_assertionInfo, Catch::translateActiveException(), m_reaction ); - } - - void AssertionHandler::handleExceptionThrownAsExpected() { - m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); - } - void AssertionHandler::handleExceptionNotThrownAsExpected() { - m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); - } - - void AssertionHandler::handleUnexpectedExceptionNotThrown() { - m_resultCapture.handleUnexpectedExceptionNotThrown( m_assertionInfo, m_reaction ); - } - - void AssertionHandler::handleThrowingCallSkipped() { - m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); - } - - // This is the overload that takes a string and infers the Equals matcher from it - // The more general overload, that takes any string matcher, is in catch_capture_matchers.cpp - void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef matcherString ) { - handleExceptionMatchExpr( handler, Matchers::Equals( str ), matcherString ); - } - -} // namespace Catch -// end catch_assertionhandler.cpp -// start catch_assertionresult.cpp - -namespace Catch { - AssertionResultData::AssertionResultData(ResultWas::OfType _resultType, LazyExpression const & _lazyExpression): - lazyExpression(_lazyExpression), - resultType(_resultType) {} - - std::string AssertionResultData::reconstructExpression() const { - - if( reconstructedExpression.empty() ) { - if( lazyExpression ) { - ReusableStringStream rss; - rss << lazyExpression; - reconstructedExpression = rss.str(); - } - } - return reconstructedExpression; - } - - AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) - : m_info( info ), - m_resultData( data ) - {} - - // Result was a success - bool AssertionResult::succeeded() const { - return Catch::isOk( m_resultData.resultType ); - } - - // Result was a success, or failure is suppressed - bool AssertionResult::isOk() const { - return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); - } - - ResultWas::OfType AssertionResult::getResultType() const { - return m_resultData.resultType; - } - - bool AssertionResult::hasExpression() const { - return m_info.capturedExpression[0] != 0; - } - - bool AssertionResult::hasMessage() const { - return !m_resultData.message.empty(); - } - - std::string AssertionResult::getExpression() const { - if( isFalseTest( m_info.resultDisposition ) ) - return "!(" + m_info.capturedExpression + ")"; - else - return m_info.capturedExpression; - } - - std::string AssertionResult::getExpressionInMacro() const { - std::string expr; - if( m_info.macroName[0] == 0 ) - expr = m_info.capturedExpression; - else { - expr.reserve( m_info.macroName.size() + m_info.capturedExpression.size() + 4 ); - expr += m_info.macroName; - expr += "( "; - expr += m_info.capturedExpression; - expr += " )"; - } - return expr; - } - - bool AssertionResult::hasExpandedExpression() const { - return hasExpression() && getExpandedExpression() != getExpression(); - } - - std::string AssertionResult::getExpandedExpression() const { - std::string expr = m_resultData.reconstructExpression(); - return expr.empty() - ? getExpression() - : expr; - } - - std::string AssertionResult::getMessage() const { - return m_resultData.message; - } - SourceLineInfo AssertionResult::getSourceInfo() const { - return m_info.lineInfo; - } - - StringRef AssertionResult::getTestMacroName() const { - return m_info.macroName; - } - -} // end namespace Catch -// end catch_assertionresult.cpp -// start catch_benchmark.cpp - -namespace Catch { - - auto BenchmarkLooper::getResolution() -> uint64_t { - return getEstimatedClockResolution() * getCurrentContext().getConfig()->benchmarkResolutionMultiple(); - } - - void BenchmarkLooper::reportStart() { - getResultCapture().benchmarkStarting( { m_name } ); - } - auto BenchmarkLooper::needsMoreIterations() -> bool { - auto elapsed = m_timer.getElapsedNanoseconds(); - - // Exponentially increasing iterations until we're confident in our timer resolution - if( elapsed < m_resolution ) { - m_iterationsToRun *= 10; - return true; - } - - getResultCapture().benchmarkEnded( { { m_name }, m_count, elapsed } ); - return false; - } - -} // end namespace Catch -// end catch_benchmark.cpp -// start catch_capture_matchers.cpp - -namespace Catch { - - using StringMatcher = Matchers::Impl::MatcherBase; - - // This is the general overload that takes a any string matcher - // There is another overload, in catch_assertionhandler.h/.cpp, that only takes a string and infers - // the Equals matcher (so the header does not mention matchers) - void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef matcherString ) { - std::string exceptionMessage = Catch::translateActiveException(); - MatchExpr expr( exceptionMessage, matcher, matcherString ); - handler.handleExpr( expr ); - } - -} // namespace Catch -// end catch_capture_matchers.cpp -// start catch_commandline.cpp - -// start catch_commandline.h - -// start catch_clara.h - -// Use Catch's value for console width (store Clara's off to the side, if present) -#ifdef CLARA_CONFIG_CONSOLE_WIDTH -#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH -#undef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH -#endif -#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH-1 - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wweak-vtables" -#pragma clang diagnostic ignored "-Wexit-time-destructors" -#pragma clang diagnostic ignored "-Wshadow" -#endif - -// start clara.hpp -// Copyright 2017 Two Blue Cubes Ltd. All rights reserved. -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// See https://github.com/philsquared/Clara for more details - -// Clara v1.1.4 - - -#ifndef CATCH_CLARA_CONFIG_CONSOLE_WIDTH -#define CATCH_CLARA_CONFIG_CONSOLE_WIDTH 80 -#endif - -#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH -#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CLARA_CONFIG_CONSOLE_WIDTH -#endif - -#ifndef CLARA_CONFIG_OPTIONAL_TYPE -#ifdef __has_include -#if __has_include() && __cplusplus >= 201703L -#include -#define CLARA_CONFIG_OPTIONAL_TYPE std::optional -#endif -#endif -#endif - -// ----------- #included from clara_textflow.hpp ----------- - -// TextFlowCpp -// -// A single-header library for wrapping and laying out basic text, by Phil Nash -// -// This work is licensed under the BSD 2-Clause license. -// See the accompanying LICENSE file, or the one at https://opensource.org/licenses/BSD-2-Clause -// -// This project is hosted at https://github.com/philsquared/textflowcpp - - -#include -#include -#include -#include - -#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH -#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 80 -#endif - -namespace Catch { namespace clara { namespace TextFlow { - - inline auto isWhitespace( char c ) -> bool { - static std::string chars = " \t\n\r"; - return chars.find( c ) != std::string::npos; - } - inline auto isBreakableBefore( char c ) -> bool { - static std::string chars = "[({<|"; - return chars.find( c ) != std::string::npos; - } - inline auto isBreakableAfter( char c ) -> bool { - static std::string chars = "])}>.,:;*+-=&/\\"; - return chars.find( c ) != std::string::npos; - } - - class Columns; - - class Column { - std::vector m_strings; - size_t m_width = CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH; - size_t m_indent = 0; - size_t m_initialIndent = std::string::npos; - - public: - class iterator { - friend Column; - - Column const& m_column; - size_t m_stringIndex = 0; - size_t m_pos = 0; - - size_t m_len = 0; - size_t m_end = 0; - bool m_suffix = false; - - iterator( Column const& column, size_t stringIndex ) - : m_column( column ), - m_stringIndex( stringIndex ) - {} - - auto line() const -> std::string const& { return m_column.m_strings[m_stringIndex]; } - - auto isBoundary( size_t at ) const -> bool { - assert( at > 0 ); - assert( at <= line().size() ); - - return at == line().size() || - ( isWhitespace( line()[at] ) && !isWhitespace( line()[at-1] ) ) || - isBreakableBefore( line()[at] ) || - isBreakableAfter( line()[at-1] ); - } - - void calcLength() { - assert( m_stringIndex < m_column.m_strings.size() ); - - m_suffix = false; - auto width = m_column.m_width-indent(); - m_end = m_pos; - while( m_end < line().size() && line()[m_end] != '\n' ) - ++m_end; - - if( m_end < m_pos + width ) { - m_len = m_end - m_pos; - } - else { - size_t len = width; - while (len > 0 && !isBoundary(m_pos + len)) - --len; - while (len > 0 && isWhitespace( line()[m_pos + len - 1] )) - --len; - - if (len > 0) { - m_len = len; - } else { - m_suffix = true; - m_len = width - 1; - } - } - } - - auto indent() const -> size_t { - auto initial = m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos; - return initial == std::string::npos ? m_column.m_indent : initial; - } - - auto addIndentAndSuffix(std::string const &plain) const -> std::string { - return std::string( indent(), ' ' ) + (m_suffix ? plain + "-" : plain); - } - - public: - explicit iterator( Column const& column ) : m_column( column ) { - assert( m_column.m_width > m_column.m_indent ); - assert( m_column.m_initialIndent == std::string::npos || m_column.m_width > m_column.m_initialIndent ); - calcLength(); - if( m_len == 0 ) - m_stringIndex++; // Empty string - } - - auto operator *() const -> std::string { - assert( m_stringIndex < m_column.m_strings.size() ); - assert( m_pos <= m_end ); - if( m_pos + m_column.m_width < m_end ) - return addIndentAndSuffix(line().substr(m_pos, m_len)); - else - return addIndentAndSuffix(line().substr(m_pos, m_end - m_pos)); - } - - auto operator ++() -> iterator& { - m_pos += m_len; - if( m_pos < line().size() && line()[m_pos] == '\n' ) - m_pos += 1; - else - while( m_pos < line().size() && isWhitespace( line()[m_pos] ) ) - ++m_pos; - - if( m_pos == line().size() ) { - m_pos = 0; - ++m_stringIndex; - } - if( m_stringIndex < m_column.m_strings.size() ) - calcLength(); - return *this; - } - auto operator ++(int) -> iterator { - iterator prev( *this ); - operator++(); - return prev; - } - - auto operator ==( iterator const& other ) const -> bool { - return - m_pos == other.m_pos && - m_stringIndex == other.m_stringIndex && - &m_column == &other.m_column; - } - auto operator !=( iterator const& other ) const -> bool { - return !operator==( other ); - } - }; - using const_iterator = iterator; - - explicit Column( std::string const& text ) { m_strings.push_back( text ); } - - auto width( size_t newWidth ) -> Column& { - assert( newWidth > 0 ); - m_width = newWidth; - return *this; - } - auto indent( size_t newIndent ) -> Column& { - m_indent = newIndent; - return *this; - } - auto initialIndent( size_t newIndent ) -> Column& { - m_initialIndent = newIndent; - return *this; - } - - auto width() const -> size_t { return m_width; } - auto begin() const -> iterator { return iterator( *this ); } - auto end() const -> iterator { return { *this, m_strings.size() }; } - - inline friend std::ostream& operator << ( std::ostream& os, Column const& col ) { - bool first = true; - for( auto line : col ) { - if( first ) - first = false; - else - os << "\n"; - os << line; - } - return os; - } - - auto operator + ( Column const& other ) -> Columns; - - auto toString() const -> std::string { - std::ostringstream oss; - oss << *this; - return oss.str(); - } - }; - - class Spacer : public Column { - - public: - explicit Spacer( size_t spaceWidth ) : Column( "" ) { - width( spaceWidth ); - } - }; - - class Columns { - std::vector m_columns; - - public: - - class iterator { - friend Columns; - struct EndTag {}; - - std::vector const& m_columns; - std::vector m_iterators; - size_t m_activeIterators; - - iterator( Columns const& columns, EndTag ) - : m_columns( columns.m_columns ), - m_activeIterators( 0 ) - { - m_iterators.reserve( m_columns.size() ); - - for( auto const& col : m_columns ) - m_iterators.push_back( col.end() ); - } - - public: - explicit iterator( Columns const& columns ) - : m_columns( columns.m_columns ), - m_activeIterators( m_columns.size() ) - { - m_iterators.reserve( m_columns.size() ); - - for( auto const& col : m_columns ) - m_iterators.push_back( col.begin() ); - } - - auto operator ==( iterator const& other ) const -> bool { - return m_iterators == other.m_iterators; - } - auto operator !=( iterator const& other ) const -> bool { - return m_iterators != other.m_iterators; - } - auto operator *() const -> std::string { - std::string row, padding; - - for( size_t i = 0; i < m_columns.size(); ++i ) { - auto width = m_columns[i].width(); - if( m_iterators[i] != m_columns[i].end() ) { - std::string col = *m_iterators[i]; - row += padding + col; - if( col.size() < width ) - padding = std::string( width - col.size(), ' ' ); - else - padding = ""; - } - else { - padding += std::string( width, ' ' ); - } - } - return row; - } - auto operator ++() -> iterator& { - for( size_t i = 0; i < m_columns.size(); ++i ) { - if (m_iterators[i] != m_columns[i].end()) - ++m_iterators[i]; - } - return *this; - } - auto operator ++(int) -> iterator { - iterator prev( *this ); - operator++(); - return prev; - } - }; - using const_iterator = iterator; - - auto begin() const -> iterator { return iterator( *this ); } - auto end() const -> iterator { return { *this, iterator::EndTag() }; } - - auto operator += ( Column const& col ) -> Columns& { - m_columns.push_back( col ); - return *this; - } - auto operator + ( Column const& col ) -> Columns { - Columns combined = *this; - combined += col; - return combined; - } - - inline friend std::ostream& operator << ( std::ostream& os, Columns const& cols ) { - - bool first = true; - for( auto line : cols ) { - if( first ) - first = false; - else - os << "\n"; - os << line; - } - return os; - } - - auto toString() const -> std::string { - std::ostringstream oss; - oss << *this; - return oss.str(); - } - }; - - inline auto Column::operator + ( Column const& other ) -> Columns { - Columns cols; - cols += *this; - cols += other; - return cols; - } -}}} // namespace Catch::clara::TextFlow - -// ----------- end of #include from clara_textflow.hpp ----------- -// ........... back in clara.hpp - -#include -#include -#include - -#if !defined(CATCH_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) ) -#define CATCH_PLATFORM_WINDOWS -#endif - -namespace Catch { namespace clara { -namespace detail { - - // Traits for extracting arg and return type of lambdas (for single argument lambdas) - template - struct UnaryLambdaTraits : UnaryLambdaTraits {}; - - template - struct UnaryLambdaTraits { - static const bool isValid = false; - }; - - template - struct UnaryLambdaTraits { - static const bool isValid = true; - using ArgType = typename std::remove_const::type>::type; - using ReturnType = ReturnT; - }; - - class TokenStream; - - // Transport for raw args (copied from main args, or supplied via init list for testing) - class Args { - friend TokenStream; - std::string m_exeName; - std::vector m_args; - - public: - Args( int argc, char const* const* argv ) - : m_exeName(argv[0]), - m_args(argv + 1, argv + argc) {} - - Args( std::initializer_list args ) - : m_exeName( *args.begin() ), - m_args( args.begin()+1, args.end() ) - {} - - auto exeName() const -> std::string { - return m_exeName; - } - }; - - // Wraps a token coming from a token stream. These may not directly correspond to strings as a single string - // may encode an option + its argument if the : or = form is used - enum class TokenType { - Option, Argument - }; - struct Token { - TokenType type; - std::string token; - }; - - inline auto isOptPrefix( char c ) -> bool { - return c == '-' -#ifdef CATCH_PLATFORM_WINDOWS - || c == '/' -#endif - ; - } - - // Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled - class TokenStream { - using Iterator = std::vector::const_iterator; - Iterator it; - Iterator itEnd; - std::vector m_tokenBuffer; - - void loadBuffer() { - m_tokenBuffer.resize( 0 ); - - // Skip any empty strings - while( it != itEnd && it->empty() ) - ++it; - - if( it != itEnd ) { - auto const &next = *it; - if( isOptPrefix( next[0] ) ) { - auto delimiterPos = next.find_first_of( " :=" ); - if( delimiterPos != std::string::npos ) { - m_tokenBuffer.push_back( { TokenType::Option, next.substr( 0, delimiterPos ) } ); - m_tokenBuffer.push_back( { TokenType::Argument, next.substr( delimiterPos + 1 ) } ); - } else { - if( next[1] != '-' && next.size() > 2 ) { - std::string opt = "- "; - for( size_t i = 1; i < next.size(); ++i ) { - opt[1] = next[i]; - m_tokenBuffer.push_back( { TokenType::Option, opt } ); - } - } else { - m_tokenBuffer.push_back( { TokenType::Option, next } ); - } - } - } else { - m_tokenBuffer.push_back( { TokenType::Argument, next } ); - } - } - } - - public: - explicit TokenStream( Args const &args ) : TokenStream( args.m_args.begin(), args.m_args.end() ) {} - - TokenStream( Iterator it, Iterator itEnd ) : it( it ), itEnd( itEnd ) { - loadBuffer(); - } - - explicit operator bool() const { - return !m_tokenBuffer.empty() || it != itEnd; - } - - auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); } - - auto operator*() const -> Token { - assert( !m_tokenBuffer.empty() ); - return m_tokenBuffer.front(); - } - - auto operator->() const -> Token const * { - assert( !m_tokenBuffer.empty() ); - return &m_tokenBuffer.front(); - } - - auto operator++() -> TokenStream & { - if( m_tokenBuffer.size() >= 2 ) { - m_tokenBuffer.erase( m_tokenBuffer.begin() ); - } else { - if( it != itEnd ) - ++it; - loadBuffer(); - } - return *this; - } - }; - - class ResultBase { - public: - enum Type { - Ok, LogicError, RuntimeError - }; - - protected: - ResultBase( Type type ) : m_type( type ) {} - virtual ~ResultBase() = default; - - virtual void enforceOk() const = 0; - - Type m_type; - }; - - template - class ResultValueBase : public ResultBase { - public: - auto value() const -> T const & { - enforceOk(); - return m_value; - } - - protected: - ResultValueBase( Type type ) : ResultBase( type ) {} - - ResultValueBase( ResultValueBase const &other ) : ResultBase( other ) { - if( m_type == ResultBase::Ok ) - new( &m_value ) T( other.m_value ); - } - - ResultValueBase( Type, T const &value ) : ResultBase( Ok ) { - new( &m_value ) T( value ); - } - - auto operator=( ResultValueBase const &other ) -> ResultValueBase & { - if( m_type == ResultBase::Ok ) - m_value.~T(); - ResultBase::operator=(other); - if( m_type == ResultBase::Ok ) - new( &m_value ) T( other.m_value ); - return *this; - } - - ~ResultValueBase() override { - if( m_type == Ok ) - m_value.~T(); - } - - union { - T m_value; - }; - }; - - template<> - class ResultValueBase : public ResultBase { - protected: - using ResultBase::ResultBase; - }; - - template - class BasicResult : public ResultValueBase { - public: - template - explicit BasicResult( BasicResult const &other ) - : ResultValueBase( other.type() ), - m_errorMessage( other.errorMessage() ) - { - assert( type() != ResultBase::Ok ); - } - - template - static auto ok( U const &value ) -> BasicResult { return { ResultBase::Ok, value }; } - static auto ok() -> BasicResult { return { ResultBase::Ok }; } - static auto logicError( std::string const &message ) -> BasicResult { return { ResultBase::LogicError, message }; } - static auto runtimeError( std::string const &message ) -> BasicResult { return { ResultBase::RuntimeError, message }; } - - explicit operator bool() const { return m_type == ResultBase::Ok; } - auto type() const -> ResultBase::Type { return m_type; } - auto errorMessage() const -> std::string { return m_errorMessage; } - - protected: - void enforceOk() const override { - - // Errors shouldn't reach this point, but if they do - // the actual error message will be in m_errorMessage - assert( m_type != ResultBase::LogicError ); - assert( m_type != ResultBase::RuntimeError ); - if( m_type != ResultBase::Ok ) - std::abort(); - } - - std::string m_errorMessage; // Only populated if resultType is an error - - BasicResult( ResultBase::Type type, std::string const &message ) - : ResultValueBase(type), - m_errorMessage(message) - { - assert( m_type != ResultBase::Ok ); - } - - using ResultValueBase::ResultValueBase; - using ResultBase::m_type; - }; - - enum class ParseResultType { - Matched, NoMatch, ShortCircuitAll, ShortCircuitSame - }; - - class ParseState { - public: - - ParseState( ParseResultType type, TokenStream const &remainingTokens ) - : m_type(type), - m_remainingTokens( remainingTokens ) - {} - - auto type() const -> ParseResultType { return m_type; } - auto remainingTokens() const -> TokenStream { return m_remainingTokens; } - - private: - ParseResultType m_type; - TokenStream m_remainingTokens; - }; - - using Result = BasicResult; - using ParserResult = BasicResult; - using InternalParseResult = BasicResult; - - struct HelpColumns { - std::string left; - std::string right; - }; - - template - inline auto convertInto( std::string const &source, T& target ) -> ParserResult { - std::stringstream ss; - ss << source; - ss >> target; - if( ss.fail() ) - return ParserResult::runtimeError( "Unable to convert '" + source + "' to destination type" ); - else - return ParserResult::ok( ParseResultType::Matched ); - } - inline auto convertInto( std::string const &source, std::string& target ) -> ParserResult { - target = source; - return ParserResult::ok( ParseResultType::Matched ); - } - inline auto convertInto( std::string const &source, bool &target ) -> ParserResult { - std::string srcLC = source; - std::transform( srcLC.begin(), srcLC.end(), srcLC.begin(), []( char c ) { return static_cast( ::tolower(c) ); } ); - if (srcLC == "y" || srcLC == "1" || srcLC == "true" || srcLC == "yes" || srcLC == "on") - target = true; - else if (srcLC == "n" || srcLC == "0" || srcLC == "false" || srcLC == "no" || srcLC == "off") - target = false; - else - return ParserResult::runtimeError( "Expected a boolean value but did not recognise: '" + source + "'" ); - return ParserResult::ok( ParseResultType::Matched ); - } -#ifdef CLARA_CONFIG_OPTIONAL_TYPE - template - inline auto convertInto( std::string const &source, CLARA_CONFIG_OPTIONAL_TYPE& target ) -> ParserResult { - T temp; - auto result = convertInto( source, temp ); - if( result ) - target = std::move(temp); - return result; - } -#endif // CLARA_CONFIG_OPTIONAL_TYPE - - struct NonCopyable { - NonCopyable() = default; - NonCopyable( NonCopyable const & ) = delete; - NonCopyable( NonCopyable && ) = delete; - NonCopyable &operator=( NonCopyable const & ) = delete; - NonCopyable &operator=( NonCopyable && ) = delete; - }; - - struct BoundRef : NonCopyable { - virtual ~BoundRef() = default; - virtual auto isContainer() const -> bool { return false; } - virtual auto isFlag() const -> bool { return false; } - }; - struct BoundValueRefBase : BoundRef { - virtual auto setValue( std::string const &arg ) -> ParserResult = 0; - }; - struct BoundFlagRefBase : BoundRef { - virtual auto setFlag( bool flag ) -> ParserResult = 0; - virtual auto isFlag() const -> bool { return true; } - }; - - template - struct BoundValueRef : BoundValueRefBase { - T &m_ref; - - explicit BoundValueRef( T &ref ) : m_ref( ref ) {} - - auto setValue( std::string const &arg ) -> ParserResult override { - return convertInto( arg, m_ref ); - } - }; - - template - struct BoundValueRef> : BoundValueRefBase { - std::vector &m_ref; - - explicit BoundValueRef( std::vector &ref ) : m_ref( ref ) {} - - auto isContainer() const -> bool override { return true; } - - auto setValue( std::string const &arg ) -> ParserResult override { - T temp; - auto result = convertInto( arg, temp ); - if( result ) - m_ref.push_back( temp ); - return result; - } - }; - - struct BoundFlagRef : BoundFlagRefBase { - bool &m_ref; - - explicit BoundFlagRef( bool &ref ) : m_ref( ref ) {} - - auto setFlag( bool flag ) -> ParserResult override { - m_ref = flag; - return ParserResult::ok( ParseResultType::Matched ); - } - }; - - template - struct LambdaInvoker { - static_assert( std::is_same::value, "Lambda must return void or clara::ParserResult" ); - - template - static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { - return lambda( arg ); - } - }; - - template<> - struct LambdaInvoker { - template - static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { - lambda( arg ); - return ParserResult::ok( ParseResultType::Matched ); - } - }; - - template - inline auto invokeLambda( L const &lambda, std::string const &arg ) -> ParserResult { - ArgType temp{}; - auto result = convertInto( arg, temp ); - return !result - ? result - : LambdaInvoker::ReturnType>::invoke( lambda, temp ); - } - - template - struct BoundLambda : BoundValueRefBase { - L m_lambda; - - static_assert( UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument" ); - explicit BoundLambda( L const &lambda ) : m_lambda( lambda ) {} - - auto setValue( std::string const &arg ) -> ParserResult override { - return invokeLambda::ArgType>( m_lambda, arg ); - } - }; - - template - struct BoundFlagLambda : BoundFlagRefBase { - L m_lambda; - - static_assert( UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument" ); - static_assert( std::is_same::ArgType, bool>::value, "flags must be boolean" ); - - explicit BoundFlagLambda( L const &lambda ) : m_lambda( lambda ) {} - - auto setFlag( bool flag ) -> ParserResult override { - return LambdaInvoker::ReturnType>::invoke( m_lambda, flag ); - } - }; - - enum class Optionality { Optional, Required }; - - struct Parser; - - class ParserBase { - public: - virtual ~ParserBase() = default; - virtual auto validate() const -> Result { return Result::ok(); } - virtual auto parse( std::string const& exeName, TokenStream const &tokens) const -> InternalParseResult = 0; - virtual auto cardinality() const -> size_t { return 1; } - - auto parse( Args const &args ) const -> InternalParseResult { - return parse( args.exeName(), TokenStream( args ) ); - } - }; - - template - class ComposableParserImpl : public ParserBase { - public: - template - auto operator|( T const &other ) const -> Parser; - - template - auto operator+( T const &other ) const -> Parser; - }; - - // Common code and state for Args and Opts - template - class ParserRefImpl : public ComposableParserImpl { - protected: - Optionality m_optionality = Optionality::Optional; - std::shared_ptr m_ref; - std::string m_hint; - std::string m_description; - - explicit ParserRefImpl( std::shared_ptr const &ref ) : m_ref( ref ) {} - - public: - template - ParserRefImpl( T &ref, std::string const &hint ) - : m_ref( std::make_shared>( ref ) ), - m_hint( hint ) - {} - - template - ParserRefImpl( LambdaT const &ref, std::string const &hint ) - : m_ref( std::make_shared>( ref ) ), - m_hint(hint) - {} - - auto operator()( std::string const &description ) -> DerivedT & { - m_description = description; - return static_cast( *this ); - } - - auto optional() -> DerivedT & { - m_optionality = Optionality::Optional; - return static_cast( *this ); - }; - - auto required() -> DerivedT & { - m_optionality = Optionality::Required; - return static_cast( *this ); - }; - - auto isOptional() const -> bool { - return m_optionality == Optionality::Optional; - } - - auto cardinality() const -> size_t override { - if( m_ref->isContainer() ) - return 0; - else - return 1; - } - - auto hint() const -> std::string { return m_hint; } - }; - - class ExeName : public ComposableParserImpl { - std::shared_ptr m_name; - std::shared_ptr m_ref; - - template - static auto makeRef(LambdaT const &lambda) -> std::shared_ptr { - return std::make_shared>( lambda) ; - } - - public: - ExeName() : m_name( std::make_shared( "" ) ) {} - - explicit ExeName( std::string &ref ) : ExeName() { - m_ref = std::make_shared>( ref ); - } - - template - explicit ExeName( LambdaT const& lambda ) : ExeName() { - m_ref = std::make_shared>( lambda ); - } - - // The exe name is not parsed out of the normal tokens, but is handled specially - auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { - return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); - } - - auto name() const -> std::string { return *m_name; } - auto set( std::string const& newName ) -> ParserResult { - - auto lastSlash = newName.find_last_of( "\\/" ); - auto filename = ( lastSlash == std::string::npos ) - ? newName - : newName.substr( lastSlash+1 ); - - *m_name = filename; - if( m_ref ) - return m_ref->setValue( filename ); - else - return ParserResult::ok( ParseResultType::Matched ); - } - }; - - class Arg : public ParserRefImpl { - public: - using ParserRefImpl::ParserRefImpl; - - auto parse( std::string const &, TokenStream const &tokens ) const -> InternalParseResult override { - auto validationResult = validate(); - if( !validationResult ) - return InternalParseResult( validationResult ); - - auto remainingTokens = tokens; - auto const &token = *remainingTokens; - if( token.type != TokenType::Argument ) - return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); - - assert( !m_ref->isFlag() ); - auto valueRef = static_cast( m_ref.get() ); - - auto result = valueRef->setValue( remainingTokens->token ); - if( !result ) - return InternalParseResult( result ); - else - return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); - } - }; - - inline auto normaliseOpt( std::string const &optName ) -> std::string { -#ifdef CATCH_PLATFORM_WINDOWS - if( optName[0] == '/' ) - return "-" + optName.substr( 1 ); - else -#endif - return optName; - } - - class Opt : public ParserRefImpl { - protected: - std::vector m_optNames; - - public: - template - explicit Opt( LambdaT const &ref ) : ParserRefImpl( std::make_shared>( ref ) ) {} - - explicit Opt( bool &ref ) : ParserRefImpl( std::make_shared( ref ) ) {} - - template - Opt( LambdaT const &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} - - template - Opt( T &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} - - auto operator[]( std::string const &optName ) -> Opt & { - m_optNames.push_back( optName ); - return *this; - } - - auto getHelpColumns() const -> std::vector { - std::ostringstream oss; - bool first = true; - for( auto const &opt : m_optNames ) { - if (first) - first = false; - else - oss << ", "; - oss << opt; - } - if( !m_hint.empty() ) - oss << " <" << m_hint << ">"; - return { { oss.str(), m_description } }; - } - - auto isMatch( std::string const &optToken ) const -> bool { - auto normalisedToken = normaliseOpt( optToken ); - for( auto const &name : m_optNames ) { - if( normaliseOpt( name ) == normalisedToken ) - return true; - } - return false; - } - - using ParserBase::parse; - - auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { - auto validationResult = validate(); - if( !validationResult ) - return InternalParseResult( validationResult ); - - auto remainingTokens = tokens; - if( remainingTokens && remainingTokens->type == TokenType::Option ) { - auto const &token = *remainingTokens; - if( isMatch(token.token ) ) { - if( m_ref->isFlag() ) { - auto flagRef = static_cast( m_ref.get() ); - auto result = flagRef->setFlag( true ); - if( !result ) - return InternalParseResult( result ); - if( result.value() == ParseResultType::ShortCircuitAll ) - return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); - } else { - auto valueRef = static_cast( m_ref.get() ); - ++remainingTokens; - if( !remainingTokens ) - return InternalParseResult::runtimeError( "Expected argument following " + token.token ); - auto const &argToken = *remainingTokens; - if( argToken.type != TokenType::Argument ) - return InternalParseResult::runtimeError( "Expected argument following " + token.token ); - auto result = valueRef->setValue( argToken.token ); - if( !result ) - return InternalParseResult( result ); - if( result.value() == ParseResultType::ShortCircuitAll ) - return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); - } - return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); - } - } - return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); - } - - auto validate() const -> Result override { - if( m_optNames.empty() ) - return Result::logicError( "No options supplied to Opt" ); - for( auto const &name : m_optNames ) { - if( name.empty() ) - return Result::logicError( "Option name cannot be empty" ); -#ifdef CATCH_PLATFORM_WINDOWS - if( name[0] != '-' && name[0] != '/' ) - return Result::logicError( "Option name must begin with '-' or '/'" ); -#else - if( name[0] != '-' ) - return Result::logicError( "Option name must begin with '-'" ); -#endif - } - return ParserRefImpl::validate(); - } - }; - - struct Help : Opt { - Help( bool &showHelpFlag ) - : Opt([&]( bool flag ) { - showHelpFlag = flag; - return ParserResult::ok( ParseResultType::ShortCircuitAll ); - }) - { - static_cast( *this ) - ("display usage information") - ["-?"]["-h"]["--help"] - .optional(); - } - }; - - struct Parser : ParserBase { - - mutable ExeName m_exeName; - std::vector m_options; - std::vector m_args; - - auto operator|=( ExeName const &exeName ) -> Parser & { - m_exeName = exeName; - return *this; - } - - auto operator|=( Arg const &arg ) -> Parser & { - m_args.push_back(arg); - return *this; - } - - auto operator|=( Opt const &opt ) -> Parser & { - m_options.push_back(opt); - return *this; - } - - auto operator|=( Parser const &other ) -> Parser & { - m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end()); - m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end()); - return *this; - } - - template - auto operator|( T const &other ) const -> Parser { - return Parser( *this ) |= other; - } - - // Forward deprecated interface with '+' instead of '|' - template - auto operator+=( T const &other ) -> Parser & { return operator|=( other ); } - template - auto operator+( T const &other ) const -> Parser { return operator|( other ); } - - auto getHelpColumns() const -> std::vector { - std::vector cols; - for (auto const &o : m_options) { - auto childCols = o.getHelpColumns(); - cols.insert( cols.end(), childCols.begin(), childCols.end() ); - } - return cols; - } - - void writeToStream( std::ostream &os ) const { - if (!m_exeName.name().empty()) { - os << "usage:\n" << " " << m_exeName.name() << " "; - bool required = true, first = true; - for( auto const &arg : m_args ) { - if (first) - first = false; - else - os << " "; - if( arg.isOptional() && required ) { - os << "["; - required = false; - } - os << "<" << arg.hint() << ">"; - if( arg.cardinality() == 0 ) - os << " ... "; - } - if( !required ) - os << "]"; - if( !m_options.empty() ) - os << " options"; - os << "\n\nwhere options are:" << std::endl; - } - - auto rows = getHelpColumns(); - size_t consoleWidth = CATCH_CLARA_CONFIG_CONSOLE_WIDTH; - size_t optWidth = 0; - for( auto const &cols : rows ) - optWidth = (std::max)(optWidth, cols.left.size() + 2); - - optWidth = (std::min)(optWidth, consoleWidth/2); - - for( auto const &cols : rows ) { - auto row = - TextFlow::Column( cols.left ).width( optWidth ).indent( 2 ) + - TextFlow::Spacer(4) + - TextFlow::Column( cols.right ).width( consoleWidth - 7 - optWidth ); - os << row << std::endl; - } - } - - friend auto operator<<( std::ostream &os, Parser const &parser ) -> std::ostream& { - parser.writeToStream( os ); - return os; - } - - auto validate() const -> Result override { - for( auto const &opt : m_options ) { - auto result = opt.validate(); - if( !result ) - return result; - } - for( auto const &arg : m_args ) { - auto result = arg.validate(); - if( !result ) - return result; - } - return Result::ok(); - } - - using ParserBase::parse; - - auto parse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override { - - struct ParserInfo { - ParserBase const* parser = nullptr; - size_t count = 0; - }; - const size_t totalParsers = m_options.size() + m_args.size(); - assert( totalParsers < 512 ); - // ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do - ParserInfo parseInfos[512]; - - { - size_t i = 0; - for (auto const &opt : m_options) parseInfos[i++].parser = &opt; - for (auto const &arg : m_args) parseInfos[i++].parser = &arg; - } - - m_exeName.set( exeName ); - - auto result = InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); - while( result.value().remainingTokens() ) { - bool tokenParsed = false; - - for( size_t i = 0; i < totalParsers; ++i ) { - auto& parseInfo = parseInfos[i]; - if( parseInfo.parser->cardinality() == 0 || parseInfo.count < parseInfo.parser->cardinality() ) { - result = parseInfo.parser->parse(exeName, result.value().remainingTokens()); - if (!result) - return result; - if (result.value().type() != ParseResultType::NoMatch) { - tokenParsed = true; - ++parseInfo.count; - break; - } - } - } - - if( result.value().type() == ParseResultType::ShortCircuitAll ) - return result; - if( !tokenParsed ) - return InternalParseResult::runtimeError( "Unrecognised token: " + result.value().remainingTokens()->token ); - } - // !TBD Check missing required options - return result; - } - }; - - template - template - auto ComposableParserImpl::operator|( T const &other ) const -> Parser { - return Parser() | static_cast( *this ) | other; - } -} // namespace detail - -// A Combined parser -using detail::Parser; - -// A parser for options -using detail::Opt; - -// A parser for arguments -using detail::Arg; - -// Wrapper for argc, argv from main() -using detail::Args; - -// Specifies the name of the executable -using detail::ExeName; - -// Convenience wrapper for option parser that specifies the help option -using detail::Help; - -// enum of result types from a parse -using detail::ParseResultType; - -// Result type for parser operation -using detail::ParserResult; - -}} // namespace Catch::clara - -// end clara.hpp -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - -// Restore Clara's value for console width, if present -#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH -#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH -#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH -#endif - -// end catch_clara.h -namespace Catch { - - clara::Parser makeCommandLineParser( ConfigData& config ); - -} // end namespace Catch - -// end catch_commandline.h -#include -#include - -namespace Catch { - - clara::Parser makeCommandLineParser( ConfigData& config ) { - - using namespace clara; - - auto const setWarning = [&]( std::string const& warning ) { - auto warningSet = [&]() { - if( warning == "NoAssertions" ) - return WarnAbout::NoAssertions; - - if ( warning == "NoTests" ) - return WarnAbout::NoTests; - - return WarnAbout::Nothing; - }(); - - if (warningSet == WarnAbout::Nothing) - return ParserResult::runtimeError( "Unrecognised warning: '" + warning + "'" ); - config.warnings = static_cast( config.warnings | warningSet ); - return ParserResult::ok( ParseResultType::Matched ); - }; - auto const loadTestNamesFromFile = [&]( std::string const& filename ) { - std::ifstream f( filename.c_str() ); - if( !f.is_open() ) - return ParserResult::runtimeError( "Unable to load input file: '" + filename + "'" ); - - std::string line; - while( std::getline( f, line ) ) { - line = trim(line); - if( !line.empty() && !startsWith( line, '#' ) ) { - if( !startsWith( line, '"' ) ) - line = '"' + line + '"'; - config.testsOrTags.push_back( line + ',' ); - } - } - return ParserResult::ok( ParseResultType::Matched ); - }; - auto const setTestOrder = [&]( std::string const& order ) { - if( startsWith( "declared", order ) ) - config.runOrder = RunTests::InDeclarationOrder; - else if( startsWith( "lexical", order ) ) - config.runOrder = RunTests::InLexicographicalOrder; - else if( startsWith( "random", order ) ) - config.runOrder = RunTests::InRandomOrder; - else - return clara::ParserResult::runtimeError( "Unrecognised ordering: '" + order + "'" ); - return ParserResult::ok( ParseResultType::Matched ); - }; - auto const setRngSeed = [&]( std::string const& seed ) { - if( seed != "time" ) - return clara::detail::convertInto( seed, config.rngSeed ); - config.rngSeed = static_cast( std::time(nullptr) ); - return ParserResult::ok( ParseResultType::Matched ); - }; - auto const setColourUsage = [&]( std::string const& useColour ) { - auto mode = toLower( useColour ); - - if( mode == "yes" ) - config.useColour = UseColour::Yes; - else if( mode == "no" ) - config.useColour = UseColour::No; - else if( mode == "auto" ) - config.useColour = UseColour::Auto; - else - return ParserResult::runtimeError( "colour mode must be one of: auto, yes or no. '" + useColour + "' not recognised" ); - return ParserResult::ok( ParseResultType::Matched ); - }; - auto const setWaitForKeypress = [&]( std::string const& keypress ) { - auto keypressLc = toLower( keypress ); - if( keypressLc == "start" ) - config.waitForKeypress = WaitForKeypress::BeforeStart; - else if( keypressLc == "exit" ) - config.waitForKeypress = WaitForKeypress::BeforeExit; - else if( keypressLc == "both" ) - config.waitForKeypress = WaitForKeypress::BeforeStartAndExit; - else - return ParserResult::runtimeError( "keypress argument must be one of: start, exit or both. '" + keypress + "' not recognised" ); - return ParserResult::ok( ParseResultType::Matched ); - }; - auto const setVerbosity = [&]( std::string const& verbosity ) { - auto lcVerbosity = toLower( verbosity ); - if( lcVerbosity == "quiet" ) - config.verbosity = Verbosity::Quiet; - else if( lcVerbosity == "normal" ) - config.verbosity = Verbosity::Normal; - else if( lcVerbosity == "high" ) - config.verbosity = Verbosity::High; - else - return ParserResult::runtimeError( "Unrecognised verbosity, '" + verbosity + "'" ); - return ParserResult::ok( ParseResultType::Matched ); - }; - - auto cli - = ExeName( config.processName ) - | Help( config.showHelp ) - | Opt( config.listTests ) - ["-l"]["--list-tests"] - ( "list all/matching test cases" ) - | Opt( config.listTags ) - ["-t"]["--list-tags"] - ( "list all/matching tags" ) - | Opt( config.showSuccessfulTests ) - ["-s"]["--success"] - ( "include successful tests in output" ) - | Opt( config.shouldDebugBreak ) - ["-b"]["--break"] - ( "break into debugger on failure" ) - | Opt( config.noThrow ) - ["-e"]["--nothrow"] - ( "skip exception tests" ) - | Opt( config.showInvisibles ) - ["-i"]["--invisibles"] - ( "show invisibles (tabs, newlines)" ) - | Opt( config.outputFilename, "filename" ) - ["-o"]["--out"] - ( "output filename" ) - | Opt( config.reporterName, "name" ) - ["-r"]["--reporter"] - ( "reporter to use (defaults to console)" ) - | Opt( config.name, "name" ) - ["-n"]["--name"] - ( "suite name" ) - | Opt( [&]( bool ){ config.abortAfter = 1; } ) - ["-a"]["--abort"] - ( "abort at first failure" ) - | Opt( [&]( int x ){ config.abortAfter = x; }, "no. failures" ) - ["-x"]["--abortx"] - ( "abort after x failures" ) - | Opt( setWarning, "warning name" ) - ["-w"]["--warn"] - ( "enable warnings" ) - | Opt( [&]( bool flag ) { config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never; }, "yes|no" ) - ["-d"]["--durations"] - ( "show test durations" ) - | Opt( loadTestNamesFromFile, "filename" ) - ["-f"]["--input-file"] - ( "load test names to run from a file" ) - | Opt( config.filenamesAsTags ) - ["-#"]["--filenames-as-tags"] - ( "adds a tag for the filename" ) - | Opt( config.sectionsToRun, "section name" ) - ["-c"]["--section"] - ( "specify section to run" ) - | Opt( setVerbosity, "quiet|normal|high" ) - ["-v"]["--verbosity"] - ( "set output verbosity" ) - | Opt( config.listTestNamesOnly ) - ["--list-test-names-only"] - ( "list all/matching test cases names only" ) - | Opt( config.listReporters ) - ["--list-reporters"] - ( "list all reporters" ) - | Opt( setTestOrder, "decl|lex|rand" ) - ["--order"] - ( "test case order (defaults to decl)" ) - | Opt( setRngSeed, "'time'|number" ) - ["--rng-seed"] - ( "set a specific seed for random numbers" ) - | Opt( setColourUsage, "yes|no" ) - ["--use-colour"] - ( "should output be colourised" ) - | Opt( config.libIdentify ) - ["--libidentify"] - ( "report name and version according to libidentify standard" ) - | Opt( setWaitForKeypress, "start|exit|both" ) - ["--wait-for-keypress"] - ( "waits for a keypress before exiting" ) - | Opt( config.benchmarkResolutionMultiple, "multiplier" ) - ["--benchmark-resolution-multiple"] - ( "multiple of clock resolution to run benchmarks" ) - - | Arg( config.testsOrTags, "test name|pattern|tags" ) - ( "which test or tests to use" ); - - return cli; - } - -} // end namespace Catch -// end catch_commandline.cpp -// start catch_common.cpp - -#include -#include - -namespace Catch { - - bool SourceLineInfo::empty() const noexcept { - return file[0] == '\0'; - } - bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const noexcept { - return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0); - } - bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const noexcept { - return line < other.line || ( line == other.line && (std::strcmp(file, other.file) < 0)); - } - - std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { -#ifndef __GNUG__ - os << info.file << '(' << info.line << ')'; -#else - os << info.file << ':' << info.line; -#endif - return os; - } - - std::string StreamEndStop::operator+() const { - return std::string(); - } - - NonCopyable::NonCopyable() = default; - NonCopyable::~NonCopyable() = default; - -} -// end catch_common.cpp -// start catch_config.cpp - -// start catch_enforce.h - -#include - -#define CATCH_PREPARE_EXCEPTION( type, msg ) \ - type( ( Catch::ReusableStringStream() << msg ).str() ) -#define CATCH_INTERNAL_ERROR( msg ) \ - throw CATCH_PREPARE_EXCEPTION( std::logic_error, CATCH_INTERNAL_LINEINFO << ": Internal Catch error: " << msg); -#define CATCH_ERROR( msg ) \ - throw CATCH_PREPARE_EXCEPTION( std::domain_error, msg ) -#define CATCH_ENFORCE( condition, msg ) \ - do{ if( !(condition) ) CATCH_ERROR( msg ); } while(false) - -// end catch_enforce.h -namespace Catch { - - Config::Config( ConfigData const& data ) - : m_data( data ), - m_stream( openStream() ) - { - TestSpecParser parser(ITagAliasRegistry::get()); - if (data.testsOrTags.empty()) { - parser.parse("~[.]"); // All not hidden tests - } - else { - m_hasTestFilters = true; - for( auto const& testOrTags : data.testsOrTags ) - parser.parse( testOrTags ); - } - m_testSpec = parser.testSpec(); - } - - std::string const& Config::getFilename() const { - return m_data.outputFilename ; - } - - bool Config::listTests() const { return m_data.listTests; } - bool Config::listTestNamesOnly() const { return m_data.listTestNamesOnly; } - bool Config::listTags() const { return m_data.listTags; } - bool Config::listReporters() const { return m_data.listReporters; } - - std::string Config::getProcessName() const { return m_data.processName; } - std::string const& Config::getReporterName() const { return m_data.reporterName; } - - std::vector const& Config::getTestsOrTags() const { return m_data.testsOrTags; } - std::vector const& Config::getSectionsToRun() const { return m_data.sectionsToRun; } - - TestSpec const& Config::testSpec() const { return m_testSpec; } - bool Config::hasTestFilters() const { return m_hasTestFilters; } - - bool Config::showHelp() const { return m_data.showHelp; } - - // IConfig interface - bool Config::allowThrows() const { return !m_data.noThrow; } - std::ostream& Config::stream() const { return m_stream->stream(); } - std::string Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } - bool Config::includeSuccessfulResults() const { return m_data.showSuccessfulTests; } - bool Config::warnAboutMissingAssertions() const { return !!(m_data.warnings & WarnAbout::NoAssertions); } - bool Config::warnAboutNoTests() const { return !!(m_data.warnings & WarnAbout::NoTests); } - ShowDurations::OrNot Config::showDurations() const { return m_data.showDurations; } - RunTests::InWhatOrder Config::runOrder() const { return m_data.runOrder; } - unsigned int Config::rngSeed() const { return m_data.rngSeed; } - int Config::benchmarkResolutionMultiple() const { return m_data.benchmarkResolutionMultiple; } - UseColour::YesOrNo Config::useColour() const { return m_data.useColour; } - bool Config::shouldDebugBreak() const { return m_data.shouldDebugBreak; } - int Config::abortAfter() const { return m_data.abortAfter; } - bool Config::showInvisibles() const { return m_data.showInvisibles; } - Verbosity Config::verbosity() const { return m_data.verbosity; } - - IStream const* Config::openStream() { - return Catch::makeStream(m_data.outputFilename); - } - -} // end namespace Catch -// end catch_config.cpp -// start catch_console_colour.cpp - -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wexit-time-destructors" -#endif - -// start catch_errno_guard.h - -namespace Catch { - - class ErrnoGuard { - public: - ErrnoGuard(); - ~ErrnoGuard(); - private: - int m_oldErrno; - }; - -} - -// end catch_errno_guard.h -#include - -namespace Catch { - namespace { - - struct IColourImpl { - virtual ~IColourImpl() = default; - virtual void use( Colour::Code _colourCode ) = 0; - }; - - struct NoColourImpl : IColourImpl { - void use( Colour::Code ) {} - - static IColourImpl* instance() { - static NoColourImpl s_instance; - return &s_instance; - } - }; - - } // anon namespace -} // namespace Catch - -#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) -# ifdef CATCH_PLATFORM_WINDOWS -# define CATCH_CONFIG_COLOUR_WINDOWS -# else -# define CATCH_CONFIG_COLOUR_ANSI -# endif -#endif - -#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) ///////////////////////////////////////// - -namespace Catch { -namespace { - - class Win32ColourImpl : public IColourImpl { - public: - Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) - { - CONSOLE_SCREEN_BUFFER_INFO csbiInfo; - GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); - originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY ); - originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY ); - } - - virtual void use( Colour::Code _colourCode ) override { - switch( _colourCode ) { - case Colour::None: return setTextAttribute( originalForegroundAttributes ); - case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); - case Colour::Red: return setTextAttribute( FOREGROUND_RED ); - case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); - case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); - case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); - case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); - case Colour::Grey: return setTextAttribute( 0 ); - - case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); - case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); - case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); - case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); - case Colour::BrightYellow: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN ); - - case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); - - default: - CATCH_ERROR( "Unknown colour requested" ); - } - } - - private: - void setTextAttribute( WORD _textAttribute ) { - SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes ); - } - HANDLE stdoutHandle; - WORD originalForegroundAttributes; - WORD originalBackgroundAttributes; - }; - - IColourImpl* platformColourInstance() { - static Win32ColourImpl s_instance; - - IConfigPtr config = getCurrentContext().getConfig(); - UseColour::YesOrNo colourMode = config - ? config->useColour() - : UseColour::Auto; - if( colourMode == UseColour::Auto ) - colourMode = UseColour::Yes; - return colourMode == UseColour::Yes - ? &s_instance - : NoColourImpl::instance(); - } - -} // end anon namespace -} // end namespace Catch - -#elif defined( CATCH_CONFIG_COLOUR_ANSI ) ////////////////////////////////////// - -#include - -namespace Catch { -namespace { - - // use POSIX/ ANSI console terminal codes - // Thanks to Adam Strzelecki for original contribution - // (http://github.com/nanoant) - // https://github.com/philsquared/Catch/pull/131 - class PosixColourImpl : public IColourImpl { - public: - virtual void use( Colour::Code _colourCode ) override { - switch( _colourCode ) { - case Colour::None: - case Colour::White: return setColour( "[0m" ); - case Colour::Red: return setColour( "[0;31m" ); - case Colour::Green: return setColour( "[0;32m" ); - case Colour::Blue: return setColour( "[0;34m" ); - case Colour::Cyan: return setColour( "[0;36m" ); - case Colour::Yellow: return setColour( "[0;33m" ); - case Colour::Grey: return setColour( "[1;30m" ); - - case Colour::LightGrey: return setColour( "[0;37m" ); - case Colour::BrightRed: return setColour( "[1;31m" ); - case Colour::BrightGreen: return setColour( "[1;32m" ); - case Colour::BrightWhite: return setColour( "[1;37m" ); - case Colour::BrightYellow: return setColour( "[1;33m" ); - - case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); - default: CATCH_INTERNAL_ERROR( "Unknown colour requested" ); - } - } - static IColourImpl* instance() { - static PosixColourImpl s_instance; - return &s_instance; - } - - private: - void setColour( const char* _escapeCode ) { - Catch::cout() << '\033' << _escapeCode; - } - }; - - bool useColourOnPlatform() { - return -#ifdef CATCH_PLATFORM_MAC - !isDebuggerActive() && -#endif -#if !(defined(__DJGPP__) && defined(__STRICT_ANSI__)) - isatty(STDOUT_FILENO) -#else - false -#endif - ; - } - IColourImpl* platformColourInstance() { - ErrnoGuard guard; - IConfigPtr config = getCurrentContext().getConfig(); - UseColour::YesOrNo colourMode = config - ? config->useColour() - : UseColour::Auto; - if( colourMode == UseColour::Auto ) - colourMode = useColourOnPlatform() - ? UseColour::Yes - : UseColour::No; - return colourMode == UseColour::Yes - ? PosixColourImpl::instance() - : NoColourImpl::instance(); - } - -} // end anon namespace -} // end namespace Catch - -#else // not Windows or ANSI /////////////////////////////////////////////// - -namespace Catch { - - static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } - -} // end namespace Catch - -#endif // Windows/ ANSI/ None - -namespace Catch { - - Colour::Colour( Code _colourCode ) { use( _colourCode ); } - Colour::Colour( Colour&& rhs ) noexcept { - m_moved = rhs.m_moved; - rhs.m_moved = true; - } - Colour& Colour::operator=( Colour&& rhs ) noexcept { - m_moved = rhs.m_moved; - rhs.m_moved = true; - return *this; - } - - Colour::~Colour(){ if( !m_moved ) use( None ); } - - void Colour::use( Code _colourCode ) { - static IColourImpl* impl = platformColourInstance(); - impl->use( _colourCode ); - } - - std::ostream& operator << ( std::ostream& os, Colour const& ) { - return os; - } - -} // end namespace Catch - -#if defined(__clang__) -# pragma clang diagnostic pop -#endif - -// end catch_console_colour.cpp -// start catch_context.cpp - -namespace Catch { - - class Context : public IMutableContext, NonCopyable { - - public: // IContext - virtual IResultCapture* getResultCapture() override { - return m_resultCapture; - } - virtual IRunner* getRunner() override { - return m_runner; - } - - virtual IConfigPtr const& getConfig() const override { - return m_config; - } - - virtual ~Context() override; - - public: // IMutableContext - virtual void setResultCapture( IResultCapture* resultCapture ) override { - m_resultCapture = resultCapture; - } - virtual void setRunner( IRunner* runner ) override { - m_runner = runner; - } - virtual void setConfig( IConfigPtr const& config ) override { - m_config = config; - } - - friend IMutableContext& getCurrentMutableContext(); - - private: - IConfigPtr m_config; - IRunner* m_runner = nullptr; - IResultCapture* m_resultCapture = nullptr; - }; - - IMutableContext *IMutableContext::currentContext = nullptr; - - void IMutableContext::createContext() - { - currentContext = new Context(); - } - - void cleanUpContext() { - delete IMutableContext::currentContext; - IMutableContext::currentContext = nullptr; - } - IContext::~IContext() = default; - IMutableContext::~IMutableContext() = default; - Context::~Context() = default; -} -// end catch_context.cpp -// start catch_debug_console.cpp - -// start catch_debug_console.h - -#include - -namespace Catch { - void writeToDebugConsole( std::string const& text ); -} - -// end catch_debug_console.h -#ifdef CATCH_PLATFORM_WINDOWS - - namespace Catch { - void writeToDebugConsole( std::string const& text ) { - ::OutputDebugStringA( text.c_str() ); - } - } - -#else - - namespace Catch { - void writeToDebugConsole( std::string const& text ) { - // !TBD: Need a version for Mac/ XCode and other IDEs - Catch::cout() << text; - } - } - -#endif // Platform -// end catch_debug_console.cpp -// start catch_debugger.cpp - -#ifdef CATCH_PLATFORM_MAC - -# include -# include -# include -# include -# include -# include -# include - -namespace Catch { - - // The following function is taken directly from the following technical note: - // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html - - // Returns true if the current process is being debugged (either - // running under the debugger or has a debugger attached post facto). - bool isDebuggerActive(){ - - int mib[4]; - struct kinfo_proc info; - std::size_t size; - - // Initialize the flags so that, if sysctl fails for some bizarre - // reason, we get a predictable result. - - info.kp_proc.p_flag = 0; - - // Initialize mib, which tells sysctl the info we want, in this case - // we're looking for information about a specific process ID. - - mib[0] = CTL_KERN; - mib[1] = KERN_PROC; - mib[2] = KERN_PROC_PID; - mib[3] = getpid(); - - // Call sysctl. - - size = sizeof(info); - if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0) != 0 ) { - Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; - return false; - } - - // We're being debugged if the P_TRACED flag is set. - - return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); - } - } // namespace Catch - -#elif defined(CATCH_PLATFORM_LINUX) - #include - #include - - namespace Catch{ - // The standard POSIX way of detecting a debugger is to attempt to - // ptrace() the process, but this needs to be done from a child and not - // this process itself to still allow attaching to this process later - // if wanted, so is rather heavy. Under Linux we have the PID of the - // "debugger" (which doesn't need to be gdb, of course, it could also - // be strace, for example) in /proc/$PID/status, so just get it from - // there instead. - bool isDebuggerActive(){ - // Libstdc++ has a bug, where std::ifstream sets errno to 0 - // This way our users can properly assert over errno values - ErrnoGuard guard; - std::ifstream in("/proc/self/status"); - for( std::string line; std::getline(in, line); ) { - static const int PREFIX_LEN = 11; - if( line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0 ) { - // We're traced if the PID is not 0 and no other PID starts - // with 0 digit, so it's enough to check for just a single - // character. - return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; - } - } - - return false; - } - } // namespace Catch -#elif defined(_MSC_VER) - extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); - namespace Catch { - bool isDebuggerActive() { - return IsDebuggerPresent() != 0; - } - } -#elif defined(__MINGW32__) - extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); - namespace Catch { - bool isDebuggerActive() { - return IsDebuggerPresent() != 0; - } - } -#else - namespace Catch { - bool isDebuggerActive() { return false; } - } -#endif // Platform -// end catch_debugger.cpp -// start catch_decomposer.cpp - -namespace Catch { - - ITransientExpression::~ITransientExpression() = default; - - void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ) { - if( lhs.size() + rhs.size() < 40 && - lhs.find('\n') == std::string::npos && - rhs.find('\n') == std::string::npos ) - os << lhs << " " << op << " " << rhs; - else - os << lhs << "\n" << op << "\n" << rhs; - } -} -// end catch_decomposer.cpp -// start catch_errno_guard.cpp - -#include - -namespace Catch { - ErrnoGuard::ErrnoGuard():m_oldErrno(errno){} - ErrnoGuard::~ErrnoGuard() { errno = m_oldErrno; } -} -// end catch_errno_guard.cpp -// start catch_exception_translator_registry.cpp - -// start catch_exception_translator_registry.h - -#include -#include -#include - -namespace Catch { - - class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { - public: - ~ExceptionTranslatorRegistry(); - virtual void registerTranslator( const IExceptionTranslator* translator ); - virtual std::string translateActiveException() const override; - std::string tryTranslators() const; - - private: - std::vector> m_translators; - }; -} - -// end catch_exception_translator_registry.h -#ifdef __OBJC__ -#import "Foundation/Foundation.h" -#endif - -namespace Catch { - - ExceptionTranslatorRegistry::~ExceptionTranslatorRegistry() { - } - - void ExceptionTranslatorRegistry::registerTranslator( const IExceptionTranslator* translator ) { - m_translators.push_back( std::unique_ptr( translator ) ); - } - - std::string ExceptionTranslatorRegistry::translateActiveException() const { - try { -#ifdef __OBJC__ - // In Objective-C try objective-c exceptions first - @try { - return tryTranslators(); - } - @catch (NSException *exception) { - return Catch::Detail::stringify( [exception description] ); - } -#else - // Compiling a mixed mode project with MSVC means that CLR - // exceptions will be caught in (...) as well. However, these - // do not fill-in std::current_exception and thus lead to crash - // when attempting rethrow. - // /EHa switch also causes structured exceptions to be caught - // here, but they fill-in current_exception properly, so - // at worst the output should be a little weird, instead of - // causing a crash. - if (std::current_exception() == nullptr) { - return "Non C++ exception. Possibly a CLR exception."; - } - return tryTranslators(); -#endif - } - catch( TestFailureException& ) { - std::rethrow_exception(std::current_exception()); - } - catch( std::exception& ex ) { - return ex.what(); - } - catch( std::string& msg ) { - return msg; - } - catch( const char* msg ) { - return msg; - } - catch(...) { - return "Unknown exception"; - } - } - - std::string ExceptionTranslatorRegistry::tryTranslators() const { - if( m_translators.empty() ) - std::rethrow_exception(std::current_exception()); - else - return m_translators[0]->translate( m_translators.begin()+1, m_translators.end() ); - } -} -// end catch_exception_translator_registry.cpp -// start catch_fatal_condition.cpp - -#if defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wmissing-field-initializers" -#endif - -#if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS ) - -namespace { - // Report the error condition - void reportFatal( char const * const message ) { - Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message ); - } -} - -#endif // signals/SEH handling - -#if defined( CATCH_CONFIG_WINDOWS_SEH ) - -namespace Catch { - struct SignalDefs { DWORD id; const char* name; }; - - // There is no 1-1 mapping between signals and windows exceptions. - // Windows can easily distinguish between SO and SigSegV, - // but SigInt, SigTerm, etc are handled differently. - static SignalDefs signalDefs[] = { - { EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal" }, - { EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow" }, - { EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal" }, - { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" }, - }; - - LONG CALLBACK FatalConditionHandler::handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { - for (auto const& def : signalDefs) { - if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { - reportFatal(def.name); - } - } - // If its not an exception we care about, pass it along. - // This stops us from eating debugger breaks etc. - return EXCEPTION_CONTINUE_SEARCH; - } - - FatalConditionHandler::FatalConditionHandler() { - isSet = true; - // 32k seems enough for Catch to handle stack overflow, - // but the value was found experimentally, so there is no strong guarantee - guaranteeSize = 32 * 1024; - exceptionHandlerHandle = nullptr; - // Register as first handler in current chain - exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); - // Pass in guarantee size to be filled - SetThreadStackGuarantee(&guaranteeSize); - } - - void FatalConditionHandler::reset() { - if (isSet) { - RemoveVectoredExceptionHandler(exceptionHandlerHandle); - SetThreadStackGuarantee(&guaranteeSize); - exceptionHandlerHandle = nullptr; - isSet = false; - } - } - - FatalConditionHandler::~FatalConditionHandler() { - reset(); - } - -bool FatalConditionHandler::isSet = false; -ULONG FatalConditionHandler::guaranteeSize = 0; -PVOID FatalConditionHandler::exceptionHandlerHandle = nullptr; - -} // namespace Catch - -#elif defined( CATCH_CONFIG_POSIX_SIGNALS ) - -namespace Catch { - - struct SignalDefs { - int id; - const char* name; - }; - - // 32kb for the alternate stack seems to be sufficient. However, this value - // is experimentally determined, so that's not guaranteed. - constexpr static std::size_t sigStackSize = 32768 >= MINSIGSTKSZ ? 32768 : MINSIGSTKSZ; - - static SignalDefs signalDefs[] = { - { SIGINT, "SIGINT - Terminal interrupt signal" }, - { SIGILL, "SIGILL - Illegal instruction signal" }, - { SIGFPE, "SIGFPE - Floating point error signal" }, - { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, - { SIGTERM, "SIGTERM - Termination request signal" }, - { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } - }; - - void FatalConditionHandler::handleSignal( int sig ) { - char const * name = ""; - for (auto const& def : signalDefs) { - if (sig == def.id) { - name = def.name; - break; - } - } - reset(); - reportFatal(name); - raise( sig ); - } - - FatalConditionHandler::FatalConditionHandler() { - isSet = true; - stack_t sigStack; - sigStack.ss_sp = altStackMem; - sigStack.ss_size = sigStackSize; - sigStack.ss_flags = 0; - sigaltstack(&sigStack, &oldSigStack); - struct sigaction sa = { }; - - sa.sa_handler = handleSignal; - sa.sa_flags = SA_ONSTACK; - for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) { - sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); - } - } - - FatalConditionHandler::~FatalConditionHandler() { - reset(); - } - - void FatalConditionHandler::reset() { - if( isSet ) { - // Set signals back to previous values -- hopefully nobody overwrote them in the meantime - for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) { - sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); - } - // Return the old stack - sigaltstack(&oldSigStack, nullptr); - isSet = false; - } - } - - bool FatalConditionHandler::isSet = false; - struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {}; - stack_t FatalConditionHandler::oldSigStack = {}; - char FatalConditionHandler::altStackMem[sigStackSize] = {}; - -} // namespace Catch - -#else - -namespace Catch { - void FatalConditionHandler::reset() {} -} - -#endif // signals/SEH handling - -#if defined(__GNUC__) -# pragma GCC diagnostic pop -#endif -// end catch_fatal_condition.cpp -// start catch_interfaces_capture.cpp - -namespace Catch { - IResultCapture::~IResultCapture() = default; -} -// end catch_interfaces_capture.cpp -// start catch_interfaces_config.cpp - -namespace Catch { - IConfig::~IConfig() = default; -} -// end catch_interfaces_config.cpp -// start catch_interfaces_exception.cpp - -namespace Catch { - IExceptionTranslator::~IExceptionTranslator() = default; - IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() = default; -} -// end catch_interfaces_exception.cpp -// start catch_interfaces_registry_hub.cpp - -namespace Catch { - IRegistryHub::~IRegistryHub() = default; - IMutableRegistryHub::~IMutableRegistryHub() = default; -} -// end catch_interfaces_registry_hub.cpp -// start catch_interfaces_reporter.cpp - -// start catch_reporter_listening.h - -namespace Catch { - - class ListeningReporter : public IStreamingReporter { - using Reporters = std::vector; - Reporters m_listeners; - IStreamingReporterPtr m_reporter = nullptr; - - public: - void addListener( IStreamingReporterPtr&& listener ); - void addReporter( IStreamingReporterPtr&& reporter ); - - public: // IStreamingReporter - - ReporterPreferences getPreferences() const override; - - void noMatchingTestCases( std::string const& spec ) override; - - static std::set getSupportedVerbosities(); - - void benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) override; - void benchmarkEnded( BenchmarkStats const& benchmarkStats ) override; - - void testRunStarting( TestRunInfo const& testRunInfo ) override; - void testGroupStarting( GroupInfo const& groupInfo ) override; - void testCaseStarting( TestCaseInfo const& testInfo ) override; - void sectionStarting( SectionInfo const& sectionInfo ) override; - void assertionStarting( AssertionInfo const& assertionInfo ) override; - - // The return value indicates if the messages buffer should be cleared: - bool assertionEnded( AssertionStats const& assertionStats ) override; - void sectionEnded( SectionStats const& sectionStats ) override; - void testCaseEnded( TestCaseStats const& testCaseStats ) override; - void testGroupEnded( TestGroupStats const& testGroupStats ) override; - void testRunEnded( TestRunStats const& testRunStats ) override; - - void skipTest( TestCaseInfo const& testInfo ) override; - bool isMulti() const override; - - }; - -} // end namespace Catch - -// end catch_reporter_listening.h -namespace Catch { - - ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig ) - : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} - - ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig, std::ostream& _stream ) - : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} - - std::ostream& ReporterConfig::stream() const { return *m_stream; } - IConfigPtr ReporterConfig::fullConfig() const { return m_fullConfig; } - - TestRunInfo::TestRunInfo( std::string const& _name ) : name( _name ) {} - - GroupInfo::GroupInfo( std::string const& _name, - std::size_t _groupIndex, - std::size_t _groupsCount ) - : name( _name ), - groupIndex( _groupIndex ), - groupsCounts( _groupsCount ) - {} - - AssertionStats::AssertionStats( AssertionResult const& _assertionResult, - std::vector const& _infoMessages, - Totals const& _totals ) - : assertionResult( _assertionResult ), - infoMessages( _infoMessages ), - totals( _totals ) - { - assertionResult.m_resultData.lazyExpression.m_transientExpression = _assertionResult.m_resultData.lazyExpression.m_transientExpression; - - if( assertionResult.hasMessage() ) { - // Copy message into messages list. - // !TBD This should have been done earlier, somewhere - MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); - builder << assertionResult.getMessage(); - builder.m_info.message = builder.m_stream.str(); - - infoMessages.push_back( builder.m_info ); - } - } - - AssertionStats::~AssertionStats() = default; - - SectionStats::SectionStats( SectionInfo const& _sectionInfo, - Counts const& _assertions, - double _durationInSeconds, - bool _missingAssertions ) - : sectionInfo( _sectionInfo ), - assertions( _assertions ), - durationInSeconds( _durationInSeconds ), - missingAssertions( _missingAssertions ) - {} - - SectionStats::~SectionStats() = default; - - TestCaseStats::TestCaseStats( TestCaseInfo const& _testInfo, - Totals const& _totals, - std::string const& _stdOut, - std::string const& _stdErr, - bool _aborting ) - : testInfo( _testInfo ), - totals( _totals ), - stdOut( _stdOut ), - stdErr( _stdErr ), - aborting( _aborting ) - {} - - TestCaseStats::~TestCaseStats() = default; - - TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo, - Totals const& _totals, - bool _aborting ) - : groupInfo( _groupInfo ), - totals( _totals ), - aborting( _aborting ) - {} - - TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo ) - : groupInfo( _groupInfo ), - aborting( false ) - {} - - TestGroupStats::~TestGroupStats() = default; - - TestRunStats::TestRunStats( TestRunInfo const& _runInfo, - Totals const& _totals, - bool _aborting ) - : runInfo( _runInfo ), - totals( _totals ), - aborting( _aborting ) - {} - - TestRunStats::~TestRunStats() = default; - - void IStreamingReporter::fatalErrorEncountered( StringRef ) {} - bool IStreamingReporter::isMulti() const { return false; } - - IReporterFactory::~IReporterFactory() = default; - IReporterRegistry::~IReporterRegistry() = default; - -} // end namespace Catch -// end catch_interfaces_reporter.cpp -// start catch_interfaces_runner.cpp - -namespace Catch { - IRunner::~IRunner() = default; -} -// end catch_interfaces_runner.cpp -// start catch_interfaces_testcase.cpp - -namespace Catch { - ITestInvoker::~ITestInvoker() = default; - ITestCaseRegistry::~ITestCaseRegistry() = default; -} -// end catch_interfaces_testcase.cpp -// start catch_leak_detector.cpp - -#ifdef CATCH_CONFIG_WINDOWS_CRTDBG -#include - -namespace Catch { - - LeakDetector::LeakDetector() { - int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); - flag |= _CRTDBG_LEAK_CHECK_DF; - flag |= _CRTDBG_ALLOC_MEM_DF; - _CrtSetDbgFlag(flag); - _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); - _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); - // Change this to leaking allocation's number to break there - _CrtSetBreakAlloc(-1); - } -} - -#else - - Catch::LeakDetector::LeakDetector() {} - -#endif -// end catch_leak_detector.cpp -// start catch_list.cpp - -// start catch_list.h - -#include - -namespace Catch { - - std::size_t listTests( Config const& config ); - - std::size_t listTestsNamesOnly( Config const& config ); - - struct TagInfo { - void add( std::string const& spelling ); - std::string all() const; - - std::set spellings; - std::size_t count = 0; - }; - - std::size_t listTags( Config const& config ); - - std::size_t listReporters( Config const& /*config*/ ); - - Option list( Config const& config ); - -} // end namespace Catch - -// end catch_list.h -// start catch_text.h - -namespace Catch { - using namespace clara::TextFlow; -} - -// end catch_text.h -#include -#include -#include - -namespace Catch { - - std::size_t listTests( Config const& config ) { - TestSpec testSpec = config.testSpec(); - if( config.hasTestFilters() ) - Catch::cout() << "Matching test cases:\n"; - else { - Catch::cout() << "All available test cases:\n"; - } - - auto matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); - for( auto const& testCaseInfo : matchedTestCases ) { - Colour::Code colour = testCaseInfo.isHidden() - ? Colour::SecondaryText - : Colour::None; - Colour colourGuard( colour ); - - Catch::cout() << Column( testCaseInfo.name ).initialIndent( 2 ).indent( 4 ) << "\n"; - if( config.verbosity() >= Verbosity::High ) { - Catch::cout() << Column( Catch::Detail::stringify( testCaseInfo.lineInfo ) ).indent(4) << std::endl; - std::string description = testCaseInfo.description; - if( description.empty() ) - description = "(NO DESCRIPTION)"; - Catch::cout() << Column( description ).indent(4) << std::endl; - } - if( !testCaseInfo.tags.empty() ) - Catch::cout() << Column( testCaseInfo.tagsAsString() ).indent( 6 ) << "\n"; - } - - if( !config.hasTestFilters() ) - Catch::cout() << pluralise( matchedTestCases.size(), "test case" ) << '\n' << std::endl; - else - Catch::cout() << pluralise( matchedTestCases.size(), "matching test case" ) << '\n' << std::endl; - return matchedTestCases.size(); - } - - std::size_t listTestsNamesOnly( Config const& config ) { - TestSpec testSpec = config.testSpec(); - std::size_t matchedTests = 0; - std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); - for( auto const& testCaseInfo : matchedTestCases ) { - matchedTests++; - if( startsWith( testCaseInfo.name, '#' ) ) - Catch::cout() << '"' << testCaseInfo.name << '"'; - else - Catch::cout() << testCaseInfo.name; - if ( config.verbosity() >= Verbosity::High ) - Catch::cout() << "\t@" << testCaseInfo.lineInfo; - Catch::cout() << std::endl; - } - return matchedTests; - } - - void TagInfo::add( std::string const& spelling ) { - ++count; - spellings.insert( spelling ); - } - - std::string TagInfo::all() const { - std::string out; - for( auto const& spelling : spellings ) - out += "[" + spelling + "]"; - return out; - } - - std::size_t listTags( Config const& config ) { - TestSpec testSpec = config.testSpec(); - if( config.hasTestFilters() ) - Catch::cout() << "Tags for matching test cases:\n"; - else { - Catch::cout() << "All available tags:\n"; - } - - std::map tagCounts; - - std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); - for( auto const& testCase : matchedTestCases ) { - for( auto const& tagName : testCase.getTestCaseInfo().tags ) { - std::string lcaseTagName = toLower( tagName ); - auto countIt = tagCounts.find( lcaseTagName ); - if( countIt == tagCounts.end() ) - countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first; - countIt->second.add( tagName ); - } - } - - for( auto const& tagCount : tagCounts ) { - ReusableStringStream rss; - rss << " " << std::setw(2) << tagCount.second.count << " "; - auto str = rss.str(); - auto wrapper = Column( tagCount.second.all() ) - .initialIndent( 0 ) - .indent( str.size() ) - .width( CATCH_CONFIG_CONSOLE_WIDTH-10 ); - Catch::cout() << str << wrapper << '\n'; - } - Catch::cout() << pluralise( tagCounts.size(), "tag" ) << '\n' << std::endl; - return tagCounts.size(); - } - - std::size_t listReporters( Config const& /*config*/ ) { - Catch::cout() << "Available reporters:\n"; - IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); - std::size_t maxNameLen = 0; - for( auto const& factoryKvp : factories ) - maxNameLen = (std::max)( maxNameLen, factoryKvp.first.size() ); - - for( auto const& factoryKvp : factories ) { - Catch::cout() - << Column( factoryKvp.first + ":" ) - .indent(2) - .width( 5+maxNameLen ) - + Column( factoryKvp.second->getDescription() ) - .initialIndent(0) - .indent(2) - .width( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) - << "\n"; - } - Catch::cout() << std::endl; - return factories.size(); - } - - Option list( Config const& config ) { - Option listedCount; - if( config.listTests() ) - listedCount = listedCount.valueOr(0) + listTests( config ); - if( config.listTestNamesOnly() ) - listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config ); - if( config.listTags() ) - listedCount = listedCount.valueOr(0) + listTags( config ); - if( config.listReporters() ) - listedCount = listedCount.valueOr(0) + listReporters( config ); - return listedCount; - } - -} // end namespace Catch -// end catch_list.cpp -// start catch_matchers.cpp - -namespace Catch { -namespace Matchers { - namespace Impl { - - std::string MatcherUntypedBase::toString() const { - if( m_cachedToString.empty() ) - m_cachedToString = describe(); - return m_cachedToString; - } - - MatcherUntypedBase::~MatcherUntypedBase() = default; - - } // namespace Impl -} // namespace Matchers - -using namespace Matchers; -using Matchers::Impl::MatcherBase; - -} // namespace Catch -// end catch_matchers.cpp -// start catch_matchers_floating.cpp - -// start catch_to_string.hpp - -#include - -namespace Catch { - template - std::string to_string(T const& t) { -#if defined(CATCH_CONFIG_CPP11_TO_STRING) - return std::to_string(t); -#else - ReusableStringStream rss; - rss << t; - return rss.str(); -#endif - } -} // end namespace Catch - -// end catch_to_string.hpp -#include -#include -#include -#include - -namespace Catch { -namespace Matchers { -namespace Floating { -enum class FloatingPointKind : uint8_t { - Float, - Double -}; -} -} -} - -namespace { - -template -struct Converter; - -template <> -struct Converter { - static_assert(sizeof(float) == sizeof(int32_t), "Important ULP matcher assumption violated"); - Converter(float f) { - std::memcpy(&i, &f, sizeof(f)); - } - int32_t i; -}; - -template <> -struct Converter { - static_assert(sizeof(double) == sizeof(int64_t), "Important ULP matcher assumption violated"); - Converter(double d) { - std::memcpy(&i, &d, sizeof(d)); - } - int64_t i; -}; - -template -auto convert(T t) -> Converter { - return Converter(t); -} - -template -bool almostEqualUlps(FP lhs, FP rhs, int maxUlpDiff) { - // Comparison with NaN should always be false. - // This way we can rule it out before getting into the ugly details - if (std::isnan(lhs) || std::isnan(rhs)) { - return false; - } - - auto lc = convert(lhs); - auto rc = convert(rhs); - - if ((lc.i < 0) != (rc.i < 0)) { - // Potentially we can have +0 and -0 - return lhs == rhs; - } - - auto ulpDiff = std::abs(lc.i - rc.i); - return ulpDiff <= maxUlpDiff; -} - -} - -namespace Catch { -namespace Matchers { -namespace Floating { - WithinAbsMatcher::WithinAbsMatcher(double target, double margin) - :m_target{ target }, m_margin{ margin } { - if (m_margin < 0) { - throw std::domain_error("Allowed margin difference has to be >= 0"); - } - } - - // Performs equivalent check of std::fabs(lhs - rhs) <= margin - // But without the subtraction to allow for INFINITY in comparison - bool WithinAbsMatcher::match(double const& matchee) const { - return (matchee + m_margin >= m_target) && (m_target + m_margin >= matchee); - } - - std::string WithinAbsMatcher::describe() const { - return "is within " + ::Catch::Detail::stringify(m_margin) + " of " + ::Catch::Detail::stringify(m_target); - } - - WithinUlpsMatcher::WithinUlpsMatcher(double target, int ulps, FloatingPointKind baseType) - :m_target{ target }, m_ulps{ ulps }, m_type{ baseType } { - if (m_ulps < 0) { - throw std::domain_error("Allowed ulp difference has to be >= 0"); - } - } - - bool WithinUlpsMatcher::match(double const& matchee) const { - switch (m_type) { - case FloatingPointKind::Float: - return almostEqualUlps(static_cast(matchee), static_cast(m_target), m_ulps); - case FloatingPointKind::Double: - return almostEqualUlps(matchee, m_target, m_ulps); - default: - throw std::domain_error("Unknown FloatingPointKind value"); - } - } - - std::string WithinUlpsMatcher::describe() const { - return "is within " + Catch::to_string(m_ulps) + " ULPs of " + ::Catch::Detail::stringify(m_target) + ((m_type == FloatingPointKind::Float)? "f" : ""); - } - -}// namespace Floating - -Floating::WithinUlpsMatcher WithinULP(double target, int maxUlpDiff) { - return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Double); -} - -Floating::WithinUlpsMatcher WithinULP(float target, int maxUlpDiff) { - return Floating::WithinUlpsMatcher(target, maxUlpDiff, Floating::FloatingPointKind::Float); -} - -Floating::WithinAbsMatcher WithinAbs(double target, double margin) { - return Floating::WithinAbsMatcher(target, margin); -} - -} // namespace Matchers -} // namespace Catch - -// end catch_matchers_floating.cpp -// start catch_matchers_generic.cpp - -std::string Catch::Matchers::Generic::Detail::finalizeDescription(const std::string& desc) { - if (desc.empty()) { - return "matches undescribed predicate"; - } else { - return "matches predicate: \"" + desc + '"'; - } -} -// end catch_matchers_generic.cpp -// start catch_matchers_string.cpp - -#include - -namespace Catch { -namespace Matchers { - - namespace StdString { - - CasedString::CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ) - : m_caseSensitivity( caseSensitivity ), - m_str( adjustString( str ) ) - {} - std::string CasedString::adjustString( std::string const& str ) const { - return m_caseSensitivity == CaseSensitive::No - ? toLower( str ) - : str; - } - std::string CasedString::caseSensitivitySuffix() const { - return m_caseSensitivity == CaseSensitive::No - ? " (case insensitive)" - : std::string(); - } - - StringMatcherBase::StringMatcherBase( std::string const& operation, CasedString const& comparator ) - : m_comparator( comparator ), - m_operation( operation ) { - } - - std::string StringMatcherBase::describe() const { - std::string description; - description.reserve(5 + m_operation.size() + m_comparator.m_str.size() + - m_comparator.caseSensitivitySuffix().size()); - description += m_operation; - description += ": \""; - description += m_comparator.m_str; - description += "\""; - description += m_comparator.caseSensitivitySuffix(); - return description; - } - - EqualsMatcher::EqualsMatcher( CasedString const& comparator ) : StringMatcherBase( "equals", comparator ) {} - - bool EqualsMatcher::match( std::string const& source ) const { - return m_comparator.adjustString( source ) == m_comparator.m_str; - } - - ContainsMatcher::ContainsMatcher( CasedString const& comparator ) : StringMatcherBase( "contains", comparator ) {} - - bool ContainsMatcher::match( std::string const& source ) const { - return contains( m_comparator.adjustString( source ), m_comparator.m_str ); - } - - StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "starts with", comparator ) {} - - bool StartsWithMatcher::match( std::string const& source ) const { - return startsWith( m_comparator.adjustString( source ), m_comparator.m_str ); - } - - EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "ends with", comparator ) {} - - bool EndsWithMatcher::match( std::string const& source ) const { - return endsWith( m_comparator.adjustString( source ), m_comparator.m_str ); - } - - RegexMatcher::RegexMatcher(std::string regex, CaseSensitive::Choice caseSensitivity): m_regex(std::move(regex)), m_caseSensitivity(caseSensitivity) {} - - bool RegexMatcher::match(std::string const& matchee) const { - auto flags = std::regex::ECMAScript; // ECMAScript is the default syntax option anyway - if (m_caseSensitivity == CaseSensitive::Choice::No) { - flags |= std::regex::icase; - } - auto reg = std::regex(m_regex, flags); - return std::regex_match(matchee, reg); - } - - std::string RegexMatcher::describe() const { - return "matches " + ::Catch::Detail::stringify(m_regex) + ((m_caseSensitivity == CaseSensitive::Choice::Yes)? " case sensitively" : " case insensitively"); - } - - } // namespace StdString - - StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity ) { - return StdString::EqualsMatcher( StdString::CasedString( str, caseSensitivity) ); - } - StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity ) { - return StdString::ContainsMatcher( StdString::CasedString( str, caseSensitivity) ); - } - StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { - return StdString::EndsWithMatcher( StdString::CasedString( str, caseSensitivity) ); - } - StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { - return StdString::StartsWithMatcher( StdString::CasedString( str, caseSensitivity) ); - } - - StdString::RegexMatcher Matches(std::string const& regex, CaseSensitive::Choice caseSensitivity) { - return StdString::RegexMatcher(regex, caseSensitivity); - } - -} // namespace Matchers -} // namespace Catch -// end catch_matchers_string.cpp -// start catch_message.cpp - -// start catch_uncaught_exceptions.h - -namespace Catch { - bool uncaught_exceptions(); -} // end namespace Catch - -// end catch_uncaught_exceptions.h -namespace Catch { - - MessageInfo::MessageInfo( std::string const& _macroName, - SourceLineInfo const& _lineInfo, - ResultWas::OfType _type ) - : macroName( _macroName ), - lineInfo( _lineInfo ), - type( _type ), - sequence( ++globalCount ) - {} - - bool MessageInfo::operator==( MessageInfo const& other ) const { - return sequence == other.sequence; - } - - bool MessageInfo::operator<( MessageInfo const& other ) const { - return sequence < other.sequence; - } - - // This may need protecting if threading support is added - unsigned int MessageInfo::globalCount = 0; - - //////////////////////////////////////////////////////////////////////////// - - Catch::MessageBuilder::MessageBuilder( std::string const& macroName, - SourceLineInfo const& lineInfo, - ResultWas::OfType type ) - :m_info(macroName, lineInfo, type) {} - - //////////////////////////////////////////////////////////////////////////// - - ScopedMessage::ScopedMessage( MessageBuilder const& builder ) - : m_info( builder.m_info ) - { - m_info.message = builder.m_stream.str(); - getResultCapture().pushScopedMessage( m_info ); - } - - ScopedMessage::~ScopedMessage() { - if ( !uncaught_exceptions() ){ - getResultCapture().popScopedMessage(m_info); - } - } -} // end namespace Catch -// end catch_message.cpp -// start catch_output_redirect.cpp - -// start catch_output_redirect.h -#ifndef TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H -#define TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H - -#include -#include -#include - -namespace Catch { - - class RedirectedStream { - std::ostream& m_originalStream; - std::ostream& m_redirectionStream; - std::streambuf* m_prevBuf; - - public: - RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream ); - ~RedirectedStream(); - }; - - class RedirectedStdOut { - ReusableStringStream m_rss; - RedirectedStream m_cout; - public: - RedirectedStdOut(); - auto str() const -> std::string; - }; - - // StdErr has two constituent streams in C++, std::cerr and std::clog - // This means that we need to redirect 2 streams into 1 to keep proper - // order of writes - class RedirectedStdErr { - ReusableStringStream m_rss; - RedirectedStream m_cerr; - RedirectedStream m_clog; - public: - RedirectedStdErr(); - auto str() const -> std::string; - }; - - // Windows's implementation of std::tmpfile is terrible (it tries - // to create a file inside system folder, thus requiring elevated - // privileges for the binary), so we have to use tmpnam(_s) and - // create the file ourselves there. - class TempFile { - public: - TempFile(TempFile const&) = delete; - TempFile& operator=(TempFile const&) = delete; - TempFile(TempFile&&) = delete; - TempFile& operator=(TempFile&&) = delete; - - TempFile(); - ~TempFile(); - - std::FILE* getFile(); - std::string getContents(); - - private: - std::FILE* m_file = nullptr; - #if defined(_MSC_VER) - char m_buffer[L_tmpnam] = { 0 }; - #endif - }; - - class OutputRedirect { - public: - OutputRedirect(OutputRedirect const&) = delete; - OutputRedirect& operator=(OutputRedirect const&) = delete; - OutputRedirect(OutputRedirect&&) = delete; - OutputRedirect& operator=(OutputRedirect&&) = delete; - - OutputRedirect(std::string& stdout_dest, std::string& stderr_dest); - ~OutputRedirect(); - - private: - int m_originalStdout = -1; - int m_originalStderr = -1; - TempFile m_stdoutFile; - TempFile m_stderrFile; - std::string& m_stdoutDest; - std::string& m_stderrDest; - }; - -} // end namespace Catch - -#endif // TWOBLUECUBES_CATCH_OUTPUT_REDIRECT_H -// end catch_output_redirect.h -#include -#include -#include -#include -#include - -#if defined(_MSC_VER) -#include //_dup and _dup2 -#define dup _dup -#define dup2 _dup2 -#define fileno _fileno -#else -#include // dup and dup2 -#endif - -namespace Catch { - - RedirectedStream::RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream ) - : m_originalStream( originalStream ), - m_redirectionStream( redirectionStream ), - m_prevBuf( m_originalStream.rdbuf() ) - { - m_originalStream.rdbuf( m_redirectionStream.rdbuf() ); - } - - RedirectedStream::~RedirectedStream() { - m_originalStream.rdbuf( m_prevBuf ); - } - - RedirectedStdOut::RedirectedStdOut() : m_cout( Catch::cout(), m_rss.get() ) {} - auto RedirectedStdOut::str() const -> std::string { return m_rss.str(); } - - RedirectedStdErr::RedirectedStdErr() - : m_cerr( Catch::cerr(), m_rss.get() ), - m_clog( Catch::clog(), m_rss.get() ) - {} - auto RedirectedStdErr::str() const -> std::string { return m_rss.str(); } - -#if defined(_MSC_VER) - TempFile::TempFile() { - if (tmpnam_s(m_buffer)) { - throw std::runtime_error("Could not get a temp filename"); - } - if (fopen_s(&m_file, m_buffer, "w")) { - char buffer[100]; - if (strerror_s(buffer, errno)) { - throw std::runtime_error("Could not translate errno to string"); - } - throw std::runtime_error("Could not open the temp file: " + std::string(m_buffer) + buffer); - } - } -#else - TempFile::TempFile() { - m_file = std::tmpfile(); - if (!m_file) { - throw std::runtime_error("Could not create a temp file."); - } - } - -#endif - - TempFile::~TempFile() { - // TBD: What to do about errors here? - std::fclose(m_file); - // We manually create the file on Windows only, on Linux - // it will be autodeleted -#if defined(_MSC_VER) - std::remove(m_buffer); -#endif - } - - FILE* TempFile::getFile() { - return m_file; - } - - std::string TempFile::getContents() { - std::stringstream sstr; - char buffer[100] = {}; - std::rewind(m_file); - while (std::fgets(buffer, sizeof(buffer), m_file)) { - sstr << buffer; - } - return sstr.str(); - } - - OutputRedirect::OutputRedirect(std::string& stdout_dest, std::string& stderr_dest) : - m_originalStdout(dup(1)), - m_originalStderr(dup(2)), - m_stdoutDest(stdout_dest), - m_stderrDest(stderr_dest) { - dup2(fileno(m_stdoutFile.getFile()), 1); - dup2(fileno(m_stderrFile.getFile()), 2); - } - - OutputRedirect::~OutputRedirect() { - Catch::cout() << std::flush; - fflush(stdout); - // Since we support overriding these streams, we flush cerr - // even though std::cerr is unbuffered - Catch::cerr() << std::flush; - Catch::clog() << std::flush; - fflush(stderr); - - dup2(m_originalStdout, 1); - dup2(m_originalStderr, 2); - - m_stdoutDest += m_stdoutFile.getContents(); - m_stderrDest += m_stderrFile.getContents(); - } - -} // namespace Catch - -#if defined(_MSC_VER) -#undef dup -#undef dup2 -#undef fileno -#endif -// end catch_output_redirect.cpp -// start catch_random_number_generator.cpp - -// start catch_random_number_generator.h - -#include - -namespace Catch { - - struct IConfig; - - void seedRng( IConfig const& config ); - - unsigned int rngSeed(); - - struct RandomNumberGenerator { - using result_type = unsigned int; - - static constexpr result_type (min)() { return 0; } - static constexpr result_type (max)() { return 1000000; } - - result_type operator()( result_type n ) const; - result_type operator()() const; - - template - static void shuffle( V& vector ) { - RandomNumberGenerator rng; - std::shuffle( vector.begin(), vector.end(), rng ); - } - }; - -} - -// end catch_random_number_generator.h -#include - -namespace Catch { - - void seedRng( IConfig const& config ) { - if( config.rngSeed() != 0 ) - std::srand( config.rngSeed() ); - } - unsigned int rngSeed() { - return getCurrentContext().getConfig()->rngSeed(); - } - - RandomNumberGenerator::result_type RandomNumberGenerator::operator()( result_type n ) const { - return std::rand() % n; - } - RandomNumberGenerator::result_type RandomNumberGenerator::operator()() const { - return std::rand() % (max)(); - } - -} -// end catch_random_number_generator.cpp -// start catch_registry_hub.cpp - -// start catch_test_case_registry_impl.h - -#include -#include -#include -#include - -namespace Catch { - - class TestCase; - struct IConfig; - - std::vector sortTests( IConfig const& config, std::vector const& unsortedTestCases ); - bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); - - void enforceNoDuplicateTestCases( std::vector const& functions ); - - std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); - std::vector const& getAllTestCasesSorted( IConfig const& config ); - - class TestRegistry : public ITestCaseRegistry { - public: - virtual ~TestRegistry() = default; - - virtual void registerTest( TestCase const& testCase ); - - std::vector const& getAllTests() const override; - std::vector const& getAllTestsSorted( IConfig const& config ) const override; - - private: - std::vector m_functions; - mutable RunTests::InWhatOrder m_currentSortOrder = RunTests::InDeclarationOrder; - mutable std::vector m_sortedFunctions; - std::size_t m_unnamedCount = 0; - std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised - }; - - /////////////////////////////////////////////////////////////////////////// - - class TestInvokerAsFunction : public ITestInvoker { - void(*m_testAsFunction)(); - public: - TestInvokerAsFunction( void(*testAsFunction)() ) noexcept; - - void invoke() const override; - }; - - std::string extractClassName( StringRef const& classOrQualifiedMethodName ); - - /////////////////////////////////////////////////////////////////////////// - -} // end namespace Catch - -// end catch_test_case_registry_impl.h -// start catch_reporter_registry.h - -#include - -namespace Catch { - - class ReporterRegistry : public IReporterRegistry { - - public: - - ~ReporterRegistry() override; - - IStreamingReporterPtr create( std::string const& name, IConfigPtr const& config ) const override; - - void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ); - void registerListener( IReporterFactoryPtr const& factory ); - - FactoryMap const& getFactories() const override; - Listeners const& getListeners() const override; - - private: - FactoryMap m_factories; - Listeners m_listeners; - }; -} - -// end catch_reporter_registry.h -// start catch_tag_alias_registry.h - -// start catch_tag_alias.h - -#include - -namespace Catch { - - struct TagAlias { - TagAlias(std::string const& _tag, SourceLineInfo _lineInfo); - - std::string tag; - SourceLineInfo lineInfo; - }; - -} // end namespace Catch - -// end catch_tag_alias.h -#include - -namespace Catch { - - class TagAliasRegistry : public ITagAliasRegistry { - public: - ~TagAliasRegistry() override; - TagAlias const* find( std::string const& alias ) const override; - std::string expandAliases( std::string const& unexpandedTestSpec ) const override; - void add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ); - - private: - std::map m_registry; - }; - -} // end namespace Catch - -// end catch_tag_alias_registry.h -// start catch_startup_exception_registry.h - -#include -#include - -namespace Catch { - - class StartupExceptionRegistry { - public: - void add(std::exception_ptr const& exception) noexcept; - std::vector const& getExceptions() const noexcept; - private: - std::vector m_exceptions; - }; - -} // end namespace Catch - -// end catch_startup_exception_registry.h -namespace Catch { - - namespace { - - class RegistryHub : public IRegistryHub, public IMutableRegistryHub, - private NonCopyable { - - public: // IRegistryHub - RegistryHub() = default; - IReporterRegistry const& getReporterRegistry() const override { - return m_reporterRegistry; - } - ITestCaseRegistry const& getTestCaseRegistry() const override { - return m_testCaseRegistry; - } - IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() override { - return m_exceptionTranslatorRegistry; - } - ITagAliasRegistry const& getTagAliasRegistry() const override { - return m_tagAliasRegistry; - } - StartupExceptionRegistry const& getStartupExceptionRegistry() const override { - return m_exceptionRegistry; - } - - public: // IMutableRegistryHub - void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) override { - m_reporterRegistry.registerReporter( name, factory ); - } - void registerListener( IReporterFactoryPtr const& factory ) override { - m_reporterRegistry.registerListener( factory ); - } - void registerTest( TestCase const& testInfo ) override { - m_testCaseRegistry.registerTest( testInfo ); - } - void registerTranslator( const IExceptionTranslator* translator ) override { - m_exceptionTranslatorRegistry.registerTranslator( translator ); - } - void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) override { - m_tagAliasRegistry.add( alias, tag, lineInfo ); - } - void registerStartupException() noexcept override { - m_exceptionRegistry.add(std::current_exception()); - } - - private: - TestRegistry m_testCaseRegistry; - ReporterRegistry m_reporterRegistry; - ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; - TagAliasRegistry m_tagAliasRegistry; - StartupExceptionRegistry m_exceptionRegistry; - }; - - // Single, global, instance - RegistryHub*& getTheRegistryHub() { - static RegistryHub* theRegistryHub = nullptr; - if( !theRegistryHub ) - theRegistryHub = new RegistryHub(); - return theRegistryHub; - } - } - - IRegistryHub& getRegistryHub() { - return *getTheRegistryHub(); - } - IMutableRegistryHub& getMutableRegistryHub() { - return *getTheRegistryHub(); - } - void cleanUp() { - delete getTheRegistryHub(); - getTheRegistryHub() = nullptr; - cleanUpContext(); - ReusableStringStream::cleanup(); - } - std::string translateActiveException() { - return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); - } - -} // end namespace Catch -// end catch_registry_hub.cpp -// start catch_reporter_registry.cpp - -namespace Catch { - - ReporterRegistry::~ReporterRegistry() = default; - - IStreamingReporterPtr ReporterRegistry::create( std::string const& name, IConfigPtr const& config ) const { - auto it = m_factories.find( name ); - if( it == m_factories.end() ) - return nullptr; - return it->second->create( ReporterConfig( config ) ); - } - - void ReporterRegistry::registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) { - m_factories.emplace(name, factory); - } - void ReporterRegistry::registerListener( IReporterFactoryPtr const& factory ) { - m_listeners.push_back( factory ); - } - - IReporterRegistry::FactoryMap const& ReporterRegistry::getFactories() const { - return m_factories; - } - IReporterRegistry::Listeners const& ReporterRegistry::getListeners() const { - return m_listeners; - } - -} -// end catch_reporter_registry.cpp -// start catch_result_type.cpp - -namespace Catch { - - bool isOk( ResultWas::OfType resultType ) { - return ( resultType & ResultWas::FailureBit ) == 0; - } - bool isJustInfo( int flags ) { - return flags == ResultWas::Info; - } - - ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { - return static_cast( static_cast( lhs ) | static_cast( rhs ) ); - } - - bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } - bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } - -} // end namespace Catch -// end catch_result_type.cpp -// start catch_run_context.cpp - -#include -#include -#include - -namespace Catch { - - RunContext::RunContext(IConfigPtr const& _config, IStreamingReporterPtr&& reporter) - : m_runInfo(_config->name()), - m_context(getCurrentMutableContext()), - m_config(_config), - m_reporter(std::move(reporter)), - m_lastAssertionInfo{ StringRef(), SourceLineInfo("",0), StringRef(), ResultDisposition::Normal }, - m_includeSuccessfulResults( m_config->includeSuccessfulResults() ) - { - m_context.setRunner(this); - m_context.setConfig(m_config); - m_context.setResultCapture(this); - m_reporter->testRunStarting(m_runInfo); - } - - RunContext::~RunContext() { - m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting())); - } - - void RunContext::testGroupStarting(std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount) { - m_reporter->testGroupStarting(GroupInfo(testSpec, groupIndex, groupsCount)); - } - - void RunContext::testGroupEnded(std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount) { - m_reporter->testGroupEnded(TestGroupStats(GroupInfo(testSpec, groupIndex, groupsCount), totals, aborting())); - } - - Totals RunContext::runTest(TestCase const& testCase) { - Totals prevTotals = m_totals; - - std::string redirectedCout; - std::string redirectedCerr; - - auto const& testInfo = testCase.getTestCaseInfo(); - - m_reporter->testCaseStarting(testInfo); - - m_activeTestCase = &testCase; - - ITracker& rootTracker = m_trackerContext.startRun(); - assert(rootTracker.isSectionTracker()); - static_cast(rootTracker).addInitialFilters(m_config->getSectionsToRun()); - do { - m_trackerContext.startCycle(); - m_testCaseTracker = &SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(testInfo.name, testInfo.lineInfo)); - runCurrentTest(redirectedCout, redirectedCerr); - } while (!m_testCaseTracker->isSuccessfullyCompleted() && !aborting()); - - Totals deltaTotals = m_totals.delta(prevTotals); - if (testInfo.expectedToFail() && deltaTotals.testCases.passed > 0) { - deltaTotals.assertions.failed++; - deltaTotals.testCases.passed--; - deltaTotals.testCases.failed++; - } - m_totals.testCases += deltaTotals.testCases; - m_reporter->testCaseEnded(TestCaseStats(testInfo, - deltaTotals, - redirectedCout, - redirectedCerr, - aborting())); - - m_activeTestCase = nullptr; - m_testCaseTracker = nullptr; - - return deltaTotals; - } - - IConfigPtr RunContext::config() const { - return m_config; - } - - IStreamingReporter& RunContext::reporter() const { - return *m_reporter; - } - - void RunContext::assertionEnded(AssertionResult const & result) { - if (result.getResultType() == ResultWas::Ok) { - m_totals.assertions.passed++; - m_lastAssertionPassed = true; - } else if (!result.isOk()) { - m_lastAssertionPassed = false; - if( m_activeTestCase->getTestCaseInfo().okToFail() ) - m_totals.assertions.failedButOk++; - else - m_totals.assertions.failed++; - } - else { - m_lastAssertionPassed = true; - } - - // We have no use for the return value (whether messages should be cleared), because messages were made scoped - // and should be let to clear themselves out. - static_cast(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals))); - - // Reset working state - resetAssertionInfo(); - m_lastResult = result; - } - void RunContext::resetAssertionInfo() { - m_lastAssertionInfo.macroName = StringRef(); - m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"_sr; - } - - bool RunContext::sectionStarted(SectionInfo const & sectionInfo, Counts & assertions) { - ITracker& sectionTracker = SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(sectionInfo.name, sectionInfo.lineInfo)); - if (!sectionTracker.isOpen()) - return false; - m_activeSections.push_back(§ionTracker); - - m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; - - m_reporter->sectionStarting(sectionInfo); - - assertions = m_totals.assertions; - - return true; - } - - bool RunContext::testForMissingAssertions(Counts& assertions) { - if (assertions.total() != 0) - return false; - if (!m_config->warnAboutMissingAssertions()) - return false; - if (m_trackerContext.currentTracker().hasChildren()) - return false; - m_totals.assertions.failed++; - assertions.failed++; - return true; - } - - void RunContext::sectionEnded(SectionEndInfo const & endInfo) { - Counts assertions = m_totals.assertions - endInfo.prevAssertions; - bool missingAssertions = testForMissingAssertions(assertions); - - if (!m_activeSections.empty()) { - m_activeSections.back()->close(); - m_activeSections.pop_back(); - } - - m_reporter->sectionEnded(SectionStats(endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions)); - m_messages.clear(); - } - - void RunContext::sectionEndedEarly(SectionEndInfo const & endInfo) { - if (m_unfinishedSections.empty()) - m_activeSections.back()->fail(); - else - m_activeSections.back()->close(); - m_activeSections.pop_back(); - - m_unfinishedSections.push_back(endInfo); - } - void RunContext::benchmarkStarting( BenchmarkInfo const& info ) { - m_reporter->benchmarkStarting( info ); - } - void RunContext::benchmarkEnded( BenchmarkStats const& stats ) { - m_reporter->benchmarkEnded( stats ); - } - - void RunContext::pushScopedMessage(MessageInfo const & message) { - m_messages.push_back(message); - } - - void RunContext::popScopedMessage(MessageInfo const & message) { - m_messages.erase(std::remove(m_messages.begin(), m_messages.end(), message), m_messages.end()); - } - - std::string RunContext::getCurrentTestName() const { - return m_activeTestCase - ? m_activeTestCase->getTestCaseInfo().name - : std::string(); - } - - const AssertionResult * RunContext::getLastResult() const { - return &(*m_lastResult); - } - - void RunContext::exceptionEarlyReported() { - m_shouldReportUnexpected = false; - } - - void RunContext::handleFatalErrorCondition( StringRef message ) { - // First notify reporter that bad things happened - m_reporter->fatalErrorEncountered(message); - - // Don't rebuild the result -- the stringification itself can cause more fatal errors - // Instead, fake a result data. - AssertionResultData tempResult( ResultWas::FatalErrorCondition, { false } ); - tempResult.message = message; - AssertionResult result(m_lastAssertionInfo, tempResult); - - assertionEnded(result); - - handleUnfinishedSections(); - - // Recreate section for test case (as we will lose the one that was in scope) - auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); - SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description); - - Counts assertions; - assertions.failed = 1; - SectionStats testCaseSectionStats(testCaseSection, assertions, 0, false); - m_reporter->sectionEnded(testCaseSectionStats); - - auto const& testInfo = m_activeTestCase->getTestCaseInfo(); - - Totals deltaTotals; - deltaTotals.testCases.failed = 1; - deltaTotals.assertions.failed = 1; - m_reporter->testCaseEnded(TestCaseStats(testInfo, - deltaTotals, - std::string(), - std::string(), - false)); - m_totals.testCases.failed++; - testGroupEnded(std::string(), m_totals, 1, 1); - m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false)); - } - - bool RunContext::lastAssertionPassed() { - return m_lastAssertionPassed; - } - - void RunContext::assertionPassed() { - m_lastAssertionPassed = true; - ++m_totals.assertions.passed; - resetAssertionInfo(); - } - - bool RunContext::aborting() const { - return m_totals.assertions.failed == static_cast(m_config->abortAfter()); - } - - void RunContext::runCurrentTest(std::string & redirectedCout, std::string & redirectedCerr) { - auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); - SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description); - m_reporter->sectionStarting(testCaseSection); - Counts prevAssertions = m_totals.assertions; - double duration = 0; - m_shouldReportUnexpected = true; - m_lastAssertionInfo = { "TEST_CASE"_sr, testCaseInfo.lineInfo, StringRef(), ResultDisposition::Normal }; - - seedRng(*m_config); - - Timer timer; - try { - if (m_reporter->getPreferences().shouldRedirectStdOut) { -#if !defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) - RedirectedStdOut redirectedStdOut; - RedirectedStdErr redirectedStdErr; - - timer.start(); - invokeActiveTestCase(); - redirectedCout += redirectedStdOut.str(); - redirectedCerr += redirectedStdErr.str(); -#else - OutputRedirect r(redirectedCout, redirectedCerr); - timer.start(); - invokeActiveTestCase(); -#endif - } else { - timer.start(); - invokeActiveTestCase(); - } - duration = timer.getElapsedSeconds(); - } catch (TestFailureException&) { - // This just means the test was aborted due to failure - } catch (...) { - // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions - // are reported without translation at the point of origin. - if( m_shouldReportUnexpected ) { - AssertionReaction dummyReaction; - handleUnexpectedInflightException( m_lastAssertionInfo, translateActiveException(), dummyReaction ); - } - } - Counts assertions = m_totals.assertions - prevAssertions; - bool missingAssertions = testForMissingAssertions(assertions); - - m_testCaseTracker->close(); - handleUnfinishedSections(); - m_messages.clear(); - - SectionStats testCaseSectionStats(testCaseSection, assertions, duration, missingAssertions); - m_reporter->sectionEnded(testCaseSectionStats); - } - - void RunContext::invokeActiveTestCase() { - FatalConditionHandler fatalConditionHandler; // Handle signals - m_activeTestCase->invoke(); - fatalConditionHandler.reset(); - } - - void RunContext::handleUnfinishedSections() { - // If sections ended prematurely due to an exception we stored their - // infos here so we can tear them down outside the unwind process. - for (auto it = m_unfinishedSections.rbegin(), - itEnd = m_unfinishedSections.rend(); - it != itEnd; - ++it) - sectionEnded(*it); - m_unfinishedSections.clear(); - } - - void RunContext::handleExpr( - AssertionInfo const& info, - ITransientExpression const& expr, - AssertionReaction& reaction - ) { - m_reporter->assertionStarting( info ); - - bool negated = isFalseTest( info.resultDisposition ); - bool result = expr.getResult() != negated; - - if( result ) { - if (!m_includeSuccessfulResults) { - assertionPassed(); - } - else { - reportExpr(info, ResultWas::Ok, &expr, negated); - } - } - else { - reportExpr(info, ResultWas::ExpressionFailed, &expr, negated ); - populateReaction( reaction ); - } - } - void RunContext::reportExpr( - AssertionInfo const &info, - ResultWas::OfType resultType, - ITransientExpression const *expr, - bool negated ) { - - m_lastAssertionInfo = info; - AssertionResultData data( resultType, LazyExpression( negated ) ); - - AssertionResult assertionResult{ info, data }; - assertionResult.m_resultData.lazyExpression.m_transientExpression = expr; - - assertionEnded( assertionResult ); - } - - void RunContext::handleMessage( - AssertionInfo const& info, - ResultWas::OfType resultType, - StringRef const& message, - AssertionReaction& reaction - ) { - m_reporter->assertionStarting( info ); - - m_lastAssertionInfo = info; - - AssertionResultData data( resultType, LazyExpression( false ) ); - data.message = message; - AssertionResult assertionResult{ m_lastAssertionInfo, data }; - assertionEnded( assertionResult ); - if( !assertionResult.isOk() ) - populateReaction( reaction ); - } - void RunContext::handleUnexpectedExceptionNotThrown( - AssertionInfo const& info, - AssertionReaction& reaction - ) { - handleNonExpr(info, Catch::ResultWas::DidntThrowException, reaction); - } - - void RunContext::handleUnexpectedInflightException( - AssertionInfo const& info, - std::string const& message, - AssertionReaction& reaction - ) { - m_lastAssertionInfo = info; - - AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); - data.message = message; - AssertionResult assertionResult{ info, data }; - assertionEnded( assertionResult ); - populateReaction( reaction ); - } - - void RunContext::populateReaction( AssertionReaction& reaction ) { - reaction.shouldDebugBreak = m_config->shouldDebugBreak(); - reaction.shouldThrow = aborting() || (m_lastAssertionInfo.resultDisposition & ResultDisposition::Normal); - } - - void RunContext::handleIncomplete( - AssertionInfo const& info - ) { - m_lastAssertionInfo = info; - - AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); - data.message = "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"; - AssertionResult assertionResult{ info, data }; - assertionEnded( assertionResult ); - } - void RunContext::handleNonExpr( - AssertionInfo const &info, - ResultWas::OfType resultType, - AssertionReaction &reaction - ) { - m_lastAssertionInfo = info; - - AssertionResultData data( resultType, LazyExpression( false ) ); - AssertionResult assertionResult{ info, data }; - assertionEnded( assertionResult ); - - if( !assertionResult.isOk() ) - populateReaction( reaction ); - } - - IResultCapture& getResultCapture() { - if (auto* capture = getCurrentContext().getResultCapture()) - return *capture; - else - CATCH_INTERNAL_ERROR("No result capture instance"); - } -} -// end catch_run_context.cpp -// start catch_section.cpp - -namespace Catch { - - Section::Section( SectionInfo const& info ) - : m_info( info ), - m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) - { - m_timer.start(); - } - - Section::~Section() { - if( m_sectionIncluded ) { - SectionEndInfo endInfo( m_info, m_assertions, m_timer.getElapsedSeconds() ); - if( uncaught_exceptions() ) - getResultCapture().sectionEndedEarly( endInfo ); - else - getResultCapture().sectionEnded( endInfo ); - } - } - - // This indicates whether the section should be executed or not - Section::operator bool() const { - return m_sectionIncluded; - } - -} // end namespace Catch -// end catch_section.cpp -// start catch_section_info.cpp - -namespace Catch { - - SectionInfo::SectionInfo - ( SourceLineInfo const& _lineInfo, - std::string const& _name, - std::string const& _description ) - : name( _name ), - description( _description ), - lineInfo( _lineInfo ) - {} - - SectionEndInfo::SectionEndInfo( SectionInfo const& _sectionInfo, Counts const& _prevAssertions, double _durationInSeconds ) - : sectionInfo( _sectionInfo ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) - {} - -} // end namespace Catch -// end catch_section_info.cpp -// start catch_session.cpp - -// start catch_session.h - -#include - -namespace Catch { - - class Session : NonCopyable { - public: - - Session(); - ~Session() override; - - void showHelp() const; - void libIdentify(); - - int applyCommandLine( int argc, char const * const * argv ); - - void useConfigData( ConfigData const& configData ); - - int run( int argc, char* argv[] ); - #if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(UNICODE) - int run( int argc, wchar_t* const argv[] ); - #endif - int run(); - - clara::Parser const& cli() const; - void cli( clara::Parser const& newParser ); - ConfigData& configData(); - Config& config(); - private: - int runInternal(); - - clara::Parser m_cli; - ConfigData m_configData; - std::shared_ptr m_config; - bool m_startupExceptions = false; - }; - -} // end namespace Catch - -// end catch_session.h -// start catch_version.h - -#include - -namespace Catch { - - // Versioning information - struct Version { - Version( Version const& ) = delete; - Version& operator=( Version const& ) = delete; - Version( unsigned int _majorVersion, - unsigned int _minorVersion, - unsigned int _patchNumber, - char const * const _branchName, - unsigned int _buildNumber ); - - unsigned int const majorVersion; - unsigned int const minorVersion; - unsigned int const patchNumber; - - // buildNumber is only used if branchName is not null - char const * const branchName; - unsigned int const buildNumber; - - friend std::ostream& operator << ( std::ostream& os, Version const& version ); - }; - - Version const& libraryVersion(); -} - -// end catch_version.h -#include -#include - -namespace Catch { - - namespace { - const int MaxExitCode = 255; - - IStreamingReporterPtr createReporter(std::string const& reporterName, IConfigPtr const& config) { - auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, config); - CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << "'"); - - return reporter; - } - - IStreamingReporterPtr makeReporter(std::shared_ptr const& config) { - if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty()) { - return createReporter(config->getReporterName(), config); - } - - auto multi = std::unique_ptr(new ListeningReporter); - - auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners(); - for (auto const& listener : listeners) { - multi->addListener(listener->create(Catch::ReporterConfig(config))); - } - multi->addReporter(createReporter(config->getReporterName(), config)); - return std::move(multi); - } - - Catch::Totals runTests(std::shared_ptr const& config) { - // FixMe: Add listeners in order first, then add reporters. - - auto reporter = makeReporter(config); - - RunContext context(config, std::move(reporter)); - - Totals totals; - - context.testGroupStarting(config->name(), 1, 1); - - TestSpec testSpec = config->testSpec(); - - auto const& allTestCases = getAllTestCasesSorted(*config); - for (auto const& testCase : allTestCases) { - if (!context.aborting() && matchTest(testCase, testSpec, *config)) - totals += context.runTest(testCase); - else - context.reporter().skipTest(testCase); - } - - if (config->warnAboutNoTests() && totals.testCases.total() == 0) { - ReusableStringStream testConfig; - - bool first = true; - for (const auto& input : config->getTestsOrTags()) { - if (!first) { testConfig << ' '; } - first = false; - testConfig << input; - } - - context.reporter().noMatchingTestCases(testConfig.str()); - totals.error = -1; - } - - context.testGroupEnded(config->name(), totals, 1, 1); - return totals; - } - - void applyFilenamesAsTags(Catch::IConfig const& config) { - auto& tests = const_cast&>(getAllTestCasesSorted(config)); - for (auto& testCase : tests) { - auto tags = testCase.tags; - - std::string filename = testCase.lineInfo.file; - auto lastSlash = filename.find_last_of("\\/"); - if (lastSlash != std::string::npos) { - filename.erase(0, lastSlash); - filename[0] = '#'; - } - - auto lastDot = filename.find_last_of('.'); - if (lastDot != std::string::npos) { - filename.erase(lastDot); - } - - tags.push_back(std::move(filename)); - setTags(testCase, tags); - } - } - - } // anon namespace - - Session::Session() { - static bool alreadyInstantiated = false; - if( alreadyInstantiated ) { - try { CATCH_INTERNAL_ERROR( "Only one instance of Catch::Session can ever be used" ); } - catch(...) { getMutableRegistryHub().registerStartupException(); } - } - - const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions(); - if ( !exceptions.empty() ) { - m_startupExceptions = true; - Colour colourGuard( Colour::Red ); - Catch::cerr() << "Errors occurred during startup!" << '\n'; - // iterate over all exceptions and notify user - for ( const auto& ex_ptr : exceptions ) { - try { - std::rethrow_exception(ex_ptr); - } catch ( std::exception const& ex ) { - Catch::cerr() << Column( ex.what() ).indent(2) << '\n'; - } - } - } - - alreadyInstantiated = true; - m_cli = makeCommandLineParser( m_configData ); - } - Session::~Session() { - Catch::cleanUp(); - } - - void Session::showHelp() const { - Catch::cout() - << "\nCatch v" << libraryVersion() << "\n" - << m_cli << std::endl - << "For more detailed usage please see the project docs\n" << std::endl; - } - void Session::libIdentify() { - Catch::cout() - << std::left << std::setw(16) << "description: " << "A Catch test executable\n" - << std::left << std::setw(16) << "category: " << "testframework\n" - << std::left << std::setw(16) << "framework: " << "Catch Test\n" - << std::left << std::setw(16) << "version: " << libraryVersion() << std::endl; - } - - int Session::applyCommandLine( int argc, char const * const * argv ) { - if( m_startupExceptions ) - return 1; - - auto result = m_cli.parse( clara::Args( argc, argv ) ); - if( !result ) { - Catch::cerr() - << Colour( Colour::Red ) - << "\nError(s) in input:\n" - << Column( result.errorMessage() ).indent( 2 ) - << "\n\n"; - Catch::cerr() << "Run with -? for usage\n" << std::endl; - return MaxExitCode; - } - - if( m_configData.showHelp ) - showHelp(); - if( m_configData.libIdentify ) - libIdentify(); - m_config.reset(); - return 0; - } - - void Session::useConfigData( ConfigData const& configData ) { - m_configData = configData; - m_config.reset(); - } - - int Session::run( int argc, char* argv[] ) { - if( m_startupExceptions ) - return 1; - int returnCode = applyCommandLine( argc, argv ); - if( returnCode == 0 ) - returnCode = run(); - return returnCode; - } - -#if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(UNICODE) - int Session::run( int argc, wchar_t* const argv[] ) { - - char **utf8Argv = new char *[ argc ]; - - for ( int i = 0; i < argc; ++i ) { - int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, NULL, 0, NULL, NULL ); - - utf8Argv[ i ] = new char[ bufSize ]; - - WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, NULL, NULL ); - } - - int returnCode = run( argc, utf8Argv ); - - for ( int i = 0; i < argc; ++i ) - delete [] utf8Argv[ i ]; - - delete [] utf8Argv; - - return returnCode; - } -#endif - int Session::run() { - if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeStart ) != 0 ) { - Catch::cout() << "...waiting for enter/ return before starting" << std::endl; - static_cast(std::getchar()); - } - int exitCode = runInternal(); - if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) { - Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << std::endl; - static_cast(std::getchar()); - } - return exitCode; - } - - clara::Parser const& Session::cli() const { - return m_cli; - } - void Session::cli( clara::Parser const& newParser ) { - m_cli = newParser; - } - ConfigData& Session::configData() { - return m_configData; - } - Config& Session::config() { - if( !m_config ) - m_config = std::make_shared( m_configData ); - return *m_config; - } - - int Session::runInternal() { - if( m_startupExceptions ) - return 1; - - if( m_configData.showHelp || m_configData.libIdentify ) - return 0; - - try - { - config(); // Force config to be constructed - - seedRng( *m_config ); - - if( m_configData.filenamesAsTags ) - applyFilenamesAsTags( *m_config ); - - // Handle list request - if( Option listed = list( config() ) ) - return static_cast( *listed ); - - auto totals = runTests( m_config ); - // Note that on unices only the lower 8 bits are usually used, clamping - // the return value to 255 prevents false negative when some multiple - // of 256 tests has failed - return (std::min) (MaxExitCode, (std::max) (totals.error, static_cast(totals.assertions.failed))); - } - catch( std::exception& ex ) { - Catch::cerr() << ex.what() << std::endl; - return MaxExitCode; - } - } - -} // end namespace Catch -// end catch_session.cpp -// start catch_startup_exception_registry.cpp - -namespace Catch { - void StartupExceptionRegistry::add( std::exception_ptr const& exception ) noexcept { - try { - m_exceptions.push_back(exception); - } - catch(...) { - // If we run out of memory during start-up there's really not a lot more we can do about it - std::terminate(); - } - } - - std::vector const& StartupExceptionRegistry::getExceptions() const noexcept { - return m_exceptions; - } - -} // end namespace Catch -// end catch_startup_exception_registry.cpp -// start catch_stream.cpp - -#include -#include -#include -#include -#include -#include - -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wexit-time-destructors" -#endif - -namespace Catch { - - Catch::IStream::~IStream() = default; - - namespace detail { namespace { - template - class StreamBufImpl : public std::streambuf { - char data[bufferSize]; - WriterF m_writer; - - public: - StreamBufImpl() { - setp( data, data + sizeof(data) ); - } - - ~StreamBufImpl() noexcept { - StreamBufImpl::sync(); - } - - private: - int overflow( int c ) override { - sync(); - - if( c != EOF ) { - if( pbase() == epptr() ) - m_writer( std::string( 1, static_cast( c ) ) ); - else - sputc( static_cast( c ) ); - } - return 0; - } - - int sync() override { - if( pbase() != pptr() ) { - m_writer( std::string( pbase(), static_cast( pptr() - pbase() ) ) ); - setp( pbase(), epptr() ); - } - return 0; - } - }; - - /////////////////////////////////////////////////////////////////////////// - - struct OutputDebugWriter { - - void operator()( std::string const&str ) { - writeToDebugConsole( str ); - } - }; - - /////////////////////////////////////////////////////////////////////////// - - class FileStream : public IStream { - mutable std::ofstream m_ofs; - public: - FileStream( StringRef filename ) { - m_ofs.open( filename.c_str() ); - CATCH_ENFORCE( !m_ofs.fail(), "Unable to open file: '" << filename << "'" ); - } - ~FileStream() override = default; - public: // IStream - std::ostream& stream() const override { - return m_ofs; - } - }; - - /////////////////////////////////////////////////////////////////////////// - - class CoutStream : public IStream { - mutable std::ostream m_os; - public: - // Store the streambuf from cout up-front because - // cout may get redirected when running tests - CoutStream() : m_os( Catch::cout().rdbuf() ) {} - ~CoutStream() override = default; - - public: // IStream - std::ostream& stream() const override { return m_os; } - }; - - /////////////////////////////////////////////////////////////////////////// - - class DebugOutStream : public IStream { - std::unique_ptr> m_streamBuf; - mutable std::ostream m_os; - public: - DebugOutStream() - : m_streamBuf( new StreamBufImpl() ), - m_os( m_streamBuf.get() ) - {} - - ~DebugOutStream() override = default; - - public: // IStream - std::ostream& stream() const override { return m_os; } - }; - - }} // namespace anon::detail - - /////////////////////////////////////////////////////////////////////////// - - auto makeStream( StringRef const &filename ) -> IStream const* { - if( filename.empty() ) - return new detail::CoutStream(); - else if( filename[0] == '%' ) { - if( filename == "%debug" ) - return new detail::DebugOutStream(); - else - CATCH_ERROR( "Unrecognised stream: '" << filename << "'" ); - } - else - return new detail::FileStream( filename ); - } - - // This class encapsulates the idea of a pool of ostringstreams that can be reused. - struct StringStreams { - std::vector> m_streams; - std::vector m_unused; - std::ostringstream m_referenceStream; // Used for copy state/ flags from - static StringStreams* s_instance; - - auto add() -> std::size_t { - if( m_unused.empty() ) { - m_streams.push_back( std::unique_ptr( new std::ostringstream ) ); - return m_streams.size()-1; - } - else { - auto index = m_unused.back(); - m_unused.pop_back(); - return index; - } - } - - void release( std::size_t index ) { - m_streams[index]->copyfmt( m_referenceStream ); // Restore initial flags and other state - m_unused.push_back(index); - } - - // !TBD: put in TLS - static auto instance() -> StringStreams& { - if( !s_instance ) - s_instance = new StringStreams(); - return *s_instance; - } - static void cleanup() { - delete s_instance; - s_instance = nullptr; - } - }; - - StringStreams* StringStreams::s_instance = nullptr; - - void ReusableStringStream::cleanup() { - StringStreams::cleanup(); - } - - ReusableStringStream::ReusableStringStream() - : m_index( StringStreams::instance().add() ), - m_oss( StringStreams::instance().m_streams[m_index].get() ) - {} - - ReusableStringStream::~ReusableStringStream() { - static_cast( m_oss )->str(""); - m_oss->clear(); - StringStreams::instance().release( m_index ); - } - - auto ReusableStringStream::str() const -> std::string { - return static_cast( m_oss )->str(); - } - - /////////////////////////////////////////////////////////////////////////// - -#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions - std::ostream& cout() { return std::cout; } - std::ostream& cerr() { return std::cerr; } - std::ostream& clog() { return std::clog; } -#endif -} - -#if defined(__clang__) -# pragma clang diagnostic pop -#endif -// end catch_stream.cpp -// start catch_string_manip.cpp - -#include -#include -#include -#include - -namespace Catch { - - bool startsWith( std::string const& s, std::string const& prefix ) { - return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin()); - } - bool startsWith( std::string const& s, char prefix ) { - return !s.empty() && s[0] == prefix; - } - bool endsWith( std::string const& s, std::string const& suffix ) { - return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin()); - } - bool endsWith( std::string const& s, char suffix ) { - return !s.empty() && s[s.size()-1] == suffix; - } - bool contains( std::string const& s, std::string const& infix ) { - return s.find( infix ) != std::string::npos; - } - char toLowerCh(char c) { - return static_cast( std::tolower( c ) ); - } - void toLowerInPlace( std::string& s ) { - std::transform( s.begin(), s.end(), s.begin(), toLowerCh ); - } - std::string toLower( std::string const& s ) { - std::string lc = s; - toLowerInPlace( lc ); - return lc; - } - std::string trim( std::string const& str ) { - static char const* whitespaceChars = "\n\r\t "; - std::string::size_type start = str.find_first_not_of( whitespaceChars ); - std::string::size_type end = str.find_last_not_of( whitespaceChars ); - - return start != std::string::npos ? str.substr( start, 1+end-start ) : std::string(); - } - - bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { - bool replaced = false; - std::size_t i = str.find( replaceThis ); - while( i != std::string::npos ) { - replaced = true; - str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() ); - if( i < str.size()-withThis.size() ) - i = str.find( replaceThis, i+withThis.size() ); - else - i = std::string::npos; - } - return replaced; - } - - pluralise::pluralise( std::size_t count, std::string const& label ) - : m_count( count ), - m_label( label ) - {} - - std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { - os << pluraliser.m_count << ' ' << pluraliser.m_label; - if( pluraliser.m_count != 1 ) - os << 's'; - return os; - } - -} -// end catch_string_manip.cpp -// start catch_stringref.cpp - -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wexit-time-destructors" -#endif - -#include -#include -#include - -namespace { - const uint32_t byte_2_lead = 0xC0; - const uint32_t byte_3_lead = 0xE0; - const uint32_t byte_4_lead = 0xF0; -} - -namespace Catch { - StringRef::StringRef( char const* rawChars ) noexcept - : StringRef( rawChars, static_cast(std::strlen(rawChars) ) ) - {} - - StringRef::operator std::string() const { - return std::string( m_start, m_size ); - } - - void StringRef::swap( StringRef& other ) noexcept { - std::swap( m_start, other.m_start ); - std::swap( m_size, other.m_size ); - std::swap( m_data, other.m_data ); - } - - auto StringRef::c_str() const -> char const* { - if( isSubstring() ) - const_cast( this )->takeOwnership(); - return m_start; - } - auto StringRef::currentData() const noexcept -> char const* { - return m_start; - } - - auto StringRef::isOwned() const noexcept -> bool { - return m_data != nullptr; - } - auto StringRef::isSubstring() const noexcept -> bool { - return m_start[m_size] != '\0'; - } - - void StringRef::takeOwnership() { - if( !isOwned() ) { - m_data = new char[m_size+1]; - memcpy( m_data, m_start, m_size ); - m_data[m_size] = '\0'; - m_start = m_data; - } - } - auto StringRef::substr( size_type start, size_type size ) const noexcept -> StringRef { - if( start < m_size ) - return StringRef( m_start+start, size ); - else - return StringRef(); - } - auto StringRef::operator == ( StringRef const& other ) const noexcept -> bool { - return - size() == other.size() && - (std::strncmp( m_start, other.m_start, size() ) == 0); - } - auto StringRef::operator != ( StringRef const& other ) const noexcept -> bool { - return !operator==( other ); - } - - auto StringRef::operator[](size_type index) const noexcept -> char { - return m_start[index]; - } - - auto StringRef::numberOfCharacters() const noexcept -> size_type { - size_type noChars = m_size; - // Make adjustments for uft encodings - for( size_type i=0; i < m_size; ++i ) { - char c = m_start[i]; - if( ( c & byte_2_lead ) == byte_2_lead ) { - noChars--; - if (( c & byte_3_lead ) == byte_3_lead ) - noChars--; - if( ( c & byte_4_lead ) == byte_4_lead ) - noChars--; - } - } - return noChars; - } - - auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string { - std::string str; - str.reserve( lhs.size() + rhs.size() ); - str += lhs; - str += rhs; - return str; - } - auto operator + ( StringRef const& lhs, const char* rhs ) -> std::string { - return std::string( lhs ) + std::string( rhs ); - } - auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string { - return std::string( lhs ) + std::string( rhs ); - } - - auto operator << ( std::ostream& os, StringRef const& str ) -> std::ostream& { - return os.write(str.currentData(), str.size()); - } - - auto operator+=( std::string& lhs, StringRef const& rhs ) -> std::string& { - lhs.append(rhs.currentData(), rhs.size()); - return lhs; - } - -} // namespace Catch - -#if defined(__clang__) -# pragma clang diagnostic pop -#endif -// end catch_stringref.cpp -// start catch_tag_alias.cpp - -namespace Catch { - TagAlias::TagAlias(std::string const & _tag, SourceLineInfo _lineInfo): tag(_tag), lineInfo(_lineInfo) {} -} -// end catch_tag_alias.cpp -// start catch_tag_alias_autoregistrar.cpp - -namespace Catch { - - RegistrarForTagAliases::RegistrarForTagAliases(char const* alias, char const* tag, SourceLineInfo const& lineInfo) { - try { - getMutableRegistryHub().registerTagAlias(alias, tag, lineInfo); - } catch (...) { - // Do not throw when constructing global objects, instead register the exception to be processed later - getMutableRegistryHub().registerStartupException(); - } - } - -} -// end catch_tag_alias_autoregistrar.cpp -// start catch_tag_alias_registry.cpp - -#include - -namespace Catch { - - TagAliasRegistry::~TagAliasRegistry() {} - - TagAlias const* TagAliasRegistry::find( std::string const& alias ) const { - auto it = m_registry.find( alias ); - if( it != m_registry.end() ) - return &(it->second); - else - return nullptr; - } - - std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { - std::string expandedTestSpec = unexpandedTestSpec; - for( auto const& registryKvp : m_registry ) { - std::size_t pos = expandedTestSpec.find( registryKvp.first ); - if( pos != std::string::npos ) { - expandedTestSpec = expandedTestSpec.substr( 0, pos ) + - registryKvp.second.tag + - expandedTestSpec.substr( pos + registryKvp.first.size() ); - } - } - return expandedTestSpec; - } - - void TagAliasRegistry::add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) { - CATCH_ENFORCE( startsWith(alias, "[@") && endsWith(alias, ']'), - "error: tag alias, '" << alias << "' is not of the form [@alias name].\n" << lineInfo ); - - CATCH_ENFORCE( m_registry.insert(std::make_pair(alias, TagAlias(tag, lineInfo))).second, - "error: tag alias, '" << alias << "' already registered.\n" - << "\tFirst seen at: " << find(alias)->lineInfo << "\n" - << "\tRedefined at: " << lineInfo ); - } - - ITagAliasRegistry::~ITagAliasRegistry() {} - - ITagAliasRegistry const& ITagAliasRegistry::get() { - return getRegistryHub().getTagAliasRegistry(); - } - -} // end namespace Catch -// end catch_tag_alias_registry.cpp -// start catch_test_case_info.cpp - -#include -#include -#include -#include - -namespace Catch { - - TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { - if( startsWith( tag, '.' ) || - tag == "!hide" ) - return TestCaseInfo::IsHidden; - else if( tag == "!throws" ) - return TestCaseInfo::Throws; - else if( tag == "!shouldfail" ) - return TestCaseInfo::ShouldFail; - else if( tag == "!mayfail" ) - return TestCaseInfo::MayFail; - else if( tag == "!nonportable" ) - return TestCaseInfo::NonPortable; - else if( tag == "!benchmark" ) - return static_cast( TestCaseInfo::Benchmark | TestCaseInfo::IsHidden ); - else - return TestCaseInfo::None; - } - bool isReservedTag( std::string const& tag ) { - return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( static_cast(tag[0]) ); - } - void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { - CATCH_ENFORCE( !isReservedTag(tag), - "Tag name: [" << tag << "] is not allowed.\n" - << "Tag names starting with non alpha-numeric characters are reserved\n" - << _lineInfo ); - } - - TestCase makeTestCase( ITestInvoker* _testCase, - std::string const& _className, - NameAndTags const& nameAndTags, - SourceLineInfo const& _lineInfo ) - { - bool isHidden = false; - - // Parse out tags - std::vector tags; - std::string desc, tag; - bool inTag = false; - std::string _descOrTags = nameAndTags.tags; - for (char c : _descOrTags) { - if( !inTag ) { - if( c == '[' ) - inTag = true; - else - desc += c; - } - else { - if( c == ']' ) { - TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag ); - if( ( prop & TestCaseInfo::IsHidden ) != 0 ) - isHidden = true; - else if( prop == TestCaseInfo::None ) - enforceNotReservedTag( tag, _lineInfo ); - - tags.push_back( tag ); - tag.clear(); - inTag = false; - } - else - tag += c; - } - } - if( isHidden ) { - tags.push_back( "." ); - } - - TestCaseInfo info( nameAndTags.name, _className, desc, tags, _lineInfo ); - return TestCase( _testCase, std::move(info) ); - } - - void setTags( TestCaseInfo& testCaseInfo, std::vector tags ) { - std::sort(begin(tags), end(tags)); - tags.erase(std::unique(begin(tags), end(tags)), end(tags)); - testCaseInfo.lcaseTags.clear(); - - for( auto const& tag : tags ) { - std::string lcaseTag = toLower( tag ); - testCaseInfo.properties = static_cast( testCaseInfo.properties | parseSpecialTag( lcaseTag ) ); - testCaseInfo.lcaseTags.push_back( lcaseTag ); - } - testCaseInfo.tags = std::move(tags); - } - - TestCaseInfo::TestCaseInfo( std::string const& _name, - std::string const& _className, - std::string const& _description, - std::vector const& _tags, - SourceLineInfo const& _lineInfo ) - : name( _name ), - className( _className ), - description( _description ), - lineInfo( _lineInfo ), - properties( None ) - { - setTags( *this, _tags ); - } - - bool TestCaseInfo::isHidden() const { - return ( properties & IsHidden ) != 0; - } - bool TestCaseInfo::throws() const { - return ( properties & Throws ) != 0; - } - bool TestCaseInfo::okToFail() const { - return ( properties & (ShouldFail | MayFail ) ) != 0; - } - bool TestCaseInfo::expectedToFail() const { - return ( properties & (ShouldFail ) ) != 0; - } - - std::string TestCaseInfo::tagsAsString() const { - std::string ret; - // '[' and ']' per tag - std::size_t full_size = 2 * tags.size(); - for (const auto& tag : tags) { - full_size += tag.size(); - } - ret.reserve(full_size); - for (const auto& tag : tags) { - ret.push_back('['); - ret.append(tag); - ret.push_back(']'); - } - - return ret; - } - - TestCase::TestCase( ITestInvoker* testCase, TestCaseInfo&& info ) : TestCaseInfo( std::move(info) ), test( testCase ) {} - - TestCase TestCase::withName( std::string const& _newName ) const { - TestCase other( *this ); - other.name = _newName; - return other; - } - - void TestCase::invoke() const { - test->invoke(); - } - - bool TestCase::operator == ( TestCase const& other ) const { - return test.get() == other.test.get() && - name == other.name && - className == other.className; - } - - bool TestCase::operator < ( TestCase const& other ) const { - return name < other.name; - } - - TestCaseInfo const& TestCase::getTestCaseInfo() const - { - return *this; - } - -} // end namespace Catch -// end catch_test_case_info.cpp -// start catch_test_case_registry_impl.cpp - -#include - -namespace Catch { - - std::vector sortTests( IConfig const& config, std::vector const& unsortedTestCases ) { - - std::vector sorted = unsortedTestCases; - - switch( config.runOrder() ) { - case RunTests::InLexicographicalOrder: - std::sort( sorted.begin(), sorted.end() ); - break; - case RunTests::InRandomOrder: - seedRng( config ); - RandomNumberGenerator::shuffle( sorted ); - break; - case RunTests::InDeclarationOrder: - // already in declaration order - break; - } - return sorted; - } - bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) { - return testSpec.matches( testCase ) && ( config.allowThrows() || !testCase.throws() ); - } - - void enforceNoDuplicateTestCases( std::vector const& functions ) { - std::set seenFunctions; - for( auto const& function : functions ) { - auto prev = seenFunctions.insert( function ); - CATCH_ENFORCE( prev.second, - "error: TEST_CASE( \"" << function.name << "\" ) already defined.\n" - << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n" - << "\tRedefined at " << function.getTestCaseInfo().lineInfo ); - } - } - - std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ) { - std::vector filtered; - filtered.reserve( testCases.size() ); - for( auto const& testCase : testCases ) - if( matchTest( testCase, testSpec, config ) ) - filtered.push_back( testCase ); - return filtered; - } - std::vector const& getAllTestCasesSorted( IConfig const& config ) { - return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config ); - } - - void TestRegistry::registerTest( TestCase const& testCase ) { - std::string name = testCase.getTestCaseInfo().name; - if( name.empty() ) { - ReusableStringStream rss; - rss << "Anonymous test case " << ++m_unnamedCount; - return registerTest( testCase.withName( rss.str() ) ); - } - m_functions.push_back( testCase ); - } - - std::vector const& TestRegistry::getAllTests() const { - return m_functions; - } - std::vector const& TestRegistry::getAllTestsSorted( IConfig const& config ) const { - if( m_sortedFunctions.empty() ) - enforceNoDuplicateTestCases( m_functions ); - - if( m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) { - m_sortedFunctions = sortTests( config, m_functions ); - m_currentSortOrder = config.runOrder(); - } - return m_sortedFunctions; - } - - /////////////////////////////////////////////////////////////////////////// - TestInvokerAsFunction::TestInvokerAsFunction( void(*testAsFunction)() ) noexcept : m_testAsFunction( testAsFunction ) {} - - void TestInvokerAsFunction::invoke() const { - m_testAsFunction(); - } - - std::string extractClassName( StringRef const& classOrQualifiedMethodName ) { - std::string className = classOrQualifiedMethodName; - if( startsWith( className, '&' ) ) - { - std::size_t lastColons = className.rfind( "::" ); - std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); - if( penultimateColons == std::string::npos ) - penultimateColons = 1; - className = className.substr( penultimateColons, lastColons-penultimateColons ); - } - return className; - } - -} // end namespace Catch -// end catch_test_case_registry_impl.cpp -// start catch_test_case_tracker.cpp - -#include -#include -#include -#include -#include - -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wexit-time-destructors" -#endif - -namespace Catch { -namespace TestCaseTracking { - - NameAndLocation::NameAndLocation( std::string const& _name, SourceLineInfo const& _location ) - : name( _name ), - location( _location ) - {} - - ITracker::~ITracker() = default; - - TrackerContext& TrackerContext::instance() { - static TrackerContext s_instance; - return s_instance; - } - - ITracker& TrackerContext::startRun() { - m_rootTracker = std::make_shared( NameAndLocation( "{root}", CATCH_INTERNAL_LINEINFO ), *this, nullptr ); - m_currentTracker = nullptr; - m_runState = Executing; - return *m_rootTracker; - } - - void TrackerContext::endRun() { - m_rootTracker.reset(); - m_currentTracker = nullptr; - m_runState = NotStarted; - } - - void TrackerContext::startCycle() { - m_currentTracker = m_rootTracker.get(); - m_runState = Executing; - } - void TrackerContext::completeCycle() { - m_runState = CompletedCycle; - } - - bool TrackerContext::completedCycle() const { - return m_runState == CompletedCycle; - } - ITracker& TrackerContext::currentTracker() { - return *m_currentTracker; - } - void TrackerContext::setCurrentTracker( ITracker* tracker ) { - m_currentTracker = tracker; - } - - TrackerBase::TrackerHasName::TrackerHasName( NameAndLocation const& nameAndLocation ) : m_nameAndLocation( nameAndLocation ) {} - bool TrackerBase::TrackerHasName::operator ()( ITrackerPtr const& tracker ) const { - return - tracker->nameAndLocation().name == m_nameAndLocation.name && - tracker->nameAndLocation().location == m_nameAndLocation.location; - } - - TrackerBase::TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) - : m_nameAndLocation( nameAndLocation ), - m_ctx( ctx ), - m_parent( parent ) - {} - - NameAndLocation const& TrackerBase::nameAndLocation() const { - return m_nameAndLocation; - } - bool TrackerBase::isComplete() const { - return m_runState == CompletedSuccessfully || m_runState == Failed; - } - bool TrackerBase::isSuccessfullyCompleted() const { - return m_runState == CompletedSuccessfully; - } - bool TrackerBase::isOpen() const { - return m_runState != NotStarted && !isComplete(); - } - bool TrackerBase::hasChildren() const { - return !m_children.empty(); - } - - void TrackerBase::addChild( ITrackerPtr const& child ) { - m_children.push_back( child ); - } - - ITrackerPtr TrackerBase::findChild( NameAndLocation const& nameAndLocation ) { - auto it = std::find_if( m_children.begin(), m_children.end(), TrackerHasName( nameAndLocation ) ); - return( it != m_children.end() ) - ? *it - : nullptr; - } - ITracker& TrackerBase::parent() { - assert( m_parent ); // Should always be non-null except for root - return *m_parent; - } - - void TrackerBase::openChild() { - if( m_runState != ExecutingChildren ) { - m_runState = ExecutingChildren; - if( m_parent ) - m_parent->openChild(); - } - } - - bool TrackerBase::isSectionTracker() const { return false; } - bool TrackerBase::isIndexTracker() const { return false; } - - void TrackerBase::open() { - m_runState = Executing; - moveToThis(); - if( m_parent ) - m_parent->openChild(); - } - - void TrackerBase::close() { - - // Close any still open children (e.g. generators) - while( &m_ctx.currentTracker() != this ) - m_ctx.currentTracker().close(); - - switch( m_runState ) { - case NeedsAnotherRun: - break; - - case Executing: - m_runState = CompletedSuccessfully; - break; - case ExecutingChildren: - if( m_children.empty() || m_children.back()->isComplete() ) - m_runState = CompletedSuccessfully; - break; - - case NotStarted: - case CompletedSuccessfully: - case Failed: - CATCH_INTERNAL_ERROR( "Illogical state: " << m_runState ); - - default: - CATCH_INTERNAL_ERROR( "Unknown state: " << m_runState ); - } - moveToParent(); - m_ctx.completeCycle(); - } - void TrackerBase::fail() { - m_runState = Failed; - if( m_parent ) - m_parent->markAsNeedingAnotherRun(); - moveToParent(); - m_ctx.completeCycle(); - } - void TrackerBase::markAsNeedingAnotherRun() { - m_runState = NeedsAnotherRun; - } - - void TrackerBase::moveToParent() { - assert( m_parent ); - m_ctx.setCurrentTracker( m_parent ); - } - void TrackerBase::moveToThis() { - m_ctx.setCurrentTracker( this ); - } - - SectionTracker::SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) - : TrackerBase( nameAndLocation, ctx, parent ) - { - if( parent ) { - while( !parent->isSectionTracker() ) - parent = &parent->parent(); - - SectionTracker& parentSection = static_cast( *parent ); - addNextFilters( parentSection.m_filters ); - } - } - - bool SectionTracker::isSectionTracker() const { return true; } - - SectionTracker& SectionTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ) { - std::shared_ptr section; - - ITracker& currentTracker = ctx.currentTracker(); - if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { - assert( childTracker ); - assert( childTracker->isSectionTracker() ); - section = std::static_pointer_cast( childTracker ); - } - else { - section = std::make_shared( nameAndLocation, ctx, ¤tTracker ); - currentTracker.addChild( section ); - } - if( !ctx.completedCycle() ) - section->tryOpen(); - return *section; - } - - void SectionTracker::tryOpen() { - if( !isComplete() && (m_filters.empty() || m_filters[0].empty() || m_filters[0] == m_nameAndLocation.name ) ) - open(); - } - - void SectionTracker::addInitialFilters( std::vector const& filters ) { - if( !filters.empty() ) { - m_filters.push_back(""); // Root - should never be consulted - m_filters.push_back(""); // Test Case - not a section filter - m_filters.insert( m_filters.end(), filters.begin(), filters.end() ); - } - } - void SectionTracker::addNextFilters( std::vector const& filters ) { - if( filters.size() > 1 ) - m_filters.insert( m_filters.end(), ++filters.begin(), filters.end() ); - } - - IndexTracker::IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size ) - : TrackerBase( nameAndLocation, ctx, parent ), - m_size( size ) - {} - - bool IndexTracker::isIndexTracker() const { return true; } - - IndexTracker& IndexTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ) { - std::shared_ptr tracker; - - ITracker& currentTracker = ctx.currentTracker(); - if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { - assert( childTracker ); - assert( childTracker->isIndexTracker() ); - tracker = std::static_pointer_cast( childTracker ); - } - else { - tracker = std::make_shared( nameAndLocation, ctx, ¤tTracker, size ); - currentTracker.addChild( tracker ); - } - - if( !ctx.completedCycle() && !tracker->isComplete() ) { - if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun ) - tracker->moveNext(); - tracker->open(); - } - - return *tracker; - } - - int IndexTracker::index() const { return m_index; } - - void IndexTracker::moveNext() { - m_index++; - m_children.clear(); - } - - void IndexTracker::close() { - TrackerBase::close(); - if( m_runState == CompletedSuccessfully && m_index < m_size-1 ) - m_runState = Executing; - } - -} // namespace TestCaseTracking - -using TestCaseTracking::ITracker; -using TestCaseTracking::TrackerContext; -using TestCaseTracking::SectionTracker; -using TestCaseTracking::IndexTracker; - -} // namespace Catch - -#if defined(__clang__) -# pragma clang diagnostic pop -#endif -// end catch_test_case_tracker.cpp -// start catch_test_registry.cpp - -namespace Catch { - - auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker* { - return new(std::nothrow) TestInvokerAsFunction( testAsFunction ); - } - - NameAndTags::NameAndTags( StringRef const& name_ , StringRef const& tags_ ) noexcept : name( name_ ), tags( tags_ ) {} - - AutoReg::AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept { - try { - getMutableRegistryHub() - .registerTest( - makeTestCase( - invoker, - extractClassName( classOrMethod ), - nameAndTags, - lineInfo)); - } catch (...) { - // Do not throw when constructing global objects, instead register the exception to be processed later - getMutableRegistryHub().registerStartupException(); - } - } - - AutoReg::~AutoReg() = default; -} -// end catch_test_registry.cpp -// start catch_test_spec.cpp - -#include -#include -#include -#include - -namespace Catch { - - TestSpec::Pattern::~Pattern() = default; - TestSpec::NamePattern::~NamePattern() = default; - TestSpec::TagPattern::~TagPattern() = default; - TestSpec::ExcludedPattern::~ExcludedPattern() = default; - - TestSpec::NamePattern::NamePattern( std::string const& name ) - : m_wildcardPattern( toLower( name ), CaseSensitive::No ) - {} - bool TestSpec::NamePattern::matches( TestCaseInfo const& testCase ) const { - return m_wildcardPattern.matches( toLower( testCase.name ) ); - } - - TestSpec::TagPattern::TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} - bool TestSpec::TagPattern::matches( TestCaseInfo const& testCase ) const { - return std::find(begin(testCase.lcaseTags), - end(testCase.lcaseTags), - m_tag) != end(testCase.lcaseTags); - } - - TestSpec::ExcludedPattern::ExcludedPattern( PatternPtr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} - bool TestSpec::ExcludedPattern::matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } - - bool TestSpec::Filter::matches( TestCaseInfo const& testCase ) const { - // All patterns in a filter must match for the filter to be a match - for( auto const& pattern : m_patterns ) { - if( !pattern->matches( testCase ) ) - return false; - } - return true; - } - - bool TestSpec::hasFilters() const { - return !m_filters.empty(); - } - bool TestSpec::matches( TestCaseInfo const& testCase ) const { - // A TestSpec matches if any filter matches - for( auto const& filter : m_filters ) - if( filter.matches( testCase ) ) - return true; - return false; - } -} -// end catch_test_spec.cpp -// start catch_test_spec_parser.cpp - -namespace Catch { - - TestSpecParser::TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} - - TestSpecParser& TestSpecParser::parse( std::string const& arg ) { - m_mode = None; - m_exclusion = false; - m_start = std::string::npos; - m_arg = m_tagAliases->expandAliases( arg ); - m_escapeChars.clear(); - for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) - visitChar( m_arg[m_pos] ); - if( m_mode == Name ) - addPattern(); - return *this; - } - TestSpec TestSpecParser::testSpec() { - addFilter(); - return m_testSpec; - } - - void TestSpecParser::visitChar( char c ) { - if( m_mode == None ) { - switch( c ) { - case ' ': return; - case '~': m_exclusion = true; return; - case '[': return startNewMode( Tag, ++m_pos ); - case '"': return startNewMode( QuotedName, ++m_pos ); - case '\\': return escape(); - default: startNewMode( Name, m_pos ); break; - } - } - if( m_mode == Name ) { - if( c == ',' ) { - addPattern(); - addFilter(); - } - else if( c == '[' ) { - if( subString() == "exclude:" ) - m_exclusion = true; - else - addPattern(); - startNewMode( Tag, ++m_pos ); - } - else if( c == '\\' ) - escape(); - } - else if( m_mode == EscapedName ) - m_mode = Name; - else if( m_mode == QuotedName && c == '"' ) - addPattern(); - else if( m_mode == Tag && c == ']' ) - addPattern(); - } - void TestSpecParser::startNewMode( Mode mode, std::size_t start ) { - m_mode = mode; - m_start = start; - } - void TestSpecParser::escape() { - if( m_mode == None ) - m_start = m_pos; - m_mode = EscapedName; - m_escapeChars.push_back( m_pos ); - } - std::string TestSpecParser::subString() const { return m_arg.substr( m_start, m_pos - m_start ); } - - void TestSpecParser::addFilter() { - if( !m_currentFilter.m_patterns.empty() ) { - m_testSpec.m_filters.push_back( m_currentFilter ); - m_currentFilter = TestSpec::Filter(); - } - } - - TestSpec parseTestSpec( std::string const& arg ) { - return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); - } - -} // namespace Catch -// end catch_test_spec_parser.cpp -// start catch_timer.cpp - -#include - -static const uint64_t nanosecondsInSecond = 1000000000; - -namespace Catch { - - auto getCurrentNanosecondsSinceEpoch() -> uint64_t { - return std::chrono::duration_cast( std::chrono::high_resolution_clock::now().time_since_epoch() ).count(); - } - - auto estimateClockResolution() -> uint64_t { - uint64_t sum = 0; - static const uint64_t iterations = 1000000; - - auto startTime = getCurrentNanosecondsSinceEpoch(); - - for( std::size_t i = 0; i < iterations; ++i ) { - - uint64_t ticks; - uint64_t baseTicks = getCurrentNanosecondsSinceEpoch(); - do { - ticks = getCurrentNanosecondsSinceEpoch(); - } while( ticks == baseTicks ); - - auto delta = ticks - baseTicks; - sum += delta; - - // If we have been calibrating for over 3 seconds -- the clock - // is terrible and we should move on. - // TBD: How to signal that the measured resolution is probably wrong? - if (ticks > startTime + 3 * nanosecondsInSecond) { - return sum / i; - } - } - - // We're just taking the mean, here. To do better we could take the std. dev and exclude outliers - // - and potentially do more iterations if there's a high variance. - return sum/iterations; - } - auto getEstimatedClockResolution() -> uint64_t { - static auto s_resolution = estimateClockResolution(); - return s_resolution; - } - - void Timer::start() { - m_nanoseconds = getCurrentNanosecondsSinceEpoch(); - } - auto Timer::getElapsedNanoseconds() const -> uint64_t { - return getCurrentNanosecondsSinceEpoch() - m_nanoseconds; - } - auto Timer::getElapsedMicroseconds() const -> uint64_t { - return getElapsedNanoseconds()/1000; - } - auto Timer::getElapsedMilliseconds() const -> unsigned int { - return static_cast(getElapsedMicroseconds()/1000); - } - auto Timer::getElapsedSeconds() const -> double { - return getElapsedMicroseconds()/1000000.0; - } - -} // namespace Catch -// end catch_timer.cpp -// start catch_tostring.cpp - -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wexit-time-destructors" -# pragma clang diagnostic ignored "-Wglobal-constructors" -#endif - -// Enable specific decls locally -#if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) -#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER -#endif - -#include -#include - -namespace Catch { - -namespace Detail { - - const std::string unprintableString = "{?}"; - - namespace { - const int hexThreshold = 255; - - struct Endianness { - enum Arch { Big, Little }; - - static Arch which() { - union _{ - int asInt; - char asChar[sizeof (int)]; - } u; - - u.asInt = 1; - return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little; - } - }; - } - - std::string rawMemoryToString( const void *object, std::size_t size ) { - // Reverse order for little endian architectures - int i = 0, end = static_cast( size ), inc = 1; - if( Endianness::which() == Endianness::Little ) { - i = end-1; - end = inc = -1; - } - - unsigned char const *bytes = static_cast(object); - ReusableStringStream rss; - rss << "0x" << std::setfill('0') << std::hex; - for( ; i != end; i += inc ) - rss << std::setw(2) << static_cast(bytes[i]); - return rss.str(); - } -} - -template -std::string fpToString( T value, int precision ) { - if (std::isnan(value)) { - return "nan"; - } - - ReusableStringStream rss; - rss << std::setprecision( precision ) - << std::fixed - << value; - std::string d = rss.str(); - std::size_t i = d.find_last_not_of( '0' ); - if( i != std::string::npos && i != d.size()-1 ) { - if( d[i] == '.' ) - i++; - d = d.substr( 0, i+1 ); - } - return d; -} - -//// ======================================================= //// -// -// Out-of-line defs for full specialization of StringMaker -// -//// ======================================================= //// - -std::string StringMaker::convert(const std::string& str) { - if (!getCurrentContext().getConfig()->showInvisibles()) { - return '"' + str + '"'; - } - - std::string s("\""); - for (char c : str) { - switch (c) { - case '\n': - s.append("\\n"); - break; - case '\t': - s.append("\\t"); - break; - default: - s.push_back(c); - break; - } - } - s.append("\""); - return s; -} - -#ifdef CATCH_CONFIG_WCHAR -std::string StringMaker::convert(const std::wstring& wstr) { - std::string s; - s.reserve(wstr.size()); - for (auto c : wstr) { - s += (c <= 0xff) ? static_cast(c) : '?'; - } - return ::Catch::Detail::stringify(s); -} -#endif - -std::string StringMaker::convert(char const* str) { - if (str) { - return ::Catch::Detail::stringify(std::string{ str }); - } else { - return{ "{null string}" }; - } -} -std::string StringMaker::convert(char* str) { - if (str) { - return ::Catch::Detail::stringify(std::string{ str }); - } else { - return{ "{null string}" }; - } -} -#ifdef CATCH_CONFIG_WCHAR -std::string StringMaker::convert(wchar_t const * str) { - if (str) { - return ::Catch::Detail::stringify(std::wstring{ str }); - } else { - return{ "{null string}" }; - } -} -std::string StringMaker::convert(wchar_t * str) { - if (str) { - return ::Catch::Detail::stringify(std::wstring{ str }); - } else { - return{ "{null string}" }; - } -} -#endif - -std::string StringMaker::convert(int value) { - return ::Catch::Detail::stringify(static_cast(value)); -} -std::string StringMaker::convert(long value) { - return ::Catch::Detail::stringify(static_cast(value)); -} -std::string StringMaker::convert(long long value) { - ReusableStringStream rss; - rss << value; - if (value > Detail::hexThreshold) { - rss << " (0x" << std::hex << value << ')'; - } - return rss.str(); -} - -std::string StringMaker::convert(unsigned int value) { - return ::Catch::Detail::stringify(static_cast(value)); -} -std::string StringMaker::convert(unsigned long value) { - return ::Catch::Detail::stringify(static_cast(value)); -} -std::string StringMaker::convert(unsigned long long value) { - ReusableStringStream rss; - rss << value; - if (value > Detail::hexThreshold) { - rss << " (0x" << std::hex << value << ')'; - } - return rss.str(); -} - -std::string StringMaker::convert(bool b) { - return b ? "true" : "false"; -} - -std::string StringMaker::convert(char value) { - if (value == '\r') { - return "'\\r'"; - } else if (value == '\f') { - return "'\\f'"; - } else if (value == '\n') { - return "'\\n'"; - } else if (value == '\t') { - return "'\\t'"; - } else if ('\0' <= value && value < ' ') { - return ::Catch::Detail::stringify(static_cast(value)); - } else { - char chstr[] = "' '"; - chstr[1] = value; - return chstr; - } -} -std::string StringMaker::convert(signed char c) { - return ::Catch::Detail::stringify(static_cast(c)); -} -std::string StringMaker::convert(unsigned char c) { - return ::Catch::Detail::stringify(static_cast(c)); -} - -std::string StringMaker::convert(std::nullptr_t) { - return "nullptr"; -} - -std::string StringMaker::convert(float value) { - return fpToString(value, 5) + 'f'; -} -std::string StringMaker::convert(double value) { - return fpToString(value, 10); -} - -std::string ratio_string::symbol() { return "a"; } -std::string ratio_string::symbol() { return "f"; } -std::string ratio_string::symbol() { return "p"; } -std::string ratio_string::symbol() { return "n"; } -std::string ratio_string::symbol() { return "u"; } -std::string ratio_string::symbol() { return "m"; } - -} // end namespace Catch - -#if defined(__clang__) -# pragma clang diagnostic pop -#endif - -// end catch_tostring.cpp -// start catch_totals.cpp - -namespace Catch { - - Counts Counts::operator - ( Counts const& other ) const { - Counts diff; - diff.passed = passed - other.passed; - diff.failed = failed - other.failed; - diff.failedButOk = failedButOk - other.failedButOk; - return diff; - } - - Counts& Counts::operator += ( Counts const& other ) { - passed += other.passed; - failed += other.failed; - failedButOk += other.failedButOk; - return *this; - } - - std::size_t Counts::total() const { - return passed + failed + failedButOk; - } - bool Counts::allPassed() const { - return failed == 0 && failedButOk == 0; - } - bool Counts::allOk() const { - return failed == 0; - } - - Totals Totals::operator - ( Totals const& other ) const { - Totals diff; - diff.assertions = assertions - other.assertions; - diff.testCases = testCases - other.testCases; - return diff; - } - - Totals& Totals::operator += ( Totals const& other ) { - assertions += other.assertions; - testCases += other.testCases; - return *this; - } - - Totals Totals::delta( Totals const& prevTotals ) const { - Totals diff = *this - prevTotals; - if( diff.assertions.failed > 0 ) - ++diff.testCases.failed; - else if( diff.assertions.failedButOk > 0 ) - ++diff.testCases.failedButOk; - else - ++diff.testCases.passed; - return diff; - } - -} -// end catch_totals.cpp -// start catch_uncaught_exceptions.cpp - -#include - -namespace Catch { - bool uncaught_exceptions() { -#if defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) - return std::uncaught_exceptions() > 0; -#else - return std::uncaught_exception(); -#endif - } -} // end namespace Catch -// end catch_uncaught_exceptions.cpp -// start catch_version.cpp - -#include - -namespace Catch { - - Version::Version - ( unsigned int _majorVersion, - unsigned int _minorVersion, - unsigned int _patchNumber, - char const * const _branchName, - unsigned int _buildNumber ) - : majorVersion( _majorVersion ), - minorVersion( _minorVersion ), - patchNumber( _patchNumber ), - branchName( _branchName ), - buildNumber( _buildNumber ) - {} - - std::ostream& operator << ( std::ostream& os, Version const& version ) { - os << version.majorVersion << '.' - << version.minorVersion << '.' - << version.patchNumber; - // branchName is never null -> 0th char is \0 if it is empty - if (version.branchName[0]) { - os << '-' << version.branchName - << '.' << version.buildNumber; - } - return os; - } - - Version const& libraryVersion() { - static Version version( 2, 2, 3, "", 0 ); - return version; - } - -} -// end catch_version.cpp -// start catch_wildcard_pattern.cpp - -#include - -namespace Catch { - - WildcardPattern::WildcardPattern( std::string const& pattern, - CaseSensitive::Choice caseSensitivity ) - : m_caseSensitivity( caseSensitivity ), - m_pattern( adjustCase( pattern ) ) - { - if( startsWith( m_pattern, '*' ) ) { - m_pattern = m_pattern.substr( 1 ); - m_wildcard = WildcardAtStart; - } - if( endsWith( m_pattern, '*' ) ) { - m_pattern = m_pattern.substr( 0, m_pattern.size()-1 ); - m_wildcard = static_cast( m_wildcard | WildcardAtEnd ); - } - } - - bool WildcardPattern::matches( std::string const& str ) const { - switch( m_wildcard ) { - case NoWildcard: - return m_pattern == adjustCase( str ); - case WildcardAtStart: - return endsWith( adjustCase( str ), m_pattern ); - case WildcardAtEnd: - return startsWith( adjustCase( str ), m_pattern ); - case WildcardAtBothEnds: - return contains( adjustCase( str ), m_pattern ); - default: - CATCH_INTERNAL_ERROR( "Unknown enum" ); - } - } - - std::string WildcardPattern::adjustCase( std::string const& str ) const { - return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str; - } -} -// end catch_wildcard_pattern.cpp -// start catch_xmlwriter.cpp - -#include - -using uchar = unsigned char; - -namespace Catch { - -namespace { - - size_t trailingBytes(unsigned char c) { - if ((c & 0xE0) == 0xC0) { - return 2; - } - if ((c & 0xF0) == 0xE0) { - return 3; - } - if ((c & 0xF8) == 0xF0) { - return 4; - } - CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); - } - - uint32_t headerValue(unsigned char c) { - if ((c & 0xE0) == 0xC0) { - return c & 0x1F; - } - if ((c & 0xF0) == 0xE0) { - return c & 0x0F; - } - if ((c & 0xF8) == 0xF0) { - return c & 0x07; - } - CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); - } - - void hexEscapeChar(std::ostream& os, unsigned char c) { - os << "\\x" - << std::uppercase << std::hex << std::setfill('0') << std::setw(2) - << static_cast(c); - } - -} // anonymous namespace - - XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat ) - : m_str( str ), - m_forWhat( forWhat ) - {} - - void XmlEncode::encodeTo( std::ostream& os ) const { - // Apostrophe escaping not necessary if we always use " to write attributes - // (see: http://www.w3.org/TR/xml/#syntax) - - for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) { - uchar c = m_str[idx]; - switch (c) { - case '<': os << "<"; break; - case '&': os << "&"; break; - - case '>': - // See: http://www.w3.org/TR/xml/#syntax - if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') - os << ">"; - else - os << c; - break; - - case '\"': - if (m_forWhat == ForAttributes) - os << """; - else - os << c; - break; - - default: - // Check for control characters and invalid utf-8 - - // Escape control characters in standard ascii - // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 - if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) { - hexEscapeChar(os, c); - break; - } - - // Plain ASCII: Write it to stream - if (c < 0x7F) { - os << c; - break; - } - - // UTF-8 territory - // Check if the encoding is valid and if it is not, hex escape bytes. - // Important: We do not check the exact decoded values for validity, only the encoding format - // First check that this bytes is a valid lead byte: - // This means that it is not encoded as 1111 1XXX - // Or as 10XX XXXX - if (c < 0xC0 || - c >= 0xF8) { - hexEscapeChar(os, c); - break; - } - - auto encBytes = trailingBytes(c); - // Are there enough bytes left to avoid accessing out-of-bounds memory? - if (idx + encBytes - 1 >= m_str.size()) { - hexEscapeChar(os, c); - break; - } - // The header is valid, check data - // The next encBytes bytes must together be a valid utf-8 - // This means: bitpattern 10XX XXXX and the extracted value is sane (ish) - bool valid = true; - uint32_t value = headerValue(c); - for (std::size_t n = 1; n < encBytes; ++n) { - uchar nc = m_str[idx + n]; - valid &= ((nc & 0xC0) == 0x80); - value = (value << 6) | (nc & 0x3F); - } - - if ( - // Wrong bit pattern of following bytes - (!valid) || - // Overlong encodings - (value < 0x80) || - (0x80 <= value && value < 0x800 && encBytes > 2) || - (0x800 < value && value < 0x10000 && encBytes > 3) || - // Encoded value out of range - (value >= 0x110000) - ) { - hexEscapeChar(os, c); - break; - } - - // If we got here, this is in fact a valid(ish) utf-8 sequence - for (std::size_t n = 0; n < encBytes; ++n) { - os << m_str[idx + n]; - } - idx += encBytes - 1; - break; - } - } - } - - std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { - xmlEncode.encodeTo( os ); - return os; - } - - XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer ) - : m_writer( writer ) - {} - - XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) noexcept - : m_writer( other.m_writer ){ - other.m_writer = nullptr; - } - XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) noexcept { - if ( m_writer ) { - m_writer->endElement(); - } - m_writer = other.m_writer; - other.m_writer = nullptr; - return *this; - } - - XmlWriter::ScopedElement::~ScopedElement() { - if( m_writer ) - m_writer->endElement(); - } - - XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) { - m_writer->writeText( text, indent ); - return *this; - } - - XmlWriter::XmlWriter( std::ostream& os ) : m_os( os ) - { - writeDeclaration(); - } - - XmlWriter::~XmlWriter() { - while( !m_tags.empty() ) - endElement(); - } - - XmlWriter& XmlWriter::startElement( std::string const& name ) { - ensureTagClosed(); - newlineIfNecessary(); - m_os << m_indent << '<' << name; - m_tags.push_back( name ); - m_indent += " "; - m_tagIsOpen = true; - return *this; - } - - XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) { - ScopedElement scoped( this ); - startElement( name ); - return scoped; - } - - XmlWriter& XmlWriter::endElement() { - newlineIfNecessary(); - m_indent = m_indent.substr( 0, m_indent.size()-2 ); - if( m_tagIsOpen ) { - m_os << "/>"; - m_tagIsOpen = false; - } - else { - m_os << m_indent << ""; - } - m_os << std::endl; - m_tags.pop_back(); - return *this; - } - - XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) { - if( !name.empty() && !attribute.empty() ) - m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; - return *this; - } - - XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) { - m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"'; - return *this; - } - - XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) { - if( !text.empty() ){ - bool tagWasOpen = m_tagIsOpen; - ensureTagClosed(); - if( tagWasOpen && indent ) - m_os << m_indent; - m_os << XmlEncode( text ); - m_needsNewline = true; - } - return *this; - } - - XmlWriter& XmlWriter::writeComment( std::string const& text ) { - ensureTagClosed(); - m_os << m_indent << ""; - m_needsNewline = true; - return *this; - } - - void XmlWriter::writeStylesheetRef( std::string const& url ) { - m_os << "\n"; - } - - XmlWriter& XmlWriter::writeBlankLine() { - ensureTagClosed(); - m_os << '\n'; - return *this; - } - - void XmlWriter::ensureTagClosed() { - if( m_tagIsOpen ) { - m_os << ">" << std::endl; - m_tagIsOpen = false; - } - } - - void XmlWriter::writeDeclaration() { - m_os << "\n"; - } - - void XmlWriter::newlineIfNecessary() { - if( m_needsNewline ) { - m_os << std::endl; - m_needsNewline = false; - } - } -} -// end catch_xmlwriter.cpp -// start catch_reporter_bases.cpp - -#include -#include -#include -#include -#include - -namespace Catch { - void prepareExpandedExpression(AssertionResult& result) { - result.getExpandedExpression(); - } - - // Because formatting using c++ streams is stateful, drop down to C is required - // Alternatively we could use stringstream, but its performance is... not good. - std::string getFormattedDuration( double duration ) { - // Max exponent + 1 is required to represent the whole part - // + 1 for decimal point - // + 3 for the 3 decimal places - // + 1 for null terminator - const std::size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1; - char buffer[maxDoubleSize]; - - // Save previous errno, to prevent sprintf from overwriting it - ErrnoGuard guard; -#ifdef _MSC_VER - sprintf_s(buffer, "%.3f", duration); -#else - sprintf(buffer, "%.3f", duration); -#endif - return std::string(buffer); - } - - TestEventListenerBase::TestEventListenerBase(ReporterConfig const & _config) - :StreamingReporterBase(_config) {} - - void TestEventListenerBase::assertionStarting(AssertionInfo const &) {} - - bool TestEventListenerBase::assertionEnded(AssertionStats const &) { - return false; - } - -} // end namespace Catch -// end catch_reporter_bases.cpp -// start catch_reporter_compact.cpp - -namespace { - -#ifdef CATCH_PLATFORM_MAC - const char* failedString() { return "FAILED"; } - const char* passedString() { return "PASSED"; } -#else - const char* failedString() { return "failed"; } - const char* passedString() { return "passed"; } -#endif - - // Colour::LightGrey - Catch::Colour::Code dimColour() { return Catch::Colour::FileName; } - - std::string bothOrAll( std::size_t count ) { - return count == 1 ? std::string() : - count == 2 ? "both " : "all " ; - } - -} // anon namespace - -namespace Catch { -namespace { -// Colour, message variants: -// - white: No tests ran. -// - red: Failed [both/all] N test cases, failed [both/all] M assertions. -// - white: Passed [both/all] N test cases (no assertions). -// - red: Failed N tests cases, failed M assertions. -// - green: Passed [both/all] N tests cases with M assertions. -void printTotals(std::ostream& out, const Totals& totals) { - if (totals.testCases.total() == 0) { - out << "No tests ran."; - } else if (totals.testCases.failed == totals.testCases.total()) { - Colour colour(Colour::ResultError); - const std::string qualify_assertions_failed = - totals.assertions.failed == totals.assertions.total() ? - bothOrAll(totals.assertions.failed) : std::string(); - out << - "Failed " << bothOrAll(totals.testCases.failed) - << pluralise(totals.testCases.failed, "test case") << ", " - "failed " << qualify_assertions_failed << - pluralise(totals.assertions.failed, "assertion") << '.'; - } else if (totals.assertions.total() == 0) { - out << - "Passed " << bothOrAll(totals.testCases.total()) - << pluralise(totals.testCases.total(), "test case") - << " (no assertions)."; - } else if (totals.assertions.failed) { - Colour colour(Colour::ResultError); - out << - "Failed " << pluralise(totals.testCases.failed, "test case") << ", " - "failed " << pluralise(totals.assertions.failed, "assertion") << '.'; - } else { - Colour colour(Colour::ResultSuccess); - out << - "Passed " << bothOrAll(totals.testCases.passed) - << pluralise(totals.testCases.passed, "test case") << - " with " << pluralise(totals.assertions.passed, "assertion") << '.'; - } -} - -// Implementation of CompactReporter formatting -class AssertionPrinter { -public: - AssertionPrinter& operator= (AssertionPrinter const&) = delete; - AssertionPrinter(AssertionPrinter const&) = delete; - AssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages) - : stream(_stream) - , result(_stats.assertionResult) - , messages(_stats.infoMessages) - , itMessage(_stats.infoMessages.begin()) - , printInfoMessages(_printInfoMessages) {} - - void print() { - printSourceInfo(); - - itMessage = messages.begin(); - - switch (result.getResultType()) { - case ResultWas::Ok: - printResultType(Colour::ResultSuccess, passedString()); - printOriginalExpression(); - printReconstructedExpression(); - if (!result.hasExpression()) - printRemainingMessages(Colour::None); - else - printRemainingMessages(); - break; - case ResultWas::ExpressionFailed: - if (result.isOk()) - printResultType(Colour::ResultSuccess, failedString() + std::string(" - but was ok")); - else - printResultType(Colour::Error, failedString()); - printOriginalExpression(); - printReconstructedExpression(); - printRemainingMessages(); - break; - case ResultWas::ThrewException: - printResultType(Colour::Error, failedString()); - printIssue("unexpected exception with message:"); - printMessage(); - printExpressionWas(); - printRemainingMessages(); - break; - case ResultWas::FatalErrorCondition: - printResultType(Colour::Error, failedString()); - printIssue("fatal error condition with message:"); - printMessage(); - printExpressionWas(); - printRemainingMessages(); - break; - case ResultWas::DidntThrowException: - printResultType(Colour::Error, failedString()); - printIssue("expected exception, got none"); - printExpressionWas(); - printRemainingMessages(); - break; - case ResultWas::Info: - printResultType(Colour::None, "info"); - printMessage(); - printRemainingMessages(); - break; - case ResultWas::Warning: - printResultType(Colour::None, "warning"); - printMessage(); - printRemainingMessages(); - break; - case ResultWas::ExplicitFailure: - printResultType(Colour::Error, failedString()); - printIssue("explicitly"); - printRemainingMessages(Colour::None); - break; - // These cases are here to prevent compiler warnings - case ResultWas::Unknown: - case ResultWas::FailureBit: - case ResultWas::Exception: - printResultType(Colour::Error, "** internal error **"); - break; - } - } - -private: - void printSourceInfo() const { - Colour colourGuard(Colour::FileName); - stream << result.getSourceInfo() << ':'; - } - - void printResultType(Colour::Code colour, std::string const& passOrFail) const { - if (!passOrFail.empty()) { - { - Colour colourGuard(colour); - stream << ' ' << passOrFail; - } - stream << ':'; - } - } - - void printIssue(std::string const& issue) const { - stream << ' ' << issue; - } - - void printExpressionWas() { - if (result.hasExpression()) { - stream << ';'; - { - Colour colour(dimColour()); - stream << " expression was:"; - } - printOriginalExpression(); - } - } - - void printOriginalExpression() const { - if (result.hasExpression()) { - stream << ' ' << result.getExpression(); - } - } - - void printReconstructedExpression() const { - if (result.hasExpandedExpression()) { - { - Colour colour(dimColour()); - stream << " for: "; - } - stream << result.getExpandedExpression(); - } - } - - void printMessage() { - if (itMessage != messages.end()) { - stream << " '" << itMessage->message << '\''; - ++itMessage; - } - } - - void printRemainingMessages(Colour::Code colour = dimColour()) { - if (itMessage == messages.end()) - return; - - // using messages.end() directly yields (or auto) compilation error: - std::vector::const_iterator itEnd = messages.end(); - const std::size_t N = static_cast(std::distance(itMessage, itEnd)); - - { - Colour colourGuard(colour); - stream << " with " << pluralise(N, "message") << ':'; - } - - for (; itMessage != itEnd; ) { - // If this assertion is a warning ignore any INFO messages - if (printInfoMessages || itMessage->type != ResultWas::Info) { - stream << " '" << itMessage->message << '\''; - if (++itMessage != itEnd) { - Colour colourGuard(dimColour()); - stream << " and"; - } - } - } - } - -private: - std::ostream& stream; - AssertionResult const& result; - std::vector messages; - std::vector::const_iterator itMessage; - bool printInfoMessages; -}; - -} // anon namespace - - std::string CompactReporter::getDescription() { - return "Reports test results on a single line, suitable for IDEs"; - } - - ReporterPreferences CompactReporter::getPreferences() const { - ReporterPreferences prefs; - prefs.shouldRedirectStdOut = false; - return prefs; - } - - void CompactReporter::noMatchingTestCases( std::string const& spec ) { - stream << "No test cases matched '" << spec << '\'' << std::endl; - } - - void CompactReporter::assertionStarting( AssertionInfo const& ) {} - - bool CompactReporter::assertionEnded( AssertionStats const& _assertionStats ) { - AssertionResult const& result = _assertionStats.assertionResult; - - bool printInfoMessages = true; - - // Drop out if result was successful and we're not printing those - if( !m_config->includeSuccessfulResults() && result.isOk() ) { - if( result.getResultType() != ResultWas::Warning ) - return false; - printInfoMessages = false; - } - - AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); - printer.print(); - - stream << std::endl; - return true; - } - - void CompactReporter::sectionEnded(SectionStats const& _sectionStats) { - if (m_config->showDurations() == ShowDurations::Always) { - stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; - } - } - - void CompactReporter::testRunEnded( TestRunStats const& _testRunStats ) { - printTotals( stream, _testRunStats.totals ); - stream << '\n' << std::endl; - StreamingReporterBase::testRunEnded( _testRunStats ); - } - - CompactReporter::~CompactReporter() {} - - CATCH_REGISTER_REPORTER( "compact", CompactReporter ) - -} // end namespace Catch -// end catch_reporter_compact.cpp -// start catch_reporter_console.cpp - -#include -#include - -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch - // Note that 4062 (not all labels are handled - // and default is missing) is enabled -#endif - -namespace Catch { - -namespace { - -// Formatter impl for ConsoleReporter -class ConsoleAssertionPrinter { -public: - ConsoleAssertionPrinter& operator= (ConsoleAssertionPrinter const&) = delete; - ConsoleAssertionPrinter(ConsoleAssertionPrinter const&) = delete; - ConsoleAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages) - : stream(_stream), - stats(_stats), - result(_stats.assertionResult), - colour(Colour::None), - message(result.getMessage()), - messages(_stats.infoMessages), - printInfoMessages(_printInfoMessages) { - switch (result.getResultType()) { - case ResultWas::Ok: - colour = Colour::Success; - passOrFail = "PASSED"; - //if( result.hasMessage() ) - if (_stats.infoMessages.size() == 1) - messageLabel = "with message"; - if (_stats.infoMessages.size() > 1) - messageLabel = "with messages"; - break; - case ResultWas::ExpressionFailed: - if (result.isOk()) { - colour = Colour::Success; - passOrFail = "FAILED - but was ok"; - } else { - colour = Colour::Error; - passOrFail = "FAILED"; - } - if (_stats.infoMessages.size() == 1) - messageLabel = "with message"; - if (_stats.infoMessages.size() > 1) - messageLabel = "with messages"; - break; - case ResultWas::ThrewException: - colour = Colour::Error; - passOrFail = "FAILED"; - messageLabel = "due to unexpected exception with "; - if (_stats.infoMessages.size() == 1) - messageLabel += "message"; - if (_stats.infoMessages.size() > 1) - messageLabel += "messages"; - break; - case ResultWas::FatalErrorCondition: - colour = Colour::Error; - passOrFail = "FAILED"; - messageLabel = "due to a fatal error condition"; - break; - case ResultWas::DidntThrowException: - colour = Colour::Error; - passOrFail = "FAILED"; - messageLabel = "because no exception was thrown where one was expected"; - break; - case ResultWas::Info: - messageLabel = "info"; - break; - case ResultWas::Warning: - messageLabel = "warning"; - break; - case ResultWas::ExplicitFailure: - passOrFail = "FAILED"; - colour = Colour::Error; - if (_stats.infoMessages.size() == 1) - messageLabel = "explicitly with message"; - if (_stats.infoMessages.size() > 1) - messageLabel = "explicitly with messages"; - break; - // These cases are here to prevent compiler warnings - case ResultWas::Unknown: - case ResultWas::FailureBit: - case ResultWas::Exception: - passOrFail = "** internal error **"; - colour = Colour::Error; - break; - } - } - - void print() const { - printSourceInfo(); - if (stats.totals.assertions.total() > 0) { - if (result.isOk()) - stream << '\n'; - printResultType(); - printOriginalExpression(); - printReconstructedExpression(); - } else { - stream << '\n'; - } - printMessage(); - } - -private: - void printResultType() const { - if (!passOrFail.empty()) { - Colour colourGuard(colour); - stream << passOrFail << ":\n"; - } - } - void printOriginalExpression() const { - if (result.hasExpression()) { - Colour colourGuard(Colour::OriginalExpression); - stream << " "; - stream << result.getExpressionInMacro(); - stream << '\n'; - } - } - void printReconstructedExpression() const { - if (result.hasExpandedExpression()) { - stream << "with expansion:\n"; - Colour colourGuard(Colour::ReconstructedExpression); - stream << Column(result.getExpandedExpression()).indent(2) << '\n'; - } - } - void printMessage() const { - if (!messageLabel.empty()) - stream << messageLabel << ':' << '\n'; - for (auto const& msg : messages) { - // If this assertion is a warning ignore any INFO messages - if (printInfoMessages || msg.type != ResultWas::Info) - stream << Column(msg.message).indent(2) << '\n'; - } - } - void printSourceInfo() const { - Colour colourGuard(Colour::FileName); - stream << result.getSourceInfo() << ": "; - } - - std::ostream& stream; - AssertionStats const& stats; - AssertionResult const& result; - Colour::Code colour; - std::string passOrFail; - std::string messageLabel; - std::string message; - std::vector messages; - bool printInfoMessages; -}; - -std::size_t makeRatio(std::size_t number, std::size_t total) { - std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number / total : 0; - return (ratio == 0 && number > 0) ? 1 : ratio; -} - -std::size_t& findMax(std::size_t& i, std::size_t& j, std::size_t& k) { - if (i > j && i > k) - return i; - else if (j > k) - return j; - else - return k; -} - -struct ColumnInfo { - enum Justification { Left, Right }; - std::string name; - int width; - Justification justification; -}; -struct ColumnBreak {}; -struct RowBreak {}; - -class Duration { - enum class Unit { - Auto, - Nanoseconds, - Microseconds, - Milliseconds, - Seconds, - Minutes - }; - static const uint64_t s_nanosecondsInAMicrosecond = 1000; - static const uint64_t s_nanosecondsInAMillisecond = 1000 * s_nanosecondsInAMicrosecond; - static const uint64_t s_nanosecondsInASecond = 1000 * s_nanosecondsInAMillisecond; - static const uint64_t s_nanosecondsInAMinute = 60 * s_nanosecondsInASecond; - - uint64_t m_inNanoseconds; - Unit m_units; - -public: - explicit Duration(uint64_t inNanoseconds, Unit units = Unit::Auto) - : m_inNanoseconds(inNanoseconds), - m_units(units) { - if (m_units == Unit::Auto) { - if (m_inNanoseconds < s_nanosecondsInAMicrosecond) - m_units = Unit::Nanoseconds; - else if (m_inNanoseconds < s_nanosecondsInAMillisecond) - m_units = Unit::Microseconds; - else if (m_inNanoseconds < s_nanosecondsInASecond) - m_units = Unit::Milliseconds; - else if (m_inNanoseconds < s_nanosecondsInAMinute) - m_units = Unit::Seconds; - else - m_units = Unit::Minutes; - } - - } - - auto value() const -> double { - switch (m_units) { - case Unit::Microseconds: - return m_inNanoseconds / static_cast(s_nanosecondsInAMicrosecond); - case Unit::Milliseconds: - return m_inNanoseconds / static_cast(s_nanosecondsInAMillisecond); - case Unit::Seconds: - return m_inNanoseconds / static_cast(s_nanosecondsInASecond); - case Unit::Minutes: - return m_inNanoseconds / static_cast(s_nanosecondsInAMinute); - default: - return static_cast(m_inNanoseconds); - } - } - auto unitsAsString() const -> std::string { - switch (m_units) { - case Unit::Nanoseconds: - return "ns"; - case Unit::Microseconds: - return "µs"; - case Unit::Milliseconds: - return "ms"; - case Unit::Seconds: - return "s"; - case Unit::Minutes: - return "m"; - default: - return "** internal error **"; - } - - } - friend auto operator << (std::ostream& os, Duration const& duration) -> std::ostream& { - return os << duration.value() << " " << duration.unitsAsString(); - } -}; -} // end anon namespace - -class TablePrinter { - std::ostream& m_os; - std::vector m_columnInfos; - std::ostringstream m_oss; - int m_currentColumn = -1; - bool m_isOpen = false; - -public: - TablePrinter( std::ostream& os, std::vector columnInfos ) - : m_os( os ), - m_columnInfos( std::move( columnInfos ) ) {} - - auto columnInfos() const -> std::vector const& { - return m_columnInfos; - } - - void open() { - if (!m_isOpen) { - m_isOpen = true; - *this << RowBreak(); - for (auto const& info : m_columnInfos) - *this << info.name << ColumnBreak(); - *this << RowBreak(); - m_os << Catch::getLineOfChars<'-'>() << "\n"; - } - } - void close() { - if (m_isOpen) { - *this << RowBreak(); - m_os << std::endl; - m_isOpen = false; - } - } - - template - friend TablePrinter& operator << (TablePrinter& tp, T const& value) { - tp.m_oss << value; - return tp; - } - - friend TablePrinter& operator << (TablePrinter& tp, ColumnBreak) { - auto colStr = tp.m_oss.str(); - // This takes account of utf8 encodings - auto strSize = Catch::StringRef(colStr).numberOfCharacters(); - tp.m_oss.str(""); - tp.open(); - if (tp.m_currentColumn == static_cast(tp.m_columnInfos.size() - 1)) { - tp.m_currentColumn = -1; - tp.m_os << "\n"; - } - tp.m_currentColumn++; - - auto colInfo = tp.m_columnInfos[tp.m_currentColumn]; - auto padding = (strSize + 2 < static_cast(colInfo.width)) - ? std::string(colInfo.width - (strSize + 2), ' ') - : std::string(); - if (colInfo.justification == ColumnInfo::Left) - tp.m_os << colStr << padding << " "; - else - tp.m_os << padding << colStr << " "; - return tp; - } - - friend TablePrinter& operator << (TablePrinter& tp, RowBreak) { - if (tp.m_currentColumn > 0) { - tp.m_os << "\n"; - tp.m_currentColumn = -1; - } - return tp; - } -}; - -ConsoleReporter::ConsoleReporter(ReporterConfig const& config) - : StreamingReporterBase(config), - m_tablePrinter(new TablePrinter(config.stream(), - { - { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 32, ColumnInfo::Left }, - { "iters", 8, ColumnInfo::Right }, - { "elapsed ns", 14, ColumnInfo::Right }, - { "average", 14, ColumnInfo::Right } - })) {} -ConsoleReporter::~ConsoleReporter() = default; - -std::string ConsoleReporter::getDescription() { - return "Reports test results as plain lines of text"; -} - -void ConsoleReporter::noMatchingTestCases(std::string const& spec) { - stream << "No test cases matched '" << spec << '\'' << std::endl; -} - -void ConsoleReporter::assertionStarting(AssertionInfo const&) {} - -bool ConsoleReporter::assertionEnded(AssertionStats const& _assertionStats) { - AssertionResult const& result = _assertionStats.assertionResult; - - bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); - - // Drop out if result was successful but we're not printing them. - if (!includeResults && result.getResultType() != ResultWas::Warning) - return false; - - lazyPrint(); - - ConsoleAssertionPrinter printer(stream, _assertionStats, includeResults); - printer.print(); - stream << std::endl; - return true; -} - -void ConsoleReporter::sectionStarting(SectionInfo const& _sectionInfo) { - m_headerPrinted = false; - StreamingReporterBase::sectionStarting(_sectionInfo); -} -void ConsoleReporter::sectionEnded(SectionStats const& _sectionStats) { - m_tablePrinter->close(); - if (_sectionStats.missingAssertions) { - lazyPrint(); - Colour colour(Colour::ResultError); - if (m_sectionStack.size() > 1) - stream << "\nNo assertions in section"; - else - stream << "\nNo assertions in test case"; - stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; - } - if (m_config->showDurations() == ShowDurations::Always) { - stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; - } - if (m_headerPrinted) { - m_headerPrinted = false; - } - StreamingReporterBase::sectionEnded(_sectionStats); -} - -void ConsoleReporter::benchmarkStarting(BenchmarkInfo const& info) { - lazyPrintWithoutClosingBenchmarkTable(); - - auto nameCol = Column( info.name ).width( static_cast( m_tablePrinter->columnInfos()[0].width - 2 ) ); - - bool firstLine = true; - for (auto line : nameCol) { - if (!firstLine) - (*m_tablePrinter) << ColumnBreak() << ColumnBreak() << ColumnBreak(); - else - firstLine = false; - - (*m_tablePrinter) << line << ColumnBreak(); - } -} -void ConsoleReporter::benchmarkEnded(BenchmarkStats const& stats) { - Duration average(stats.elapsedTimeInNanoseconds / stats.iterations); - (*m_tablePrinter) - << stats.iterations << ColumnBreak() - << stats.elapsedTimeInNanoseconds << ColumnBreak() - << average << ColumnBreak(); -} - -void ConsoleReporter::testCaseEnded(TestCaseStats const& _testCaseStats) { - m_tablePrinter->close(); - StreamingReporterBase::testCaseEnded(_testCaseStats); - m_headerPrinted = false; -} -void ConsoleReporter::testGroupEnded(TestGroupStats const& _testGroupStats) { - if (currentGroupInfo.used) { - printSummaryDivider(); - stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; - printTotals(_testGroupStats.totals); - stream << '\n' << std::endl; - } - StreamingReporterBase::testGroupEnded(_testGroupStats); -} -void ConsoleReporter::testRunEnded(TestRunStats const& _testRunStats) { - printTotalsDivider(_testRunStats.totals); - printTotals(_testRunStats.totals); - stream << std::endl; - StreamingReporterBase::testRunEnded(_testRunStats); -} - -void ConsoleReporter::lazyPrint() { - - m_tablePrinter->close(); - lazyPrintWithoutClosingBenchmarkTable(); -} - -void ConsoleReporter::lazyPrintWithoutClosingBenchmarkTable() { - - if (!currentTestRunInfo.used) - lazyPrintRunInfo(); - if (!currentGroupInfo.used) - lazyPrintGroupInfo(); - - if (!m_headerPrinted) { - printTestCaseAndSectionHeader(); - m_headerPrinted = true; - } -} -void ConsoleReporter::lazyPrintRunInfo() { - stream << '\n' << getLineOfChars<'~'>() << '\n'; - Colour colour(Colour::SecondaryText); - stream << currentTestRunInfo->name - << " is a Catch v" << libraryVersion() << " host application.\n" - << "Run with -? for options\n\n"; - - if (m_config->rngSeed() != 0) - stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; - - currentTestRunInfo.used = true; -} -void ConsoleReporter::lazyPrintGroupInfo() { - if (!currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1) { - printClosedHeader("Group: " + currentGroupInfo->name); - currentGroupInfo.used = true; - } -} -void ConsoleReporter::printTestCaseAndSectionHeader() { - assert(!m_sectionStack.empty()); - printOpenHeader(currentTestCaseInfo->name); - - if (m_sectionStack.size() > 1) { - Colour colourGuard(Colour::Headers); - - auto - it = m_sectionStack.begin() + 1, // Skip first section (test case) - itEnd = m_sectionStack.end(); - for (; it != itEnd; ++it) - printHeaderString(it->name, 2); - } - - SourceLineInfo lineInfo = m_sectionStack.back().lineInfo; - - if (!lineInfo.empty()) { - stream << getLineOfChars<'-'>() << '\n'; - Colour colourGuard(Colour::FileName); - stream << lineInfo << '\n'; - } - stream << getLineOfChars<'.'>() << '\n' << std::endl; -} - -void ConsoleReporter::printClosedHeader(std::string const& _name) { - printOpenHeader(_name); - stream << getLineOfChars<'.'>() << '\n'; -} -void ConsoleReporter::printOpenHeader(std::string const& _name) { - stream << getLineOfChars<'-'>() << '\n'; - { - Colour colourGuard(Colour::Headers); - printHeaderString(_name); - } -} - -// if string has a : in first line will set indent to follow it on -// subsequent lines -void ConsoleReporter::printHeaderString(std::string const& _string, std::size_t indent) { - std::size_t i = _string.find(": "); - if (i != std::string::npos) - i += 2; - else - i = 0; - stream << Column(_string).indent(indent + i).initialIndent(indent) << '\n'; -} - -struct SummaryColumn { - - SummaryColumn( std::string _label, Colour::Code _colour ) - : label( std::move( _label ) ), - colour( _colour ) {} - SummaryColumn addRow( std::size_t count ) { - ReusableStringStream rss; - rss << count; - std::string row = rss.str(); - for (auto& oldRow : rows) { - while (oldRow.size() < row.size()) - oldRow = ' ' + oldRow; - while (oldRow.size() > row.size()) - row = ' ' + row; - } - rows.push_back(row); - return *this; - } - - std::string label; - Colour::Code colour; - std::vector rows; - -}; - -void ConsoleReporter::printTotals( Totals const& totals ) { - if (totals.testCases.total() == 0) { - stream << Colour(Colour::Warning) << "No tests ran\n"; - } else if (totals.assertions.total() > 0 && totals.testCases.allPassed()) { - stream << Colour(Colour::ResultSuccess) << "All tests passed"; - stream << " (" - << pluralise(totals.assertions.passed, "assertion") << " in " - << pluralise(totals.testCases.passed, "test case") << ')' - << '\n'; - } else { - - std::vector columns; - columns.push_back(SummaryColumn("", Colour::None) - .addRow(totals.testCases.total()) - .addRow(totals.assertions.total())); - columns.push_back(SummaryColumn("passed", Colour::Success) - .addRow(totals.testCases.passed) - .addRow(totals.assertions.passed)); - columns.push_back(SummaryColumn("failed", Colour::ResultError) - .addRow(totals.testCases.failed) - .addRow(totals.assertions.failed)); - columns.push_back(SummaryColumn("failed as expected", Colour::ResultExpectedFailure) - .addRow(totals.testCases.failedButOk) - .addRow(totals.assertions.failedButOk)); - - printSummaryRow("test cases", columns, 0); - printSummaryRow("assertions", columns, 1); - } -} -void ConsoleReporter::printSummaryRow(std::string const& label, std::vector const& cols, std::size_t row) { - for (auto col : cols) { - std::string value = col.rows[row]; - if (col.label.empty()) { - stream << label << ": "; - if (value != "0") - stream << value; - else - stream << Colour(Colour::Warning) << "- none -"; - } else if (value != "0") { - stream << Colour(Colour::LightGrey) << " | "; - stream << Colour(col.colour) - << value << ' ' << col.label; - } - } - stream << '\n'; -} - -void ConsoleReporter::printTotalsDivider(Totals const& totals) { - if (totals.testCases.total() > 0) { - std::size_t failedRatio = makeRatio(totals.testCases.failed, totals.testCases.total()); - std::size_t failedButOkRatio = makeRatio(totals.testCases.failedButOk, totals.testCases.total()); - std::size_t passedRatio = makeRatio(totals.testCases.passed, totals.testCases.total()); - while (failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH - 1) - findMax(failedRatio, failedButOkRatio, passedRatio)++; - while (failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH - 1) - findMax(failedRatio, failedButOkRatio, passedRatio)--; - - stream << Colour(Colour::Error) << std::string(failedRatio, '='); - stream << Colour(Colour::ResultExpectedFailure) << std::string(failedButOkRatio, '='); - if (totals.testCases.allPassed()) - stream << Colour(Colour::ResultSuccess) << std::string(passedRatio, '='); - else - stream << Colour(Colour::Success) << std::string(passedRatio, '='); - } else { - stream << Colour(Colour::Warning) << std::string(CATCH_CONFIG_CONSOLE_WIDTH - 1, '='); - } - stream << '\n'; -} -void ConsoleReporter::printSummaryDivider() { - stream << getLineOfChars<'-'>() << '\n'; -} - -CATCH_REGISTER_REPORTER("console", ConsoleReporter) - -} // end namespace Catch - -#if defined(_MSC_VER) -#pragma warning(pop) -#endif -// end catch_reporter_console.cpp -// start catch_reporter_junit.cpp - -#include -#include -#include -#include - -namespace Catch { - - namespace { - std::string getCurrentTimestamp() { - // Beware, this is not reentrant because of backward compatibility issues - // Also, UTC only, again because of backward compatibility (%z is C++11) - time_t rawtime; - std::time(&rawtime); - auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); - -#ifdef _MSC_VER - std::tm timeInfo = {}; - gmtime_s(&timeInfo, &rawtime); -#else - std::tm* timeInfo; - timeInfo = std::gmtime(&rawtime); -#endif - - char timeStamp[timeStampSize]; - const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; - -#ifdef _MSC_VER - std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); -#else - std::strftime(timeStamp, timeStampSize, fmt, timeInfo); -#endif - return std::string(timeStamp); - } - - std::string fileNameTag(const std::vector &tags) { - auto it = std::find_if(begin(tags), - end(tags), - [] (std::string const& tag) {return tag.front() == '#'; }); - if (it != tags.end()) - return it->substr(1); - return std::string(); - } - } // anonymous namespace - - JunitReporter::JunitReporter( ReporterConfig const& _config ) - : CumulativeReporterBase( _config ), - xml( _config.stream() ) - { - m_reporterPrefs.shouldRedirectStdOut = true; - } - - JunitReporter::~JunitReporter() {} - - std::string JunitReporter::getDescription() { - return "Reports test results in an XML format that looks like Ant's junitreport target"; - } - - void JunitReporter::noMatchingTestCases( std::string const& /*spec*/ ) {} - - void JunitReporter::testRunStarting( TestRunInfo const& runInfo ) { - CumulativeReporterBase::testRunStarting( runInfo ); - xml.startElement( "testsuites" ); - } - - void JunitReporter::testGroupStarting( GroupInfo const& groupInfo ) { - suiteTimer.start(); - stdOutForSuite.clear(); - stdErrForSuite.clear(); - unexpectedExceptions = 0; - CumulativeReporterBase::testGroupStarting( groupInfo ); - } - - void JunitReporter::testCaseStarting( TestCaseInfo const& testCaseInfo ) { - m_okToFail = testCaseInfo.okToFail(); - } - - bool JunitReporter::assertionEnded( AssertionStats const& assertionStats ) { - if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail ) - unexpectedExceptions++; - return CumulativeReporterBase::assertionEnded( assertionStats ); - } - - void JunitReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { - stdOutForSuite += testCaseStats.stdOut; - stdErrForSuite += testCaseStats.stdErr; - CumulativeReporterBase::testCaseEnded( testCaseStats ); - } - - void JunitReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { - double suiteTime = suiteTimer.getElapsedSeconds(); - CumulativeReporterBase::testGroupEnded( testGroupStats ); - writeGroup( *m_testGroups.back(), suiteTime ); - } - - void JunitReporter::testRunEndedCumulative() { - xml.endElement(); - } - - void JunitReporter::writeGroup( TestGroupNode const& groupNode, double suiteTime ) { - XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); - TestGroupStats const& stats = groupNode.value; - xml.writeAttribute( "name", stats.groupInfo.name ); - xml.writeAttribute( "errors", unexpectedExceptions ); - xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); - xml.writeAttribute( "tests", stats.totals.assertions.total() ); - xml.writeAttribute( "hostname", "tbd" ); // !TBD - if( m_config->showDurations() == ShowDurations::Never ) - xml.writeAttribute( "time", "" ); - else - xml.writeAttribute( "time", suiteTime ); - xml.writeAttribute( "timestamp", getCurrentTimestamp() ); - - // Write test cases - for( auto const& child : groupNode.children ) - writeTestCase( *child ); - - xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite ), false ); - xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite ), false ); - } - - void JunitReporter::writeTestCase( TestCaseNode const& testCaseNode ) { - TestCaseStats const& stats = testCaseNode.value; - - // All test cases have exactly one section - which represents the - // test case itself. That section may have 0-n nested sections - assert( testCaseNode.children.size() == 1 ); - SectionNode const& rootSection = *testCaseNode.children.front(); - - std::string className = stats.testInfo.className; - - if( className.empty() ) { - className = fileNameTag(stats.testInfo.tags); - if ( className.empty() ) - className = "global"; - } - - if ( !m_config->name().empty() ) - className = m_config->name() + "." + className; - - writeSection( className, "", rootSection ); - } - - void JunitReporter::writeSection( std::string const& className, - std::string const& rootName, - SectionNode const& sectionNode ) { - std::string name = trim( sectionNode.stats.sectionInfo.name ); - if( !rootName.empty() ) - name = rootName + '/' + name; - - if( !sectionNode.assertions.empty() || - !sectionNode.stdOut.empty() || - !sectionNode.stdErr.empty() ) { - XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); - if( className.empty() ) { - xml.writeAttribute( "classname", name ); - xml.writeAttribute( "name", "root" ); - } - else { - xml.writeAttribute( "classname", className ); - xml.writeAttribute( "name", name ); - } - xml.writeAttribute( "time", ::Catch::Detail::stringify( sectionNode.stats.durationInSeconds ) ); - - writeAssertions( sectionNode ); - - if( !sectionNode.stdOut.empty() ) - xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); - if( !sectionNode.stdErr.empty() ) - xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); - } - for( auto const& childNode : sectionNode.childSections ) - if( className.empty() ) - writeSection( name, "", *childNode ); - else - writeSection( className, name, *childNode ); - } - - void JunitReporter::writeAssertions( SectionNode const& sectionNode ) { - for( auto const& assertion : sectionNode.assertions ) - writeAssertion( assertion ); - } - - void JunitReporter::writeAssertion( AssertionStats const& stats ) { - AssertionResult const& result = stats.assertionResult; - if( !result.isOk() ) { - std::string elementName; - switch( result.getResultType() ) { - case ResultWas::ThrewException: - case ResultWas::FatalErrorCondition: - elementName = "error"; - break; - case ResultWas::ExplicitFailure: - elementName = "failure"; - break; - case ResultWas::ExpressionFailed: - elementName = "failure"; - break; - case ResultWas::DidntThrowException: - elementName = "failure"; - break; - - // We should never see these here: - case ResultWas::Info: - case ResultWas::Warning: - case ResultWas::Ok: - case ResultWas::Unknown: - case ResultWas::FailureBit: - case ResultWas::Exception: - elementName = "internalError"; - break; - } - - XmlWriter::ScopedElement e = xml.scopedElement( elementName ); - - xml.writeAttribute( "message", result.getExpandedExpression() ); - xml.writeAttribute( "type", result.getTestMacroName() ); - - ReusableStringStream rss; - if( !result.getMessage().empty() ) - rss << result.getMessage() << '\n'; - for( auto const& msg : stats.infoMessages ) - if( msg.type == ResultWas::Info ) - rss << msg.message << '\n'; - - rss << "at " << result.getSourceInfo(); - xml.writeText( rss.str(), false ); - } - } - - CATCH_REGISTER_REPORTER( "junit", JunitReporter ) - -} // end namespace Catch -// end catch_reporter_junit.cpp -// start catch_reporter_listening.cpp - -#include - -namespace Catch { - - void ListeningReporter::addListener( IStreamingReporterPtr&& listener ) { - m_listeners.push_back( std::move( listener ) ); - } - - void ListeningReporter::addReporter(IStreamingReporterPtr&& reporter) { - assert(!m_reporter && "Listening reporter can wrap only 1 real reporter"); - m_reporter = std::move( reporter ); - } - - ReporterPreferences ListeningReporter::getPreferences() const { - return m_reporter->getPreferences(); - } - - std::set ListeningReporter::getSupportedVerbosities() { - return std::set{ }; - } - - void ListeningReporter::noMatchingTestCases( std::string const& spec ) { - for ( auto const& listener : m_listeners ) { - listener->noMatchingTestCases( spec ); - } - m_reporter->noMatchingTestCases( spec ); - } - - void ListeningReporter::benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) { - for ( auto const& listener : m_listeners ) { - listener->benchmarkStarting( benchmarkInfo ); - } - m_reporter->benchmarkStarting( benchmarkInfo ); - } - void ListeningReporter::benchmarkEnded( BenchmarkStats const& benchmarkStats ) { - for ( auto const& listener : m_listeners ) { - listener->benchmarkEnded( benchmarkStats ); - } - m_reporter->benchmarkEnded( benchmarkStats ); - } - - void ListeningReporter::testRunStarting( TestRunInfo const& testRunInfo ) { - for ( auto const& listener : m_listeners ) { - listener->testRunStarting( testRunInfo ); - } - m_reporter->testRunStarting( testRunInfo ); - } - - void ListeningReporter::testGroupStarting( GroupInfo const& groupInfo ) { - for ( auto const& listener : m_listeners ) { - listener->testGroupStarting( groupInfo ); - } - m_reporter->testGroupStarting( groupInfo ); - } - - void ListeningReporter::testCaseStarting( TestCaseInfo const& testInfo ) { - for ( auto const& listener : m_listeners ) { - listener->testCaseStarting( testInfo ); - } - m_reporter->testCaseStarting( testInfo ); - } - - void ListeningReporter::sectionStarting( SectionInfo const& sectionInfo ) { - for ( auto const& listener : m_listeners ) { - listener->sectionStarting( sectionInfo ); - } - m_reporter->sectionStarting( sectionInfo ); - } - - void ListeningReporter::assertionStarting( AssertionInfo const& assertionInfo ) { - for ( auto const& listener : m_listeners ) { - listener->assertionStarting( assertionInfo ); - } - m_reporter->assertionStarting( assertionInfo ); - } - - // The return value indicates if the messages buffer should be cleared: - bool ListeningReporter::assertionEnded( AssertionStats const& assertionStats ) { - for( auto const& listener : m_listeners ) { - static_cast( listener->assertionEnded( assertionStats ) ); - } - return m_reporter->assertionEnded( assertionStats ); - } - - void ListeningReporter::sectionEnded( SectionStats const& sectionStats ) { - for ( auto const& listener : m_listeners ) { - listener->sectionEnded( sectionStats ); - } - m_reporter->sectionEnded( sectionStats ); - } - - void ListeningReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { - for ( auto const& listener : m_listeners ) { - listener->testCaseEnded( testCaseStats ); - } - m_reporter->testCaseEnded( testCaseStats ); - } - - void ListeningReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { - for ( auto const& listener : m_listeners ) { - listener->testGroupEnded( testGroupStats ); - } - m_reporter->testGroupEnded( testGroupStats ); - } - - void ListeningReporter::testRunEnded( TestRunStats const& testRunStats ) { - for ( auto const& listener : m_listeners ) { - listener->testRunEnded( testRunStats ); - } - m_reporter->testRunEnded( testRunStats ); - } - - void ListeningReporter::skipTest( TestCaseInfo const& testInfo ) { - for ( auto const& listener : m_listeners ) { - listener->skipTest( testInfo ); - } - m_reporter->skipTest( testInfo ); - } - - bool ListeningReporter::isMulti() const { - return true; - } - -} // end namespace Catch -// end catch_reporter_listening.cpp -// start catch_reporter_xml.cpp - -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch - // Note that 4062 (not all labels are handled - // and default is missing) is enabled -#endif - -namespace Catch { - XmlReporter::XmlReporter( ReporterConfig const& _config ) - : StreamingReporterBase( _config ), - m_xml(_config.stream()) - { - m_reporterPrefs.shouldRedirectStdOut = true; - } - - XmlReporter::~XmlReporter() = default; - - std::string XmlReporter::getDescription() { - return "Reports test results as an XML document"; - } - - std::string XmlReporter::getStylesheetRef() const { - return std::string(); - } - - void XmlReporter::writeSourceInfo( SourceLineInfo const& sourceInfo ) { - m_xml - .writeAttribute( "filename", sourceInfo.file ) - .writeAttribute( "line", sourceInfo.line ); - } - - void XmlReporter::noMatchingTestCases( std::string const& s ) { - StreamingReporterBase::noMatchingTestCases( s ); - } - - void XmlReporter::testRunStarting( TestRunInfo const& testInfo ) { - StreamingReporterBase::testRunStarting( testInfo ); - std::string stylesheetRef = getStylesheetRef(); - if( !stylesheetRef.empty() ) - m_xml.writeStylesheetRef( stylesheetRef ); - m_xml.startElement( "Catch" ); - if( !m_config->name().empty() ) - m_xml.writeAttribute( "name", m_config->name() ); - } - - void XmlReporter::testGroupStarting( GroupInfo const& groupInfo ) { - StreamingReporterBase::testGroupStarting( groupInfo ); - m_xml.startElement( "Group" ) - .writeAttribute( "name", groupInfo.name ); - } - - void XmlReporter::testCaseStarting( TestCaseInfo const& testInfo ) { - StreamingReporterBase::testCaseStarting(testInfo); - m_xml.startElement( "TestCase" ) - .writeAttribute( "name", trim( testInfo.name ) ) - .writeAttribute( "description", testInfo.description ) - .writeAttribute( "tags", testInfo.tagsAsString() ); - - writeSourceInfo( testInfo.lineInfo ); - - if ( m_config->showDurations() == ShowDurations::Always ) - m_testCaseTimer.start(); - m_xml.ensureTagClosed(); - } - - void XmlReporter::sectionStarting( SectionInfo const& sectionInfo ) { - StreamingReporterBase::sectionStarting( sectionInfo ); - if( m_sectionDepth++ > 0 ) { - m_xml.startElement( "Section" ) - .writeAttribute( "name", trim( sectionInfo.name ) ) - .writeAttribute( "description", sectionInfo.description ); - writeSourceInfo( sectionInfo.lineInfo ); - m_xml.ensureTagClosed(); - } - } - - void XmlReporter::assertionStarting( AssertionInfo const& ) { } - - bool XmlReporter::assertionEnded( AssertionStats const& assertionStats ) { - - AssertionResult const& result = assertionStats.assertionResult; - - bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); - - if( includeResults || result.getResultType() == ResultWas::Warning ) { - // Print any info messages in tags. - for( auto const& msg : assertionStats.infoMessages ) { - if( msg.type == ResultWas::Info && includeResults ) { - m_xml.scopedElement( "Info" ) - .writeText( msg.message ); - } else if ( msg.type == ResultWas::Warning ) { - m_xml.scopedElement( "Warning" ) - .writeText( msg.message ); - } - } - } - - // Drop out if result was successful but we're not printing them. - if( !includeResults && result.getResultType() != ResultWas::Warning ) - return true; - - // Print the expression if there is one. - if( result.hasExpression() ) { - m_xml.startElement( "Expression" ) - .writeAttribute( "success", result.succeeded() ) - .writeAttribute( "type", result.getTestMacroName() ); - - writeSourceInfo( result.getSourceInfo() ); - - m_xml.scopedElement( "Original" ) - .writeText( result.getExpression() ); - m_xml.scopedElement( "Expanded" ) - .writeText( result.getExpandedExpression() ); - } - - // And... Print a result applicable to each result type. - switch( result.getResultType() ) { - case ResultWas::ThrewException: - m_xml.startElement( "Exception" ); - writeSourceInfo( result.getSourceInfo() ); - m_xml.writeText( result.getMessage() ); - m_xml.endElement(); - break; - case ResultWas::FatalErrorCondition: - m_xml.startElement( "FatalErrorCondition" ); - writeSourceInfo( result.getSourceInfo() ); - m_xml.writeText( result.getMessage() ); - m_xml.endElement(); - break; - case ResultWas::Info: - m_xml.scopedElement( "Info" ) - .writeText( result.getMessage() ); - break; - case ResultWas::Warning: - // Warning will already have been written - break; - case ResultWas::ExplicitFailure: - m_xml.startElement( "Failure" ); - writeSourceInfo( result.getSourceInfo() ); - m_xml.writeText( result.getMessage() ); - m_xml.endElement(); - break; - default: - break; - } - - if( result.hasExpression() ) - m_xml.endElement(); - - return true; - } - - void XmlReporter::sectionEnded( SectionStats const& sectionStats ) { - StreamingReporterBase::sectionEnded( sectionStats ); - if( --m_sectionDepth > 0 ) { - XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); - e.writeAttribute( "successes", sectionStats.assertions.passed ); - e.writeAttribute( "failures", sectionStats.assertions.failed ); - e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); - - if ( m_config->showDurations() == ShowDurations::Always ) - e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); - - m_xml.endElement(); - } - } - - void XmlReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { - StreamingReporterBase::testCaseEnded( testCaseStats ); - XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); - e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); - - if ( m_config->showDurations() == ShowDurations::Always ) - e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); - - if( !testCaseStats.stdOut.empty() ) - m_xml.scopedElement( "StdOut" ).writeText( trim( testCaseStats.stdOut ), false ); - if( !testCaseStats.stdErr.empty() ) - m_xml.scopedElement( "StdErr" ).writeText( trim( testCaseStats.stdErr ), false ); - - m_xml.endElement(); - } - - void XmlReporter::testGroupEnded( TestGroupStats const& testGroupStats ) { - StreamingReporterBase::testGroupEnded( testGroupStats ); - // TODO: Check testGroupStats.aborting and act accordingly. - m_xml.scopedElement( "OverallResults" ) - .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) - .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) - .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); - m_xml.endElement(); - } - - void XmlReporter::testRunEnded( TestRunStats const& testRunStats ) { - StreamingReporterBase::testRunEnded( testRunStats ); - m_xml.scopedElement( "OverallResults" ) - .writeAttribute( "successes", testRunStats.totals.assertions.passed ) - .writeAttribute( "failures", testRunStats.totals.assertions.failed ) - .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); - m_xml.endElement(); - } - - CATCH_REGISTER_REPORTER( "xml", XmlReporter ) - -} // end namespace Catch - -#if defined(_MSC_VER) -#pragma warning(pop) -#endif -// end catch_reporter_xml.cpp - -namespace Catch { - LeakDetector leakDetector; -} - -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - -// end catch_impl.hpp -#endif - -#ifdef CATCH_CONFIG_MAIN -// start catch_default_main.hpp - -#ifndef __OBJC__ - -#if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN) -// Standard C/C++ Win32 Unicode wmain entry point -extern "C" int wmain (int argc, wchar_t * argv[], wchar_t * []) { -#else -// Standard C/C++ main entry point -int main (int argc, char * argv[]) { -#endif - - return Catch::Session().run( argc, argv ); -} - -#else // __OBJC__ - -// Objective-C entry point -int main (int argc, char * const argv[]) { -#if !CATCH_ARC_ENABLED - NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; -#endif - - Catch::registerTestMethods(); - int result = Catch::Session().run( argc, (char**)argv ); - -#if !CATCH_ARC_ENABLED - [pool drain]; -#endif - - return result; -} - -#endif // __OBJC__ - -// end catch_default_main.hpp -#endif - -#if !defined(CATCH_CONFIG_IMPL_ONLY) - -#ifdef CLARA_CONFIG_MAIN_NOT_DEFINED -# undef CLARA_CONFIG_MAIN -#endif - -#if !defined(CATCH_CONFIG_DISABLE) -////// -// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ -#ifdef CATCH_CONFIG_PREFIX_ALL - -#define CATCH_REQUIRE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ ) -#define CATCH_REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) - -#define CATCH_REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", __VA_ARGS__ ) -#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) -#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CATCH_REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr ) -#endif// CATCH_CONFIG_DISABLE_MATCHERS -#define CATCH_REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ ) - -#define CATCH_CHECK( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) -#define CATCH_CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) -#define CATCH_CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) -#define CATCH_CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) -#define CATCH_CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) - -#define CATCH_CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", __VA_ARGS__ ) -#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) -#define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CATCH_CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) -#endif // CATCH_CONFIG_DISABLE_MATCHERS -#define CATCH_CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) - -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) - -#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) -#endif // CATCH_CONFIG_DISABLE_MATCHERS - -#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) -#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) -#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CATCH_CAPTURE", #msg " := " << ::Catch::Detail::stringify(msg) ) - -#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) -#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) -#define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) -#define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) -#define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) -#define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) -#define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) -#define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) - -#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE() - -// "BDD-style" convenience wrappers -#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) -#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) -#define CATCH_GIVEN( desc ) CATCH_SECTION( std::string( "Given: ") + desc ) -#define CATCH_WHEN( desc ) CATCH_SECTION( std::string( " When: ") + desc ) -#define CATCH_AND_WHEN( desc ) CATCH_SECTION( std::string( " And: ") + desc ) -#define CATCH_THEN( desc ) CATCH_SECTION( std::string( " Then: ") + desc ) -#define CATCH_AND_THEN( desc ) CATCH_SECTION( std::string( " And: ") + desc ) - -// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required -#else - -#define REQUIRE( ... ) INTERNAL_CATCH_TEST( "REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ ) -#define REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) - -#define REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__ ) -#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) -#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr ) -#endif // CATCH_CONFIG_DISABLE_MATCHERS -#define REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ ) - -#define CHECK( ... ) INTERNAL_CATCH_TEST( "CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) -#define CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) -#define CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) -#define CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) -#define CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) - -#define CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) -#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) -#define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) -#endif // CATCH_CONFIG_DISABLE_MATCHERS -#define CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) - -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) - -#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) -#endif // CATCH_CONFIG_DISABLE_MATCHERS - -#define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) -#define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) -#define CAPTURE( msg ) INTERNAL_CATCH_INFO( "CAPTURE", #msg " := " << ::Catch::Detail::stringify(msg) ) - -#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) -#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) -#define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) -#define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) -#define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) -#define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) -#define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) -#define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) -#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE() - -#endif - -#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) - -// "BDD-style" convenience wrappers -#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) -#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) - -#define GIVEN( desc ) SECTION( std::string(" Given: ") + desc ) -#define WHEN( desc ) SECTION( std::string(" When: ") + desc ) -#define AND_WHEN( desc ) SECTION( std::string("And when: ") + desc ) -#define THEN( desc ) SECTION( std::string(" Then: ") + desc ) -#define AND_THEN( desc ) SECTION( std::string(" And: ") + desc ) - -using Catch::Detail::Approx; - -#else -////// -// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ -#ifdef CATCH_CONFIG_PREFIX_ALL - -#define CATCH_REQUIRE( ... ) (void)(0) -#define CATCH_REQUIRE_FALSE( ... ) (void)(0) - -#define CATCH_REQUIRE_THROWS( ... ) (void)(0) -#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0) -#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) (void)(0) -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) -#endif// CATCH_CONFIG_DISABLE_MATCHERS -#define CATCH_REQUIRE_NOTHROW( ... ) (void)(0) - -#define CATCH_CHECK( ... ) (void)(0) -#define CATCH_CHECK_FALSE( ... ) (void)(0) -#define CATCH_CHECKED_IF( ... ) if (__VA_ARGS__) -#define CATCH_CHECKED_ELSE( ... ) if (!(__VA_ARGS__)) -#define CATCH_CHECK_NOFAIL( ... ) (void)(0) - -#define CATCH_CHECK_THROWS( ... ) (void)(0) -#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) (void)(0) -#define CATCH_CHECK_THROWS_WITH( expr, matcher ) (void)(0) -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) -#endif // CATCH_CONFIG_DISABLE_MATCHERS -#define CATCH_CHECK_NOTHROW( ... ) (void)(0) - -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CATCH_CHECK_THAT( arg, matcher ) (void)(0) - -#define CATCH_REQUIRE_THAT( arg, matcher ) (void)(0) -#endif // CATCH_CONFIG_DISABLE_MATCHERS - -#define CATCH_INFO( msg ) (void)(0) -#define CATCH_WARN( msg ) (void)(0) -#define CATCH_CAPTURE( msg ) (void)(0) - -#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) -#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) -#define CATCH_METHOD_AS_TEST_CASE( method, ... ) -#define CATCH_REGISTER_TEST_CASE( Function, ... ) (void)(0) -#define CATCH_SECTION( ... ) -#define CATCH_FAIL( ... ) (void)(0) -#define CATCH_FAIL_CHECK( ... ) (void)(0) -#define CATCH_SUCCEED( ... ) (void)(0) - -#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) - -// "BDD-style" convenience wrappers -#define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) -#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className ) -#define CATCH_GIVEN( desc ) -#define CATCH_WHEN( desc ) -#define CATCH_AND_WHEN( desc ) -#define CATCH_THEN( desc ) -#define CATCH_AND_THEN( desc ) - -// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required -#else - -#define REQUIRE( ... ) (void)(0) -#define REQUIRE_FALSE( ... ) (void)(0) - -#define REQUIRE_THROWS( ... ) (void)(0) -#define REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0) -#define REQUIRE_THROWS_WITH( expr, matcher ) (void)(0) -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) -#endif // CATCH_CONFIG_DISABLE_MATCHERS -#define REQUIRE_NOTHROW( ... ) (void)(0) - -#define CHECK( ... ) (void)(0) -#define CHECK_FALSE( ... ) (void)(0) -#define CHECKED_IF( ... ) if (__VA_ARGS__) -#define CHECKED_ELSE( ... ) if (!(__VA_ARGS__)) -#define CHECK_NOFAIL( ... ) (void)(0) - -#define CHECK_THROWS( ... ) (void)(0) -#define CHECK_THROWS_AS( expr, exceptionType ) (void)(0) -#define CHECK_THROWS_WITH( expr, matcher ) (void)(0) -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) -#endif // CATCH_CONFIG_DISABLE_MATCHERS -#define CHECK_NOTHROW( ... ) (void)(0) - -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CHECK_THAT( arg, matcher ) (void)(0) - -#define REQUIRE_THAT( arg, matcher ) (void)(0) -#endif // CATCH_CONFIG_DISABLE_MATCHERS - -#define INFO( msg ) (void)(0) -#define WARN( msg ) (void)(0) -#define CAPTURE( msg ) (void)(0) - -#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) -#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) -#define METHOD_AS_TEST_CASE( method, ... ) -#define REGISTER_TEST_CASE( Function, ... ) (void)(0) -#define SECTION( ... ) -#define FAIL( ... ) (void)(0) -#define FAIL_CHECK( ... ) (void)(0) -#define SUCCEED( ... ) (void)(0) -#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) - -#endif - -#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) - -// "BDD-style" convenience wrappers -#define SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) ) -#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className ) - -#define GIVEN( desc ) -#define WHEN( desc ) -#define AND_WHEN( desc ) -#define THEN( desc ) -#define AND_THEN( desc ) - -using Catch::Detail::Approx; - -#endif - -#endif // ! CATCH_CONFIG_IMPL_ONLY - -// start catch_reenable_warnings.h - - -#ifdef __clang__ -# ifdef __ICC // icpc defines the __clang__ macro -# pragma warning(pop) -# else -# pragma clang diagnostic pop -# endif -#elif defined __GNUC__ -# pragma GCC diagnostic pop -#endif - -// end catch_reenable_warnings.h -// end catch.hpp -#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED - diff --git a/Community_made.md b/Community_made.md new file mode 100644 index 000000000..efa8af415 --- /dev/null +++ b/Community_made.md @@ -0,0 +1,36 @@ +# Community made + +## Prusa-Firmware build +- `PF-build.sh` + - Maintainers: **@3d-gussner** + - Co-maintainers: + - Contributors: **@mkbel**, **@ropaha**, **@deliopoulos**, **@DRracer**, **wavexx**, **@leptun**, **@andrewluebke**, **@kuhnmarek** + - [X] **Active** since February 2019 + - [X] **Maintained** since January 2019 + +### How-to use PF-build.sh +Start `./PF-build.sh` and follow the instructions + +Help `./PF-build.sh -h` + +# MK404 Simulator + +## MK404-build.sh +**MK404 is a community 3d printer simulator created by @vintagepc** +Please checkout and support his github repository [MK404](https://github.com/vintagepc/MK404) and the [MK404 Wiki](https://github.com/vintagepc/MK404/wiki) + +At this moment the `MK404-build.sh` script is only supported on Linux +- `MK404-build.sh` + - Maintainers: **@3d-gussner** + - Co-maintainers: + - Contributors: + - [X] **Active** since August 2021 + - [X] **Maintained** since August 2021 + +### How-to use MK404-build.sh +After compiling with `PF-build.sh` you get the option to start the `MK404` simulator with the fresh compiled firmware. (Linux only at this moment) + +Help `./MK404-build.sh -h` + +## Translations +- see [/lang/Community_made_translations.md](https://github.com/prusa3d/Prusa-Firmware/blob/MK3/lang/Community_made_translations.md) diff --git a/Firmware/AutoDeplete.cpp b/Firmware/AutoDeplete.cpp deleted file mode 100644 index 9c4340f09..000000000 --- a/Firmware/AutoDeplete.cpp +++ /dev/null @@ -1,79 +0,0 @@ -//! @file -//! @author: Marek Bel -//! @date Jan 3, 2019 - -#include "AutoDeplete.h" -#include "assert.h" - -//! @brief bit field marking depleted filaments -//! -//! binary 1 marks filament as depleted -//! Zero initialized value means, that no filament is depleted. -static uint8_t depleted; -static const uint8_t filamentCount = 5; - -//! @return binary 1 for all filaments -//! @par fCount number of filaments -static constexpr uint8_t allDepleted(uint8_t fCount) -{ - return fCount == 1 ? 1 : ((1 << (fCount - 1)) | allDepleted(fCount - 1)); -} - -//! @brief Is filament available for printing? -//! @par filament Filament number to be checked -//! @retval true Filament is available for printing. -//! @retval false Filament is not available for printing. -static bool loaded(uint8_t filament) -{ - if (depleted & (1 << filament)) return false; - return true; -} - -//! @brief Mark filament as not available for printing. -//! @par filament filament to be marked -void ad_markDepleted(uint8_t filament) -{ - assert(filament < filamentCount); - if (filament < filamentCount) - { - depleted |= 1 << filament; - } -} - -//! @brief Mark filament as available for printing. -//! @par filament filament to be marked -void ad_markLoaded(uint8_t filament) -{ - assert(filament < filamentCount); - if (filament < filamentCount) - { - depleted &= ~(1 << filament); - } -} - -//! @brief Get alternative filament, which is not depleted -//! @par filament filament -//! @return Filament, if it is depleted, returns next available, -//! if all filaments are depleted, returns filament function parameter. -uint8_t ad_getAlternative(uint8_t filament) -{ - assert(filament < filamentCount); - for (uint8_t i = 0; i - -void ad_markDepleted(uint8_t filament); -void ad_markLoaded(uint8_t filament); -uint8_t ad_getAlternative(uint8_t filament); -bool ad_allDepleted(); - -#endif /* AUTODEPLETE_H */ diff --git a/Firmware/Configuration.cpp b/Firmware/Configuration.cpp index f87849285..343ae419a 100644 --- a/Firmware/Configuration.cpp +++ b/Firmware/Configuration.cpp @@ -1,10 +1,7 @@ #include "Configuration.h" -#include "Configuration_prusa.h" +#include "Configuration_var.h" const uint16_t _nPrinterType PROGMEM=PRINTER_TYPE; const char _sPrinterName[] PROGMEM=PRINTER_NAME; const uint16_t _nPrinterMmuType PROGMEM=PRINTER_MMU_TYPE; const char _sPrinterMmuName[] PROGMEM=PRINTER_MMU_NAME; - -uint16_t nPrinterType; -PGM_P sPrinterName; \ No newline at end of file diff --git a/Firmware/Configuration.h b/Firmware/Configuration.h index 2f2ea7d8e..7874313fa 100644 --- a/Firmware/Configuration.h +++ b/Firmware/Configuration.h @@ -12,12 +12,21 @@ extern const uint16_t _nPrinterType; extern const char _sPrinterName[] PROGMEM; extern const uint16_t _nPrinterMmuType; extern const char _sPrinterMmuName[] PROGMEM; -extern uint16_t nPrinterType; -extern PGM_P sPrinterName; // Firmware version -#define FW_VERSION "3.9.0" -#define FW_COMMIT_NR 3175 +#define FW_MAJOR 3 +#define FW_MINOR 13 +#define FW_REVISION 0 +#define FW_FLAVOR ALPHA //uncomment if DEBUG, DEVEL, ALPHA, BETA or RC +#define FW_FLAVERSION 1 //uncomment if FW_FLAVOR is defined and versioning is needed. Limited to max 8. +#ifndef FW_FLAVOR + #define FW_VERSION STR(FW_MAJOR) "." STR(FW_MINOR) "." STR(FW_REVISION) +#else + #define FW_VERSION STR(FW_MAJOR) "." STR(FW_MINOR) "." STR(FW_REVISION) "-" STR(FW_FLAVOR) "" STR(FW_FLAVERSION) +#endif + +#define FW_COMMIT_NR 6054 + // FW_VERSION_UNKNOWN means this is an unofficial build. // The firmware should only be checked into github with this symbol. #define FW_DEV_VERSION FW_VERSION_UNKNOWN @@ -52,7 +61,14 @@ extern PGM_P sPrinterName; #undef DEBUG_BUILD #endif -#include "Configuration_prusa.h" +#ifndef SOURCE_DATE_EPOCH +#define SOURCE_DATE_EPOCH __DATE__ +#endif +#ifndef SOURCE_TIME_EPOCH +#define SOURCE_TIME_EPOCH __TIME__ +#endif + +#include "Configuration_var.h" #define FW_PRUSA3D_MAGIC "PRUSA3DFW" #define FW_PRUSA3D_MAGIC_LEN 10 @@ -67,9 +83,7 @@ extern PGM_P sPrinterName; // startup. Implementation of an idea by Prof Braino to inform user that any changes made to this // build by the user have been successfully uploaded into firmware. -//#define STRING_VERSION "1.0.2" - -#define STRING_VERSION_CONFIG_H __DATE__ " " __TIME__ // build date and time +#define STRING_VERSION_CONFIG_H SOURCE_DATE_EPOCH " " SOURCE_TIME_EPOCH // build date and time #define STRING_CONFIG_H_AUTHOR "(none, default config)" // Who made the changes. // SERIAL_PORT selects which serial port should be used for communication with the host. @@ -262,7 +276,6 @@ your extruder heater takes 2 minutes to hit the target on heating. #define DISABLE_Y 0 #define DISABLE_Z 0 #define DISABLE_E 0// For all extruders -#define DISABLE_INACTIVE_EXTRUDER 1 //disable only inactive extruders and keep active extruder enabled // ENDSTOP SETTINGS: @@ -413,18 +426,11 @@ your extruder heater takes 2 minutes to hit the target on heating. //Manual homing switch locations: // For deltabots this means top and center of the Cartesian print volume. - -// Offset of the extruders (uncomment if using more than one and relying on firmware to position when changing). -// The offset has to be X=0, Y=0 for the extruder 0 hotend (default extruder). -// For the other hotends it is their distance from the extruder 0 hotend. -// #define EXTRUDER_OFFSET_X {0.0, 20.00} // (in mm) for each extruder, offset of the hotend on the X axis -// #define EXTRUDER_OFFSET_Y {0.0, 5.00} // (in mm) for each extruder, offset of the hotend on the Y axis - // The speed change that does not require acceleration (i.e. the software might assume it can be done instantaneously) #define DEFAULT_XJERK 10 // (mm/sec) #define DEFAULT_YJERK 10 // (mm/sec) #define DEFAULT_ZJERK 0.4 // (mm/sec) -#define DEFAULT_EJERK 2.5 // (mm/sec) +#define DEFAULT_EJERK 4.5 // (mm/sec) //=========================================================================== //=============================Additional Features=========================== @@ -433,23 +439,14 @@ your extruder heater takes 2 minutes to hit the target on heating. // Custom M code points #define CUSTOM_M_CODES #ifdef CUSTOM_M_CODES +#ifdef ENABLE_AUTO_BED_LEVELING #define CUSTOM_M_CODE_SET_Z_PROBE_OFFSET 851 #define Z_PROBE_OFFSET_RANGE_MIN -15 #define Z_PROBE_OFFSET_RANGE_MAX -5 -#endif +#endif // ENABLE_AUTO_BED_LEVELING +#endif // CUSTOM_M_CODES -// EEPROM -// The microcontroller can store settings in the EEPROM, e.g. max velocity... -// M500 - stores parameters in EEPROM -// M501 - reads parameters from EEPROM (if you need reset them after you changed them temporarily). -// M502 - reverts to the default "factory settings". You still need to store them in EEPROM afterwards if you want to. -//define this to enable EEPROM support -//#define EEPROM_SETTINGS -//to disable EEPROM Serial responses and decrease program space by ~1700 byte: comment this out: -// please keep turned on if you can. -//#define EEPROM_CHITCHAT - // Host Keepalive // // When enabled Marlin will send a busy status message to the host @@ -478,11 +475,6 @@ your extruder heater takes 2 minutes to hit the target on heating. // Increase the FAN pwm frequency. Removes the PWM noise but increases heating in the FET/Arduino //#define FAST_PWM_FAN -// Temperature status LEDs that display the hotend and bet temperature. -// If all hotends and bed temperature and temperature setpoint are < 54C then the BLUE led is on. -// Otherwise the RED led is on. There is 1C hysteresis. -//#define TEMP_STAT_LEDS - // Use software PWM to drive the fan, as for the heaters. This uses a very low frequency // which is not ass annoying as with the hardware PWM. On the other hand, if this frequency // is too low, you should also increment SOFT_PWM_SCALE. @@ -524,39 +516,14 @@ your extruder heater takes 2 minutes to hit the target on heating. #define DEFAULT_NOMINAL_FILAMENT_DIA 1.75 //Enter the diameter (in mm) of the filament generally used (3.0 mm or 1.75 mm). Used by the volumetric extrusion. -// Calibration status of the machine, to be stored into the EEPROM, -// (unsigned char*)EEPROM_CALIBRATION_STATUS -enum CalibrationStatus -{ - // Freshly assembled, needs to peform a self-test and the XYZ calibration. - CALIBRATION_STATUS_ASSEMBLED = 255, - - // For the wizard: self test has been performed, now the XYZ calibration is needed. - CALIBRATION_STATUS_XYZ_CALIBRATION = 250, - - // For the wizard: factory assembled, needs to run Z calibration. - CALIBRATION_STATUS_Z_CALIBRATION = 240, - - // The XYZ calibration has been performed, now it remains to run the V2Calibration.gcode. - CALIBRATION_STATUS_LIVE_ADJUST = 230, - - // Calibrated, ready to print. - CALIBRATION_STATUS_CALIBRATED = 1, - - // Legacy: resetted by issuing a G86 G-code. - // This value can only be expected after an upgrade from the initial MK2 firmware releases. - // Currently the G86 sets the calibration status to - CALIBRATION_STATUS_UNKNOWN = 0, -}; - // Try to maintain a minimum distance from the bed even when Z is // unknown when doing the following operations -#define MIN_Z_FOR_LOAD 50 -#define MIN_Z_FOR_UNLOAD 20 -#define MIN_Z_FOR_PREHEAT 10 +#define MIN_Z_FOR_LOAD 50 // lcd filament loading or autoload +#define MIN_Z_FOR_UNLOAD 50 // lcd filament unloading +#define MIN_Z_FOR_SWAP 27 // filament change (including M600) +#define MIN_Z_FOR_PREHEAT 10 // lcd preheat #include "Configuration_adv.h" #include "thermistortables.h" - #endif //__CONFIGURATION_H diff --git a/Firmware/ConfigurationStore.cpp b/Firmware/ConfigurationStore.cpp index 0bd13a3a1..7f5222736 100644 --- a/Firmware/ConfigurationStore.cpp +++ b/Firmware/ConfigurationStore.cpp @@ -5,7 +5,7 @@ #include "temperature.h" #include "ultralcd.h" #include "ConfigurationStore.h" -#include "Configuration_prusa.h" +#include "Configuration_var.h" #ifdef MESH_BED_LEVELING #include "mesh_bed_leveling.h" @@ -15,88 +15,23 @@ #include "tmc2130.h" #endif - M500_conf cs; -//! @brief Write data to EEPROM -//! @param pos destination in EEPROM, 0 is start -//! @param value value to be written -//! @param size size of type pointed by value -//! @param name name of variable written, used only for debug input if DEBUG_EEPROM_WRITE defined -//! @retval true success -//! @retval false failed -#ifdef DEBUG_EEPROM_WRITE -static bool EEPROM_writeData(uint8_t* pos, uint8_t* value, uint8_t size, const char* name) -#else //DEBUG_EEPROM_WRITE -static bool EEPROM_writeData(uint8_t* pos, uint8_t* value, uint8_t size, const char*) -#endif //DEBUG_EEPROM_WRITE -{ -#ifdef DEBUG_EEPROM_WRITE - printf_P(PSTR("EEPROM_WRITE_VAR addr=0x%04x size=0x%02hhx name=%s\n"), pos, size, name); -#endif //DEBUG_EEPROM_WRITE - while (size--) - { - - eeprom_update_byte(pos, *value); - if (eeprom_read_byte(pos) != *value) { - SERIAL_ECHOLNPGM("EEPROM Error"); - return false; - } - - pos++; - value++; - } - return true; -} - -#ifdef DEBUG_EEPROM_READ -static void EEPROM_readData(uint8_t* pos, uint8_t* value, uint8_t size, const char* name) -#else //DEBUG_EEPROM_READ -static void EEPROM_readData(uint8_t* pos, uint8_t* value, uint8_t size, const char*) -#endif //DEBUG_EEPROM_READ -{ -#ifdef DEBUG_EEPROM_READ - printf_P(PSTR("EEPROM_READ_VAR addr=0x%04x size=0x%02hhx name=%s\n"), pos, size, name); -#endif //DEBUG_EEPROM_READ - while(size--) - { - *value = eeprom_read_byte(pos); - pos++; - value++; - } -} - #define EEPROM_VERSION "V2" -#ifdef EEPROM_SETTINGS -void Config_StoreSettings() -{ - strcpy(cs.version,"000"); //!< invalidate data first @TODO use erase to save one erase cycle - - if (EEPROM_writeData(reinterpret_cast(EEPROM_M500_base),reinterpret_cast(&cs),sizeof(cs),0), "cs, invalid version") - { - strcpy(cs.version,EEPROM_VERSION); //!< validate data if write succeed - EEPROM_writeData(reinterpret_cast(EEPROM_M500_base->version), reinterpret_cast(cs.version), sizeof(cs.version), "cs.version valid"); - } - - SERIAL_ECHO_START; - SERIAL_ECHOLNPGM("Settings Stored"); -} -#endif //EEPROM_SETTINGS - #ifndef DISABLE_M503 void Config_PrintSettings(uint8_t level) { // Always have this function, even with EEPROM_SETTINGS disabled, the current values will be shown #ifdef TMC2130 - printf_P(PSTR( - "%SSteps per unit:\n%S M92 X%.2f Y%.2f Z%.2f E%.2f\n" + printf_P(PSTR( + "%SSteps per unit:\n%S M92 X%.2f Y%.2f Z%.2f E%.2f\n" "%SUStep resolution: \n%S M350 X%d Y%d Z%d E%d\n" "%SMaximum feedrates - normal (mm/s):\n%S M203 X%.2f Y%.2f Z%.2f E%.2f\n" "%SMaximum feedrates - stealth (mm/s):\n%S M203 X%.2f Y%.2f Z%.2f E%.2f\n" "%SMaximum acceleration - normal (mm/s2):\n%S M201 X%lu Y%lu Z%lu E%lu\n" "%SMaximum acceleration - stealth (mm/s2):\n%S M201 X%lu Y%lu Z%lu E%lu\n" - "%SAcceleration: S=acceleration, T=retract acceleration\n%S M204 S%.2f T%.2f\n" + "%SAcceleration: P=print, R=retract, T=travel\n%S M204 P%.2f R%.2f T%.2f\n" "%SAdvanced variables: S=Min feedrate (mm/s), T=Min travel feedrate (mm/s), B=minimum segment time (ms), X=maximum XY jerk (mm/s), Z=maximum Z jerk (mm/s), E=maximum E jerk (mm/s)\n%S M205 S%.2f T%.2f B%.2f X%.2f Y%.2f Z%.2f E%.2f\n" "%SHome offset (mm):\n%S M206 X%.2f Y%.2f Z%.2f\n" ), @@ -106,7 +41,7 @@ void Config_PrintSettings(uint8_t level) echomagic, echomagic, cs.max_feedrate_silent[X_AXIS], cs.max_feedrate_silent[Y_AXIS], cs.max_feedrate_silent[Z_AXIS], cs.max_feedrate_silent[E_AXIS], echomagic, echomagic, cs.max_acceleration_units_per_sq_second_normal[X_AXIS], cs.max_acceleration_units_per_sq_second_normal[Y_AXIS], cs.max_acceleration_units_per_sq_second_normal[Z_AXIS], cs.max_acceleration_units_per_sq_second_normal[E_AXIS], echomagic, echomagic, cs.max_acceleration_units_per_sq_second_silent[X_AXIS], cs.max_acceleration_units_per_sq_second_silent[Y_AXIS], cs.max_acceleration_units_per_sq_second_silent[Z_AXIS], cs.max_acceleration_units_per_sq_second_silent[E_AXIS], - echomagic, echomagic, cs.acceleration, cs.retract_acceleration, + echomagic, echomagic, cs.acceleration, cs.retract_acceleration, cs.travel_acceleration, echomagic, echomagic, cs.minimumfeedrate, cs.mintravelfeedrate, cs.minsegmenttime, cs.max_jerk[X_AXIS], cs.max_jerk[Y_AXIS], cs.max_jerk[Z_AXIS], cs.max_jerk[E_AXIS], echomagic, echomagic, cs.add_homing[X_AXIS], cs.add_homing[Y_AXIS], cs.add_homing[Z_AXIS] #else //TMC2130 @@ -114,61 +49,68 @@ void Config_PrintSettings(uint8_t level) "%SSteps per unit:\n%S M92 X%.2f Y%.2f Z%.2f E%.2f\n" "%SMaximum feedrates (mm/s):\n%S M203 X%.2f Y%.2f Z%.2f E%.2f\n" "%SMaximum acceleration (mm/s2):\n%S M201 X%lu Y%lu Z%lu E%lu\n" - "%SAcceleration: S=acceleration, T=retract acceleration\n%S M204 S%.2f T%.2f\n" + "%SAcceleration: P=print, R=retract, T=travel\n%S M204 P%.2f R%.2f T%.2f\n" "%SAdvanced variables: S=Min feedrate (mm/s), T=Min travel feedrate (mm/s), B=minimum segment time (ms), X=maximum XY jerk (mm/s), Z=maximum Z jerk (mm/s), E=maximum E jerk (mm/s)\n%S M205 S%.2f T%.2f B%.2f X%.2f Y%.2f Z%.2f E%.2f\n" "%SHome offset (mm):\n%S M206 X%.2f Y%.2f Z%.2f\n" ), echomagic, echomagic, cs.axis_steps_per_unit[X_AXIS], cs.axis_steps_per_unit[Y_AXIS], cs.axis_steps_per_unit[Z_AXIS], cs.axis_steps_per_unit[E_AXIS], echomagic, echomagic, max_feedrate[X_AXIS], max_feedrate[Y_AXIS], max_feedrate[Z_AXIS], max_feedrate[E_AXIS], echomagic, echomagic, max_acceleration_units_per_sq_second[X_AXIS], max_acceleration_units_per_sq_second[Y_AXIS], max_acceleration_units_per_sq_second[Z_AXIS], max_acceleration_units_per_sq_second[E_AXIS], - echomagic, echomagic, cs.acceleration, cs.retract_acceleration, + echomagic, echomagic, cs.acceleration, cs.retract_acceleration, cs.travel_acceleration, echomagic, echomagic, cs.minimumfeedrate, cs.mintravelfeedrate, cs.minsegmenttime, cs.max_jerk[X_AXIS], cs.max_jerk[Y_AXIS], cs.max_jerk[Z_AXIS], cs.max_jerk[E_AXIS], echomagic, echomagic, cs.add_homing[X_AXIS], cs.add_homing[Y_AXIS], cs.add_homing[Z_AXIS] #endif //TMC2130 - ); + ); #ifdef PIDTEMP - printf_P(PSTR("%SPID settings:\n%S M301 P%.2f I%.2f D%.2f\n"), - echomagic, echomagic, cs.Kp, unscalePID_i(cs.Ki), unscalePID_d(cs.Kd)); + printf_P(PSTR("%SPID settings:\n%S M301 P%.2f I%.2f D%.2f\n"), + echomagic, echomagic, cs.Kp, unscalePID_i(cs.Ki), unscalePID_d(cs.Kd)); #endif #ifdef PIDTEMPBED - printf_P(PSTR("%SPID heatbed settings:\n%S M304 P%.2f I%.2f D%.2f\n"), - echomagic, echomagic, cs.bedKp, unscalePID_i(cs.bedKi), unscalePID_d(cs.bedKd)); + printf_P(PSTR("%SPID heatbed settings:\n%S M304 P%.2f I%.2f D%.2f\n"), + echomagic, echomagic, cs.bedKp, unscalePID_i(cs.bedKi), unscalePID_d(cs.bedKd)); #endif #ifdef FWRETRACT - printf_P(PSTR( - "%SRetract: S=Length (mm) F:Speed (mm/m) Z: ZLift (mm)\n%S M207 S%.2f F%.2f Z%.2f\n" - "%SRecover: S=Extra length (mm) F:Speed (mm/m)\n%S M208 S%.2f F%.2f\n" - "%SAuto-Retract: S=0 to disable, 1 to interpret extrude-only moves as retracts or recoveries\n%S M209 S%d\n" - ), - echomagic, echomagic, cs.retract_length, cs.retract_feedrate*60, cs.retract_zlift, - echomagic, echomagic, cs.retract_recover_length, cs.retract_recover_feedrate*60, - echomagic, echomagic, (cs.autoretract_enabled ? 1 : 0) - ); + printf_P(PSTR( + "%SRetract: S=Length (mm) F:Speed (mm/m) Z: ZLift (mm)\n%S M207 S%.2f F%.2f Z%.2f\n" + "%SRecover: S=Extra length (mm) F:Speed (mm/m)\n%S M208 S%.2f F%.2f\n" + "%SAuto-Retract: S=0 to disable, 1 to interpret extrude-only moves as retracts or recoveries\n%S M209 S%d\n" + ), + echomagic, echomagic, cs.retract_length, cs.retract_feedrate*60, cs.retract_zlift, + echomagic, echomagic, cs.retract_recover_length, cs.retract_recover_feedrate*60, + echomagic, echomagic, (cs.autoretract_enabled ? 1 : 0) + ); #if EXTRUDERS > 1 - printf_P(PSTR("%SMulti-extruder settings:\n%S Swap retract length (mm): %.2f\n%S Swap rec. addl. length (mm): %.2f\n"), - echomagic, echomagic, retract_length_swap, echomagic, retract_recover_length_swap); + printf_P(PSTR("%SMulti-extruder settings:\n%S Swap retract length (mm): %.2f\n%S Swap rec. addl. length (mm): %.2f\n"), + echomagic, echomagic, retract_length_swap, echomagic, retract_recover_length_swap); #endif - if (cs.volumetric_enabled) { - printf_P(PSTR("%SFilament settings:\n%S M200 D%.2f\n"), - echomagic, echomagic, cs.filament_size[0]); + if (cs.volumetric_enabled) { + printf_P(PSTR("%SFilament settings:\n%S M200 D%.2f\n"), + echomagic, echomagic, cs.filament_size[0]); #if EXTRUDERS > 1 - printf_P(PSTR("%S M200 T1 D%.2f\n"), - echomagic, echomagic, cs.filament_size[1]); + printf_P(PSTR("%S M200 T1 D%.2f\n"), + echomagic, echomagic, cs.filament_size[1]); #if EXTRUDERS > 2 - printf_P(PSTR("%S M200 T1 D%.2f\n"), - echomagic, echomagic, cs.filament_size[2]); + printf_P(PSTR("%S M200 T1 D%.2f\n"), + echomagic, echomagic, cs.filament_size[2]); #endif #endif } else { printf_P(PSTR("%SFilament settings: Disabled\n"), echomagic); } #endif - if (level >= 10) { + if (level >= 10) { #ifdef LIN_ADVANCE - printf_P(PSTR("%SLinear advance settings:%S M900 K%.2f\n"), + printf_P(PSTR("%SLinear advance settings:%S M900 K%.2f\n"), echomagic, echomagic, extruder_advance_K); #endif //LIN_ADVANCE - } + } + // Arc Interpolation Settings + printf_P(PSTR( + "%SArc Settings: P:Max length(mm) S:Min length (mm) N:Corrections R:Min segments F:Segments/sec.\n%S M214 P%.2f S%.2f N%d R%d F%d\n"), + echomagic, echomagic, cs.mm_per_arc_segment, cs.min_mm_per_arc_segment, cs.n_arc_correction, cs.min_arc_segments, cs.arc_segments_per_sec); +#ifdef TEMP_MODEL + temp_model_report_settings(); +#endif } #endif @@ -184,7 +126,7 @@ static_assert (false, "zprobe_zoffset was not initialized in printers in field t "0.0, if this is not acceptable, increment EEPROM_VERSION to force use default_conf"); #endif -static_assert (sizeof(M500_conf) == 192, "sizeof(M500_conf) has changed, ensure that EEPROM_VERSION has been incremented, " +static_assert (sizeof(M500_conf) == 209, "sizeof(M500_conf) has changed, ensure that EEPROM_VERSION has been incremented, " "or if you added members in the end of struct, ensure that historically uninitialized values will be initialized." "If this is caused by change to more then 8bit processor, decide whether make this struct packed to save EEPROM," "leave as it is to keep fast code, or reorder struct members to pack more tightly."); @@ -232,90 +174,100 @@ static const M500_conf default_conf PROGMEM = #else // TMC2130 {16,16,16,16}, #endif + DEFAULT_TRAVEL_ACCELERATION, + DEFAULT_MM_PER_ARC_SEGMENT, + DEFAULT_MIN_MM_PER_ARC_SEGMENT, + DEFAULT_N_ARC_CORRECTION, + DEFAULT_MIN_ARC_SEGMENTS, + DEFAULT_ARC_SEGMENTS_PER_SEC }; + +void Config_StoreSettings() +{ + strcpy_P(cs.version, default_conf.version); + eeprom_update_block(reinterpret_cast(&cs), reinterpret_cast(EEPROM_M500_base), sizeof(cs)); +#ifdef TEMP_MODEL + temp_model_save_settings(); +#endif + + SERIAL_ECHO_START; + SERIAL_ECHOLNPGM("Settings Stored"); +} + + //! @brief Read M500 configuration -//! @retval true Succeeded. Stored settings retrieved or default settings retrieved in case EEPROM has been erased. -//! @retval false Failed. Default settings has been retrieved, because of older version or corrupted data. +//! @retval true Succeeded. Stored settings retrieved or default settings retrieved in case EEPROM cs was empty. +//! @retval false Failed. Default settings has been retrieved, because of version mismatch bool Config_RetrieveSettings() { - bool previous_settings_retrieved = true; - char ver[4]=EEPROM_VERSION; - EEPROM_readData(reinterpret_cast(EEPROM_M500_base->version), reinterpret_cast(cs.version), sizeof(cs.version), "cs.version"); //read stored version + eeprom_read_block(reinterpret_cast(cs.version), reinterpret_cast(EEPROM_M500_base->version), sizeof(cs.version)); // SERIAL_ECHOLN("Version: [" << ver << "] Stored version: [" << cs.version << "]"); - if (strncmp(ver,cs.version,3) == 0) // version number match + if (strncmp_P(cs.version, default_conf.version, sizeof(EEPROM_VERSION)) == 0) // version number match { - - EEPROM_readData(reinterpret_cast(EEPROM_M500_base), reinterpret_cast(&cs), sizeof(cs), "cs"); - + // Initialize arc interpolation settings in eeprom if they are not already + eeprom_init_default_float(&EEPROM_M500_base->mm_per_arc_segment, pgm_read_float(&default_conf.mm_per_arc_segment)); + eeprom_init_default_float(&EEPROM_M500_base->min_mm_per_arc_segment, pgm_read_float(&default_conf.min_mm_per_arc_segment)); + eeprom_init_default_byte(&EEPROM_M500_base->n_arc_correction, pgm_read_byte(&default_conf.n_arc_correction)); + eeprom_init_default_word(&EEPROM_M500_base->min_arc_segments, pgm_read_word(&default_conf.min_arc_segments)); + eeprom_init_default_word(&EEPROM_M500_base->arc_segments_per_sec, pgm_read_word(&default_conf.arc_segments_per_sec)); - if (cs.max_jerk[X_AXIS] > DEFAULT_XJERK) cs.max_jerk[X_AXIS] = DEFAULT_XJERK; - if (cs.max_jerk[Y_AXIS] > DEFAULT_YJERK) cs.max_jerk[Y_AXIS] = DEFAULT_YJERK; + // Initialize the travel_acceleration in eeprom if not already + eeprom_init_default_float(&EEPROM_M500_base->travel_acceleration, pgm_read_float(&default_conf.travel_acceleration)); + + // Initialize the max_feedrate_silent and max_acceleration_units_per_sq_second_silent in eeprom if not already + eeprom_init_default_block(&EEPROM_M500_base->max_feedrate_silent, sizeof(EEPROM_M500_base->max_feedrate_silent), default_conf.max_feedrate_silent); + eeprom_init_default_block(&EEPROM_M500_base->max_acceleration_units_per_sq_second_silent, sizeof(EEPROM_M500_base->max_acceleration_units_per_sq_second_silent), default_conf.max_acceleration_units_per_sq_second_silent); + + // load the CS to RAM + eeprom_read_block(reinterpret_cast(&cs), reinterpret_cast(EEPROM_M500_base), sizeof(cs)); calculate_extruder_multipliers(); - //if max_feedrate_silent and max_acceleration_units_per_sq_second_silent were never stored to eeprom, use default values: - for (uint8_t i = 0; i < (sizeof(cs.max_feedrate_silent)/sizeof(cs.max_feedrate_silent[0])); ++i) - { - const uint32_t erased = 0xffffffff; - bool initialized = false; - - for(uint8_t j = 0; j < sizeof(float); ++j) - { - if(0xff != reinterpret_cast(&(cs.max_feedrate_silent[i]))[j]) initialized = true; - } - if (!initialized) memcpy_P(&cs.max_feedrate_silent[i],&default_conf.max_feedrate_silent[i], sizeof(cs.max_feedrate_silent[i])); - if (erased == cs.max_acceleration_units_per_sq_second_silent[i]) { - memcpy_P(&cs.max_acceleration_units_per_sq_second_silent[i],&default_conf.max_acceleration_units_per_sq_second_silent[i],sizeof(cs.max_acceleration_units_per_sq_second_silent[i])); - } - } - #ifdef TMC2130 - for (uint8_t j = X_AXIS; j <= Y_AXIS; j++) - { - if (cs.max_feedrate_normal[j] > NORMAL_MAX_FEEDRATE_XY) - cs.max_feedrate_normal[j] = NORMAL_MAX_FEEDRATE_XY; - if (cs.max_feedrate_silent[j] > SILENT_MAX_FEEDRATE_XY) - cs.max_feedrate_silent[j] = SILENT_MAX_FEEDRATE_XY; - if (cs.max_acceleration_units_per_sq_second_normal[j] > NORMAL_MAX_ACCEL_XY) - cs.max_acceleration_units_per_sq_second_normal[j] = NORMAL_MAX_ACCEL_XY; - if (cs.max_acceleration_units_per_sq_second_silent[j] > SILENT_MAX_ACCEL_XY) - cs.max_acceleration_units_per_sq_second_silent[j] = SILENT_MAX_ACCEL_XY; - } + for (uint8_t j = X_AXIS; j <= Y_AXIS; j++) + { + if (cs.max_feedrate_normal[j] > NORMAL_MAX_FEEDRATE_XY) + cs.max_feedrate_normal[j] = NORMAL_MAX_FEEDRATE_XY; + if (cs.max_feedrate_silent[j] > SILENT_MAX_FEEDRATE_XY) + cs.max_feedrate_silent[j] = SILENT_MAX_FEEDRATE_XY; + if (cs.max_acceleration_units_per_sq_second_normal[j] > NORMAL_MAX_ACCEL_XY) + cs.max_acceleration_units_per_sq_second_normal[j] = NORMAL_MAX_ACCEL_XY; + if (cs.max_acceleration_units_per_sq_second_silent[j] > SILENT_MAX_ACCEL_XY) + cs.max_acceleration_units_per_sq_second_silent[j] = SILENT_MAX_ACCEL_XY; + } - if(cs.axis_ustep_resolution[X_AXIS] == 0xff){ cs.axis_ustep_resolution[X_AXIS] = TMC2130_USTEPS_XY; } - if(cs.axis_ustep_resolution[Y_AXIS] == 0xff){ cs.axis_ustep_resolution[Y_AXIS] = TMC2130_USTEPS_XY; } - if(cs.axis_ustep_resolution[Z_AXIS] == 0xff){ cs.axis_ustep_resolution[Z_AXIS] = TMC2130_USTEPS_Z; } - if(cs.axis_ustep_resolution[E_AXIS] == 0xff){ cs.axis_ustep_resolution[E_AXIS] = TMC2130_USTEPS_E; } + if(cs.axis_ustep_resolution[X_AXIS] == 0xff){ cs.axis_ustep_resolution[X_AXIS] = TMC2130_USTEPS_XY; } + if(cs.axis_ustep_resolution[Y_AXIS] == 0xff){ cs.axis_ustep_resolution[Y_AXIS] = TMC2130_USTEPS_XY; } + if(cs.axis_ustep_resolution[Z_AXIS] == 0xff){ cs.axis_ustep_resolution[Z_AXIS] = TMC2130_USTEPS_Z; } + if(cs.axis_ustep_resolution[E_AXIS] == 0xff){ cs.axis_ustep_resolution[E_AXIS] = TMC2130_USTEPS_E; } - tmc2130_set_res(X_AXIS, cs.axis_ustep_resolution[X_AXIS]); - tmc2130_set_res(Y_AXIS, cs.axis_ustep_resolution[Y_AXIS]); - tmc2130_set_res(Z_AXIS, cs.axis_ustep_resolution[Z_AXIS]); - tmc2130_set_res(E_AXIS, cs.axis_ustep_resolution[E_AXIS]); + tmc2130_set_res(X_AXIS, cs.axis_ustep_resolution[X_AXIS]); + tmc2130_set_res(Y_AXIS, cs.axis_ustep_resolution[Y_AXIS]); + tmc2130_set_res(Z_AXIS, cs.axis_ustep_resolution[Z_AXIS]); + tmc2130_set_res(E_AXIS, cs.axis_ustep_resolution[E_AXIS]); #endif //TMC2130 reset_acceleration_rates(); - // Call updatePID (similar to when we have processed M301) - updatePID(); + // Call updatePID (similar to when we have processed M301) + updatePID(); +#ifdef TEMP_MODEL + temp_model_load_settings(); +#endif + SERIAL_ECHO_START; SERIAL_ECHOLNPGM("Stored settings retrieved"); } else { Config_ResetDefault(); - //Return false to inform user that eeprom version was changed and firmware is using default hardcoded settings now. - //In case that storing to eeprom was not used yet, do not inform user that hardcoded settings are used. - if (eeprom_read_byte(reinterpret_cast(&(EEPROM_M500_base->version[0]))) != 0xFF || - eeprom_read_byte(reinterpret_cast(&(EEPROM_M500_base->version[1]))) != 0xFF || - eeprom_read_byte(reinterpret_cast(&(EEPROM_M500_base->version[2]))) != 0xFF) - { - previous_settings_retrieved = false; - } + //Return false to inform user that eeprom version was changed and firmware is using default hardcoded settings now. + //In case that storing to eeprom was not used yet, do not inform user that hardcoded settings are used. + if (eeprom_is_initialized_block(EEPROM_M500_base->version, sizeof(EEPROM_M500_base->version))) { + return false; + } } - #ifdef EEPROM_CHITCHAT - Config_PrintSettings(); - #endif - return previous_settings_retrieved; + return true; } #endif @@ -323,19 +275,20 @@ void Config_ResetDefault() { memcpy_P(&cs,&default_conf, sizeof(cs)); - // steps per sq second need to be updated to agree with the units per sq second + // steps per sq second need to be updated to agree with the units per sq second reset_acceleration_rates(); #ifdef PIDTEMP updatePID(); -#ifdef PID_ADD_EXTRUSION_RATE - Kc = DEFAULT_Kc; //this is not stored by Config_StoreSettings -#endif//PID_ADD_EXTRUSION_RATE #endif//PIDTEMP +#ifdef TEMP_MODEL + temp_model_reset_settings(); +#endif - calculate_extruder_multipliers(); + calculate_extruder_multipliers(); SERIAL_ECHO_START; SERIAL_ECHOLNPGM("Hardcoded Default Settings Loaded"); } + diff --git a/Firmware/ConfigurationStore.h b/Firmware/ConfigurationStore.h index b9dca3685..f3c61ce8c 100644 --- a/Firmware/ConfigurationStore.h +++ b/Firmware/ConfigurationStore.h @@ -19,7 +19,7 @@ typedef struct unsigned long minsegmenttime; float max_jerk[4]; //!< Jerk is a maximum immediate velocity change. float add_homing[3]; - float zprobe_zoffset; + float zprobe_zoffset; //!< Only used with define ENABLE_AUTO_BED_LEVELING float Kp; float Ki; float Kd; @@ -38,6 +38,13 @@ typedef struct float max_feedrate_silent[4]; //!< max speeds for silent mode unsigned long max_acceleration_units_per_sq_second_silent[4]; unsigned char axis_ustep_resolution[4]; + float travel_acceleration; //!< travel acceleration mm/s^2 + // Arc Interpolation Settings, configurable via M214 + float mm_per_arc_segment; + float min_mm_per_arc_segment; + uint8_t n_arc_correction; // If equal to zero, this is disabled + uint16_t min_arc_segments; // If equal to zero, this is disabled + uint16_t arc_segments_per_sec; // If equal to zero, this is disabled } M500_conf; extern M500_conf cs; @@ -58,8 +65,4 @@ FORCE_INLINE void Config_StoreSettings() {} FORCE_INLINE void Config_RetrieveSettings() { Config_ResetDefault(); Config_PrintSettings(); } #endif -inline uint8_t calibration_status() { return eeprom_read_byte((uint8_t*)EEPROM_CALIBRATION_STATUS); } -inline void calibration_status_store(uint8_t status) { eeprom_update_byte((uint8_t*)EEPROM_CALIBRATION_STATUS, status); } -inline bool calibration_status_pinda() { return eeprom_read_byte((uint8_t*)EEPROM_CALIBRATION_STATUS_PINDA); } - #endif//CONFIG_STORE_H diff --git a/Firmware/Configuration_adv.h b/Firmware/Configuration_adv.h index d25b345c3..d115e558d 100644 --- a/Firmware/Configuration_adv.h +++ b/Firmware/Configuration_adv.h @@ -10,16 +10,6 @@ #endif #define BED_CHECK_INTERVAL 5000 //ms between checks in bang-bang control -#ifdef PIDTEMP - // this adds an experimental additional term to the heating power, proportional to the extrusion speed. - // if Kc is chosen well, the additional required power due to increased melting should be compensated. - #define PID_ADD_EXTRUSION_RATE - #ifdef PID_ADD_EXTRUSION_RATE - #define DEFAULT_Kc (1) //heating power=Kc*(e_speed) - #endif -#endif - - //automatic temperature: The hot end target temperature is calculated by all the buffered lines of gcode. //The maximum buffered steps/sec of the extruder motor are called "se". //You enter the autotemp mode by a M109 S B F @@ -62,8 +52,19 @@ // before setting a PWM value. (Does not work with software PWM for fan on Sanguinololu) #define FAN_KICKSTART_TIME 800 - - +/** + * Auto-report all at once with M155 S C[bitmask] with single timer + * + * bit 0 = Auto-report temperatures + * bit 1 = Auto-report fans + * bit 2 = Auto-report position + * bit 3 = free + * bit 4 = free + * bit 5 = free + * bit 6 = free + * bit 7 = free +*/ +#define AUTO_REPORT //=========================================================================== //=============================Mechanical Settings=========================== @@ -152,7 +153,6 @@ #define Z_HOME_RETRACT_MM 2 //#define QUICK_HOME //if this is defined, if both x and y are to be homed, a diagonal move will be performed initially. -#define AXIS_RELATIVE_MODES {0, 0, 0, 0} #define MAX_STEP_FREQUENCY 40000 // Max step frequency for Ultimaker (5000 pps / half step). Toshiba steppers are 4x slower, but Prusa3D does not use those. //By default pololu step drivers require an active high signal. However, some high power drivers require an active low signal as step. #define INVERT_X_STEP_PIN 0 @@ -221,35 +221,29 @@ * SD sorting uses static allocation (as set by SDSORT_LIMIT), allowing the * compiler to calculate the worst-case usage and throw an error if the SRAM * limit is exceeded. -* -* - SDSORT_USES_RAM provides faster sorting via a static directory buffer. -* - SDSORT_USES_STACK does the same, but uses a local stack-based buffer. -* - SDSORT_CACHE_NAMES will retain the sorted file listing in RAM. (Expensive!) -* - SDSORT_DYNAMIC_RAM only uses RAM when the SD menu is visible. (Use with caution!) */ #define SDCARD_SORT_ALPHA //Alphabetical sorting of SD files menu // SD Card Sorting options - // In current firmware Prusa Firmware version, - // SDSORT_CACHE_NAMES and SDSORT_DYNAMIC_RAM is not supported and must be set to 0. #ifdef SDCARD_SORT_ALPHA #define SD_SORT_TIME 0 #define SD_SORT_ALPHA 1 #define SD_SORT_NONE 2 + #define INSERTSORT + // #define SORTING_DUMP + // #define SORTING_SPEEDTEST #define SDSORT_LIMIT 100 // Maximum number of sorted items (10-256). #define FOLDER_SORTING -1 // -1=above 0=none 1=below - #define SDSORT_GCODE 0 // Allow turning sorting on/off with LCD and M34 g-code. - #define SDSORT_USES_RAM 0 // Pre-allocate a static array for faster pre-sorting. - #define SDSORT_USES_STACK 0 // Prefer the stack for pre-sorting to give back some SRAM. (Negated by next 2 options.) - #define SDSORT_CACHE_NAMES 0 // Keep sorted items in RAM longer for speedy performance. Most expensive option. - #define SDSORT_DYNAMIC_RAM 0 // Use dynamic allocation (within SD menus). Least expensive option. Set SDSORT_LIMIT before use! #endif #if defined(SDCARD_SORT_ALPHA) - #define HAS_FOLDER_SORTING (FOLDER_SORTING || SDSORT_GCODE) + #define HAS_FOLDER_SORTING (FOLDER_SORTING) #endif +// Enabe this option to get a pretty message whenever the endstop gets hit (as in the position at which the endstop got triggered) +//#define VERBOSE_CHECK_HIT_ENDSTOPS + // Enable the option to stop SD printing when hitting and endstops, needs to be enabled from the LCD menu when this option is enabled. //#define ABORT_ON_ENDSTOP_HIT_FEATURE_ENABLED @@ -286,16 +280,17 @@ #define LIN_ADVANCE #ifdef LIN_ADVANCE - #define LIN_ADVANCE_K 0 // Unit: mm compression per 1mm/s extruder speed - //#define LA_NOCOMPAT // Disable Linear Advance 1.0 compatibility - //#define LA_LIVE_K // Allow adjusting K in the Tune menu - //#define LA_DEBUG // If enabled, this will generate debug information output over USB. - //#define LA_DEBUG_LOGIC // @wavexx: setup logic channels for isr debugging + #define LA_K_DEF 0 // Default K factor (Unit: mm compression per 1mm/s extruder speed) + #define LA_K_MAX 10 // Maximum acceptable K factor (exclusive, see notes in planner.cpp:plan_buffer_line) + #define LA_LA10_MIN LA_K_MAX // Lin. Advance 1.0 threshold value (inclusive) + //#define LA_FLOWADJ // Adjust LA along with flow/M221 for uniform width + //#define LA_NOCOMPAT // Disable Linear Advance 1.0 compatibility + //#define LA_LIVE_K // Allow adjusting K in the Tune menu + //#define LA_DEBUG // If enabled, this will generate debug information output over USB. + //#define LA_DEBUG_LOGIC // @wavexx: setup logic channels for isr debugging #endif -// Arc interpretation settings: -#define MM_PER_ARC_SEGMENT 1 -#define N_ARC_CORRECTION 25 +// Arc interpretation settings : Moved to the variant files. const unsigned int dropsegments=5; //everything with less than this number of steps will be ignored as move and joined with the next movement @@ -325,6 +320,11 @@ const unsigned int dropsegments=5; //everything with less than this number of st // Control heater 0 and heater 1 in parallel. //#define HEATERS_PARALLEL +//LCD status clock interval timer to switch between +// remaining print time +// and time to change/pause/interaction +#define CLOCK_INTERVAL_TIME 5 + //=========================================================================== //=============================Buffers ============================ //=========================================================================== @@ -346,6 +346,62 @@ const unsigned int dropsegments=5; //everything with less than this number of st // 2nd and 3rd byte (LSB first) contains a 16bit length of a command including its preceding comments. #define CMDHDRSIZE 3 +/** + * Advanced Pause for Filament Change + * - Adds the G-code M600 Filament Change to initiate a filament change. + * - This feature is required for the default FILAMENT_RUNOUT_SCRIPT. + * + * Requirements: + * - For Filament Change parking enable and configure NOZZLE_PARK_FEATURE. + * - For user interaction enable an LCD display, HOST_PROMPT_SUPPORT, or EMERGENCY_PARSER. + * + * Enable PARK_HEAD_ON_PAUSE to add the G-code M125 Pause and Park. + */ + +#define PAUSE_PARK_RETRACT_FEEDRATE 60 // (mm/s) Initial retract feedrate. +#define PAUSE_PARK_RETRACT_LENGTH 2 // (mm) Initial retract. + // This short retract is done immediately, before parking the nozzle. +#define FILAMENT_CHANGE_UNLOAD_FEEDRATE 10 // (mm/s) Unload filament feedrate. This can be pretty fast. +#define FILAMENT_CHANGE_UNLOAD_ACCEL 25 // (mm/s^2) Lower acceleration may allow a faster feedrate. +#define FILAMENT_CHANGE_UNLOAD_LENGTH 100 // (mm) The length of filament for a complete unload. + // For Bowden, the full length of the tube and nozzle. + // For direct drive, the full length of the nozzle. + // Set to 0 for manual unloading. +#define FILAMENT_CHANGE_SLOW_LOAD_FEEDRATE 6 // (mm/s) Slow move when starting load. +#define FILAMENT_CHANGE_SLOW_LOAD_LENGTH 0 // (mm) Slow length, to allow time to insert material. + // 0 to disable start loading and skip to fast load only +#define FILAMENT_CHANGE_FAST_LOAD_FEEDRATE 6 // (mm/s) Load filament feedrate. This can be pretty fast. +#define FILAMENT_CHANGE_FAST_LOAD_ACCEL 25 // (mm/s^2) Lower acceleration may allow a faster feedrate. +#define FILAMENT_CHANGE_FAST_LOAD_LENGTH 0 // (mm) Load length of filament, from extruder gear to nozzle. + // For Bowden, the full length of the tube and nozzle. + // For direct drive, the full length of the nozzle. +//#define ADVANCED_PAUSE_CONTINUOUS_PURGE // Purge continuously up to the purge length until interrupted. +#define ADVANCED_PAUSE_PURGE_FEEDRATE 3 // (mm/s) Extrude feedrate (after loading). Should be slower than load feedrate. +#define ADVANCED_PAUSE_PURGE_LENGTH 50 // (mm) Length to extrude after loading. + // Set to 0 for manual extrusion. + // Filament can be extruded repeatedly from the Filament Change menu + // until extrusion is consistent, and to purge old filament. +#define ADVANCED_PAUSE_RESUME_PRIME 0 // (mm) Extra distance to prime nozzle after returning from park. +//#define ADVANCED_PAUSE_FANS_PAUSE // Turn off print-cooling fans while the machine is paused. + + // Filament Unload does a Retract, Delay, and Purge first: +#define FILAMENT_UNLOAD_PURGE_RETRACT 13 // (mm) Unload initial retract length. +#define FILAMENT_UNLOAD_PURGE_DELAY 5000 // (ms) Delay for the filament to cool after retract. +#define FILAMENT_UNLOAD_PURGE_LENGTH 8 // (mm) An unretract is done, then this length is purged. +#define FILAMENT_UNLOAD_PURGE_FEEDRATE 25 // (mm/s) feedrate to purge before unload + +#define PAUSE_PARK_NOZZLE_TIMEOUT 45 // (seconds) Time limit before the nozzle is turned off for safety. +#define FILAMENT_CHANGE_ALERT_BEEPS 10 // Number of alert beeps to play when a response is needed. +#define PAUSE_PARK_NO_STEPPER_TIMEOUT // Enable for XYZ steppers to stay powered on during filament change. +//#define FILAMENT_CHANGE_RESUME_ON_INSERT // Automatically continue / load filament when runout sensor is triggered again. +//#define PAUSE_REHEAT_FAST_RESUME // Reduce number of waits by not prompting again post-timeout before continuing. + +//#define PARK_HEAD_ON_PAUSE // Park the nozzle during pause and filament change. +//#define HOME_BEFORE_FILAMENT_CHANGE // If needed, home before parking for filament change + +//#define FILAMENT_LOAD_UNLOAD_GCODES // Add M701/M702 Load/Unload G-codes, plus Load/Unload in the LCD Prepare menu. +//#define FILAMENT_UNLOAD_ALL_EXTRUDERS // Allow M702 to unload all extruders above a minimum target temp (as set by M302) + // Firmware based and LCD controlled retract // M207 and M208 can be used to define parameters for the retraction. @@ -374,6 +430,17 @@ const unsigned int dropsegments=5; //everything with less than this number of st #endif #endif +/** + * Include capabilities in M115 output + */ +#define EXTENDED_CAPABILITIES_REPORT + +/** + * Enable M120/M121 G-code commands + * + */ +//#define M120_M121_ENABLED //Be careful enabling and using these G-code commands. + //=========================================================================== //============================= Define Defines ============================ //=========================================================================== @@ -435,6 +502,10 @@ const unsigned int dropsegments=5; //everything with less than this number of st #undef BED_MINTEMP #undef BED_MAXTEMP #endif +#if TEMP_SENSOR_AMBIENT == 0 + #undef AMBIENT_MINTEMP + #undef AMBIENT_MAXTEMP +#endif #endif //__CONFIGURATION_ADV_H diff --git a/Firmware/Configuration_var.h b/Firmware/Configuration_var.h new file mode 100644 index 000000000..c7dab9d0a --- /dev/null +++ b/Firmware/Configuration_var.h @@ -0,0 +1,10 @@ +// Include the printer's variant configuration header +#pragma once + +// This is set by the cmake build to be able to take control of +// the variant header without breaking existing build mechanisms. +#ifndef CMAKE_CONTROL +#include "Configuration_prusa.h" +#else +#include FW_VARIANT +#endif diff --git a/Firmware/Dcodes.cpp b/Firmware/Dcodes.cpp index 0a5142629..a172e7d60 100644 --- a/Firmware/Dcodes.cpp +++ b/Firmware/Dcodes.cpp @@ -1,5 +1,6 @@ +#include "Marlin.h" #include "Dcodes.h" -//#include "Marlin.h" +#include "Configuration.h" #include "language.h" #include "cmdqueue.h" #include @@ -22,31 +23,28 @@ void print_hex_byte(uint8_t val) print_hex_nibble(val & 15); } -void print_hex_word(uint16_t val) +// debug range address type (fits all SRAM/PROGMEM/XFLASH memory ranges) +#if defined(DEBUG_DCODE6) || defined(DEBUG_DCODES) || defined(XFLASH_DUMP) +#include "xflash.h" +#include "xflash_layout.h" + +#define DADDR_SIZE 32 +typedef uint32_t daddr_t; // XFLASH requires 24 bits +#else +#define DADDR_SIZE 16 +typedef uint16_t daddr_t; +#endif + +void print_hex_word(daddr_t val) { - print_hex_byte(val >> 8); - print_hex_byte(val & 255); +#if DADDR_SIZE > 16 + print_hex_byte((val >> 16) & 0xFF); +#endif + print_hex_byte((val >> 8) & 0xFF); + print_hex_byte(val & 0xFF); } -void print_eeprom(uint16_t address, uint16_t count, uint8_t countperline = 16) -{ - while (count) - { - print_hex_word(address); - putchar(' '); - uint8_t count_line = countperline; - while (count && count_line) - { - putchar(' '); - print_hex_byte(eeprom_read_byte((uint8_t*)address++)); - count_line--; - count--; - } - putchar('\n'); - } -} - -int parse_hex(char* hex, uint8_t* data, int count) +int parse_hex(const char* hex, uint8_t* data, int count) { int parsed = 0; while (*hex) @@ -70,12 +68,16 @@ int parse_hex(char* hex, uint8_t* data, int count) } -void print_mem(uint32_t address, uint16_t count, uint8_t type, uint8_t countperline = 16) +enum class dcode_mem_t:uint8_t { sram, eeprom, progmem, xflash }; + +void print_mem(daddr_t address, daddr_t count, dcode_mem_t type, uint8_t countperline = 16) { +#if defined(DEBUG_DCODE6) || defined(DEBUG_DCODES) || defined(XFLASH_DUMP) + if(type == dcode_mem_t::xflash) + XFLASH_SPI_ENTER(); +#endif while (count) { - if (type == 2) - print_hex_nibble(address >> 16); print_hex_word(address); putchar(' '); uint8_t count_line = countperline; @@ -84,75 +86,98 @@ void print_mem(uint32_t address, uint16_t count, uint8_t type, uint8_t countperl uint8_t data = 0; switch (type) { - case 0: data = *((uint8_t*)address++); break; - case 1: data = eeprom_read_byte((uint8_t*)address++); break; - case 2: data = pgm_read_byte_far((uint8_t*)address++); break; + case dcode_mem_t::sram: data = *((uint8_t*)address); break; + case dcode_mem_t::eeprom: data = eeprom_read_byte((uint8_t*)address); break; + case dcode_mem_t::progmem: break; +#if defined(DEBUG_DCODE6) || defined(DEBUG_DCODES) || defined(XFLASH_DUMP) + case dcode_mem_t::xflash: xflash_rd_data(address, &data, 1); break; +#else + case dcode_mem_t::xflash: break; +#endif } + ++address; putchar(' '); print_hex_byte(data); count_line--; count--; + + // sporadically call manage_heater, but only when interrupts are enabled (meaning + // print_mem is called by D2). Don't do anything otherwise: we are inside a crash + // handler where memory & stack needs to be preserved! + if((SREG & (1 << SREG_I)) && !((uint16_t)count % 8192)) + manage_heater(); } putchar('\n'); } } -#ifdef DEBUG_DCODE3 +// TODO: this only handles SRAM/EEPROM 16bit addresses +void write_mem(uint16_t address, uint16_t count, const uint8_t* data, const dcode_mem_t type) +{ + for (uint16_t i = 0; i < count; i++) + { + switch (type) + { + case dcode_mem_t::sram: *((uint8_t*)address) = data[i]; break; + case dcode_mem_t::eeprom: eeprom_write_byte((uint8_t*)address, data[i]); break; + case dcode_mem_t::progmem: break; + case dcode_mem_t::xflash: break; + } + ++address; + } +} + +void dcode_core(daddr_t addr_start, const daddr_t addr_end, const dcode_mem_t type, + uint8_t dcode, const char* type_desc) +{ + KEEPALIVE_STATE(NOT_BUSY); + DBG(_N("D%d - Read/Write %S\n"), dcode, type_desc); + daddr_t count = -1; // RW the entire space by default + if (code_seen('A')) + addr_start = (strchr_pointer[1] == 'x')?strtol(strchr_pointer + 2, 0, 16):(int)code_value(); + if (code_seen('C')) + count = code_value_long(); + if (addr_start > addr_end) + addr_start = addr_end; + if ((addr_start + count) > addr_end || (addr_start + count) < addr_start) + count = addr_end - addr_start; + if (code_seen('X')) + { + uint8_t data[16]; + count = parse_hex(strchr_pointer + 1, data, 16); + write_mem(addr_start, count, data, type); +#if DADDR_SIZE > 16 + DBG(_N("%lu bytes written to %S at address 0x%04lx\n"), count, type_desc, addr_start); +#else + DBG(_N("%u bytes written to %S at address 0x%08x\n"), count, type_desc, addr_start); +#endif + } + print_mem(addr_start, count, type); +} + +#if defined DEBUG_DCODE3 || defined DEBUG_DCODES #define EEPROM_SIZE 0x1000 /*! - * ### D3 - Read/Write EEPROM D3: Read/Write EEPROM This command can be used without any additional parameters. It will read the entire eeprom. - - D3 [ A | C | X ] - - - `A` - Address (0x0000-0x0fff) - - `C` - Count (0x0001-0x1000) - - `X` - Data - * + #### Usage + + D3 [ A | C | X ] + + #### Parameters + - `A` - Address (x0000-x0fff) + - `C` - Count (1-4096) + - `X` - Data (hex) + + #### Notes + - The hex address needs to be lowercase without the 0 before the x + - Count is decimal + - The hex data needs to be lowercase + */ void dcode_3() { - DBG(_N("D3 - Read/Write EEPROM\n")); - uint16_t address = 0x0000; //default 0x0000 - uint16_t count = EEPROM_SIZE; //default 0x1000 (entire eeprom) - if (code_seen('A')) // Address (0x0000-0x0fff) - address = (strchr_pointer[1] == 'x')?strtol(strchr_pointer + 2, 0, 16):(int)code_value(); - if (code_seen('C')) // Count (0x0001-0x1000) - count = (int)code_value(); - address &= 0x1fff; - if (count > EEPROM_SIZE) count = EEPROM_SIZE; - if ((address + count) > EEPROM_SIZE) count = EEPROM_SIZE - address; - if (code_seen('X')) // Data - { - uint8_t data[16]; - count = parse_hex(strchr_pointer + 1, data, 16); - if (count > 0) - { - for (uint16_t i = 0; i < count; i++) - eeprom_write_byte((uint8_t*)(address + i), data[i]); - printf_P(_N("%d bytes written to EEPROM at address 0x%04x"), count, address); - putchar('\n'); - } - else - count = 0; - } - print_mem(address, count, 1); -/* while (count) - { - print_hex_word(address); - putchar(' '); - uint8_t countperline = 16; - while (count && countperline) - { - uint8_t data = eeprom_read_byte((uint8_t*)address++); - putchar(' '); - print_hex_byte(data); - countperline--; - count--; - } - putchar('\n'); - }*/ + dcode_core(0, EEPROM_SIZE, dcode_mem_t::eeprom, 3, _N("EEPROM")); } #endif //DEBUG_DCODE3 @@ -166,20 +191,6 @@ void dcode_3() #include "bootapp.h" #if 0 -#define FLASHSIZE 0x40000 - -#define RAMSIZE 0x2000 -#define boot_src_addr (*((uint32_t*)(RAMSIZE - 16))) -#define boot_dst_addr (*((uint32_t*)(RAMSIZE - 12))) -#define boot_copy_size (*((uint16_t*)(RAMSIZE - 8))) -#define boot_reserved (*((uint8_t*)(RAMSIZE - 6))) -#define boot_app_flags (*((uint8_t*)(RAMSIZE - 5))) -#define boot_app_magic (*((uint32_t*)(RAMSIZE - 4))) -#define BOOT_APP_FLG_ERASE 0x01 -#define BOOT_APP_FLG_COPY 0x02 -#define BOOT_APP_FLG_FLASH 0x04 - -extern uint8_t fsensor_log; extern float current_temperature_pinda; extern float axis_steps_per_unit[NUM_AXIS]; @@ -198,7 +209,7 @@ extern float axis_steps_per_unit[NUM_AXIS]; */ void dcode__1() { - printf_P(PSTR("D-1 - Endless loop\n")); + DBG(_N("D-1 - Endless loop\n")); // cli(); while (1); } @@ -206,13 +217,13 @@ void dcode__1() #ifdef DEBUG_DCODES /*! - * ### D0 - Reset D0: Reset - - D0 [ B ] - - - `B` - Bootloader - * + #### Usage + + D0 [ B ] + + #### Parameters + - `B` - Bootloader */ void dcode_0() { @@ -220,9 +231,7 @@ void dcode_0() LOG("D0 - Reset\n"); if (code_seen('B')) //bootloader { - cli(); - wdt_enable(WDTO_15MS); - while(1); + softReset(); } else //reset { @@ -246,77 +255,48 @@ void dcode_1() cli(); for (int i = 0; i < 8192; i++) eeprom_write_byte((unsigned char*)i, (unsigned char)0xff); - wdt_enable(WDTO_15MS); - while(1); + softReset(); } +#endif +#if defined DEBUG_DCODE2 || defined DEBUG_DCODES /*! - * ### D2 - Read/Write RAM D3: Read/Write RAM This command can be used without any additional parameters. It will read the entire RAM. - - D3 [ A | C | X ] - - - `A` - Address (0x0000-0x1fff) - - `C` - Count (0x0001-0x2000) - - `X` - Data - * + #### Usage + + D2 [ A | C | X ] + + #### Parameters + - `A` - Address (x0000-x21ff) + - `C` - Count (1-8704) + - `X` - Data + + #### Notes + - The hex address needs to be lowercase without the 0 before the x + - Count is decimal + - The hex data needs to be lowercase + */ void dcode_2() { - LOG("D2 - Read/Write RAM\n"); - uint16_t address = 0x0000; //default 0x0000 - uint16_t count = 0x2000; //default 0x2000 (entire ram) - if (code_seen('A')) // Address (0x0000-0x1fff) - address = (strchr_pointer[1] == 'x')?strtol(strchr_pointer + 2, 0, 16):(int)code_value(); - if (code_seen('C')) // Count (0x0001-0x2000) - count = (int)code_value(); - address &= 0x1fff; - if (count > 0x2000) count = 0x2000; - if ((address + count) > 0x2000) count = 0x2000 - address; - if (code_seen('X')) // Data - { - uint8_t data[16]; - count = parse_hex(strchr_pointer + 1, data, 16); - if (count > 0) - { - for (uint16_t i = 0; i < count; i++) - *((uint8_t*)(address + i)) = data[i]; - LOG("%d bytes written to RAM at address %04x", count, address); - } - else - count = 0; - } - print_mem(address, count, 0); -/* while (count) - { - print_hex_word(address); - putchar(' '); - uint8_t countperline = 16; - while (count && countperline) - { - uint8_t data = *((uint8_t*)address++); - putchar(' '); - print_hex_byte(data); - countperline--; - count--; - } - putchar('\n'); - }*/ + dcode_core(RAMSTART, RAMEND+1, dcode_mem_t::sram, 2, _N("SRAM")); } +#endif +#ifdef DEBUG_DCODES /*! - * - ### D4 - Read/Write PIN D4: Read/Write PIN + ### D4 - Read/Write PIN D4: Read/Write PIN To read the digital value of a pin you need only to define the pin number. - - D4 [ P | F | V ] - - - `P` - Pin (0-255) - - `F` - Function in/out (0/1) - - `V` - Value (0/1) - * + #### Usage + + D4 [ P | F | V ] + + #### Parameters + - `P` - Pin (0-255) + - `F` - Function in/out (0/1) + - `V` - Value (0/1) */ void dcode_4() { @@ -348,24 +328,30 @@ void dcode_4() } #endif //DEBUG_DCODES -#ifdef DEBUG_DCODE5 +#if defined DEBUG_DCODE5 || defined DEBUG_DCODES /*! - * ### D5 - Read/Write FLASH D5: Read/Write Flash This command can be used without any additional parameters. It will read the 1kb FLASH. - - D3 [ A | C | X | E ] - - - `A` - Address (0x00000-0x3ffff) - - `C` - Count (0x0001-0x2000) - - `X` - Data - - `E` - Erase - * - */ + #### Usage + + D5 [ A | C | X | E ] + + #### Parameters + - `A` - Address (x00000-x3ffff) + - `C` - Count (1-8192) + - `X` - Data (hex) + - `E` - Erase + + #### Notes + - The hex address needs to be lowercase without the 0 before the x + - Count is decimal + - The hex data needs to be lowercase + + */ void dcode_5() { - printf_P(PSTR("D5 - Read/Write FLASH\n")); + puts_P(PSTR("D5 - Read/Write FLASH")); uint32_t address = 0x0000; //default 0x0000 uint16_t count = 0x0400; //default 0x0400 (1kb block) if (code_seen('A')) // Address (0x00000-0x3ffff) @@ -402,8 +388,7 @@ void dcode_5() boot_dst_addr = (uint32_t)address; boot_src_addr = (uint32_t)(&data); bootapp_print_vars(); - wdt_enable(WDTO_15MS); - while(1); + softReset(); } while (count) { @@ -424,27 +409,36 @@ void dcode_5() } #endif //DEBUG_DCODE5 -#ifdef DEBUG_DCODES - +#if defined(XFLASH) && (defined DEBUG_DCODE6 || defined DEBUG_DCODES) /*! - * ### D6 - Read/Write external FLASH D6: Read/Write external Flash - - Reserved - * - */ + This command can be used without any additional parameters. It will read the entire XFLASH. + #### Usage + + D6 [ A | C | X ] + + #### Parameters + - `A` - Address (x0000-x3ffff) + - `C` - Count (1-262144) + - `X` - Data + + #### Notes + - The hex address needs to be lowercase without the 0 before the x + - Count is decimal + - The hex data needs to be lowercase + - Writing is currently not implemented + */ void dcode_6() { - LOG("D6 - Read/Write external FLASH\n"); + dcode_core(0x0, XFLASH_SIZE, dcode_mem_t::xflash, 6, _N("XFLASH")); } +#endif +#ifdef DEBUG_DCODES /*! - * ### D7 - Read/Write Bootloader D7: Read/Write Bootloader - Reserved - * - */ + */ void dcode_7() { LOG("D7 - Read/Write Bootloader\n"); @@ -455,26 +449,25 @@ void dcode_7() boot_copy_size = (uint16_t)0xc00; boot_src_addr = (uint32_t)0x0003e400; boot_dst_addr = (uint32_t)0x0003f400; - wdt_enable(WDTO_15MS); - while(1); + softReset(); */ } /*! - * ### D8 - Read/Write PINDA D8: Read/Write PINDA - - D8 [ ? | ! | P | Z ] - - - `?` - Read PINDA temperature shift values - - `!` - Reset PINDA temperature shift values to default - - `P` - Pinda temperature [C] - - `Z` - Z Offset [mm] - * + #### Usage + + D8 [ ? | ! | P | Z ] + + #### Parameters + - `?` - Read PINDA temperature shift values + - `!` - Reset PINDA temperature shift values to default + - `P` - Pinda temperature [C] + - `Z` - Z Offset [mm] */ void dcode_8() { - printf_P(PSTR("D8 - Read/Write PINDA\n")); + puts_P(PSTR("D8 - Read/Write PINDA")); uint8_t cal_status = calibration_status_pinda(); float temp_pinda = current_temperature_pinda; float offset_z = temp_compensation_pinda_thermistor_offset(temp_pinda); @@ -514,21 +507,21 @@ void dcode_8() } /*! - * ### D9 - Read ADC D9: Read ADC - - D9 [ I | V ] - - - `I` - ADC channel index - - `0` - Heater 0 temperature - - `1` - Heater 1 temperature - - `2` - Bed temperature - - `3` - PINDA temperature - - `4` - PWR voltage - - `5` - Ambient temperature - - `6` - BED voltage - - `V` Value to be written as simulated - * + #### Usage + + D9 [ I | V ] + + #### Parameters + - `I` - ADC channel index + - `0` - Heater 0 temperature + - `1` - Heater 1 temperature + - `2` - Bed temperature + - `3` - PINDA temperature + - `4` - PWR voltage + - `5` - Ambient temperature + - `6` - BED voltage + - `V` Value to be written as simulated */ const char* dcode_9_ADC_name(uint8_t i) { @@ -545,26 +538,20 @@ const char* dcode_9_ADC_name(uint8_t i) return 0; } -#ifdef AMBIENT_THERMISTOR -extern int current_temperature_raw_ambient; -#endif //AMBIENT_THERMISTOR - -#ifdef VOLT_PWR_PIN -extern int current_voltage_raw_pwr; -#endif //VOLT_PWR_PIN - -#ifdef VOLT_BED_PIN -extern int current_voltage_raw_bed; -#endif //VOLT_BED_PIN - uint16_t dcode_9_ADC_val(uint8_t i) { switch (i) { +#ifdef SHOW_TEMP_ADC_VALUES case 0: return current_temperature_raw[0]; +#endif //SHOW_TEMP_ADC_VALUES case 1: return 0; +#ifdef SHOW_TEMP_ADC_VALUES case 2: return current_temperature_bed_raw; +#endif //SHOW_TEMP_ADC_VALUES +#ifdef PINDA_THERMISTOR case 3: return current_temperature_raw_pinda; +#endif //PINDA_THERMISTOR #ifdef VOLT_PWR_PIN case 4: return current_voltage_raw_pwr; #endif //VOLT_PWR_PIN @@ -580,12 +567,13 @@ uint16_t dcode_9_ADC_val(uint8_t i) void dcode_9() { - printf_P(PSTR("D9 - Read/Write ADC\n")); + puts_P(PSTR("D9 - Read/Write ADC")); if ((strchr_pointer[1+1] == '?') || (strchr_pointer[1+1] == 0)) { for (uint8_t i = 0; i < ADC_CHAN_CNT; i++) printf_P(PSTR("\tADC%d=%4d\t(%S)\n"), i, dcode_9_ADC_val(i) >> 4, dcode_9_ADC_name(i)); } +#if 0 else { uint8_t index = 0xff; @@ -596,77 +584,189 @@ void dcode_9() if (code_seen('V')) // value to be written as simulated { adc_sim_mask |= (1 << index); - adc_values[index] = (((int)code_value()) << 4); + adc_values[index] = ((uint16_t)code_value_short() << 4); printf_P(PSTR("ADC%d=%4d\n"), index, adc_values[index] >> 4); } } } +#endif } /*! - * ### D10 - Set XYZ calibration = OK D10: Set XYZ calibration = OK - - * - */ + */ void dcode_10() {//Tell the printer that XYZ calibration went OK LOG("D10 - XYZ calibration = OK\n"); - calibration_status_store(CALIBRATION_STATUS_LIVE_ADJUST); + calibration_status_set(CALIBRATION_STATUS_XYZ); } /*! - * ### D12 - Time D12: Time - - * - */ + Writes the current time in the log file. + */ + void dcode_12() {//Time LOG("D12 - Time\n"); } +#ifdef HEATBED_ANALYSIS + /*! + ### D80 - Bed check D80: Bed check + This command will log data to SD card file "mesh.txt". + #### Usage + + D80 [ E | F | G | H | I | J ] + + #### Parameters + - `E` - Dimension X (default 40) + - `F` - Dimention Y (default 40) + - `G` - Points X (default 40) + - `H` - Points Y (default 40) + - `I` - Offset X (default 74) + - `J` - Offset Y (default 34) + */ +void dcode_80() +{ + float dimension_x = 40; + float dimension_y = 40; + int points_x = 40; + int points_y = 40; + float offset_x = 74; + float offset_y = 33; + + if (code_seen('E')) dimension_x = code_value(); + if (code_seen('F')) dimension_y = code_value(); + if (code_seen('G')) {points_x = code_value(); } + if (code_seen('H')) {points_y = code_value(); } + if (code_seen('I')) {offset_x = code_value(); } + if (code_seen('J')) {offset_y = code_value(); } + printf_P(PSTR("DIM X: %f\n"), dimension_x); + printf_P(PSTR("DIM Y: %f\n"), dimension_y); + printf_P(PSTR("POINTS X: %d\n"), points_x); + printf_P(PSTR("POINTS Y: %d\n"), points_y); + printf_P(PSTR("OFFSET X: %f\n"), offset_x); + printf_P(PSTR("OFFSET Y: %f\n"), offset_y); + bed_check(dimension_x,dimension_y,points_x,points_y,offset_x,offset_y); +} + + + /*! + ### D81 - Bed analysis D80: Bed analysis + This command will log data to SD card file "wldsd.txt". + #### Usage + + D81 [ E | F | G | H | I | J ] + + #### Parameters + - `E` - Dimension X (default 40) + - `F` - Dimention Y (default 40) + - `G` - Points X (default 40) + - `H` - Points Y (default 40) + - `I` - Offset X (default 74) + - `J` - Offset Y (default 34) + */ +void dcode_81() +{ + float dimension_x = 40; + float dimension_y = 40; + int points_x = 40; + int points_y = 40; + float offset_x = 74; + float offset_y = 33; + + if (code_seen('E')) dimension_x = code_value(); + if (code_seen('F')) dimension_y = code_value(); + if (code_seen("G")) { strchr_pointer+=1; points_x = code_value(); } + if (code_seen("H")) { strchr_pointer+=1; points_y = code_value(); } + if (code_seen("I")) { strchr_pointer+=1; offset_x = code_value(); } + if (code_seen("J")) { strchr_pointer+=1; offset_y = code_value(); } + + bed_analysis(dimension_x,dimension_y,points_x,points_y,offset_x,offset_y); + +} + +#endif //HEATBED_ANALYSIS + + /*! + ### D106 - Print measured fan speed for different pwm values D106: Print measured fan speed for different pwm values + */ +void dcode_106() +{ + for (int i = 255; i > 0; i = i - 5) { + fanSpeed = i; + //delay_keep_alive(2000); + for (int j = 0; j < 100; j++) { + delay_keep_alive(100); + } + printf_P(_N("%d: %d\n"), i, fan_speed[1]); + } +} #ifdef TMC2130 #include "planner.h" #include "tmc2130.h" extern void st_synchronize(); -/** - * @brief D2130 Trinamic stepper controller - * D2130[subcommand][value] - * * Axis - * * * 'X' - * * * 'Y' - * * * 'Z' - * * * 'E' - * * command - * * * '0' current off - * * * '1' current on - * * * '+' single step - * * * * value sereval steps - * * * '-' dtto oposite direction - * * * '?' read register - * * * * "mres" - * * * * "step" - * * * * "mscnt" - * * * * "mscuract" - * * * * "wave" - * * * '!' set register - * * * * "mres" - * * * * "step" - * * * * "wave" - * * * * *0, 180..250 meaning: off, 0.9..1.25, recommended value is 1.1 - * * * '@' home calibrate axis - * - * Example: - * D2130E?wave //print extruder microstep linearity compensation curve - * D2130E!wave0 //disable extruder linearity compensation curve, (sine curve is used) - * D2130E!wave220 // (sin(x))^1.1 extruder microstep compensation curve used - */ + /*! + ### D2130 - Trinamic stepper controller D2130: Trinamic stepper controller + @todo Please review by owner of the code. RepRap Wiki Gcode needs to be updated after review of owner as well. + + #### Usage + + D2130 [ Axis | Command | Subcommand | Value ] + + #### Parameters + - Axis + - `X` - X stepper driver + - `Y` - Y stepper driver + - `Z` - Z stepper driver + - `E` - Extruder stepper driver + - Commands + - `0` - Current off + - `1` - Current on + - `+` - Single step + - `-` - Single step oposite direction + - `NNN` - Value sereval steps + - `?` - Read register + - Subcommands for read register + - `mres` - Micro step resolution. More information in datasheet '5.5.2 CHOPCONF – Chopper Configuration' + - `step` - Step + - `mscnt` - Microstep counter. More information in datasheet '5.5 Motor Driver Registers' + - `mscuract` - Actual microstep current for motor. More information in datasheet '5.5 Motor Driver Registers' + - `wave` - Microstep linearity compensation curve + - `!` - Set register + - Subcommands for set register + - `mres` - Micro step resolution + - `step` - Step + - `wave` - Microstep linearity compensation curve + - Values for set register + - `0, 180 --> 250` - Off + - `0.9 --> 1.25` - Valid values (recommended is 1.1) + - `@` - Home calibrate axis + + Examples: + + D2130E?wave + + Print extruder microstep linearity compensation curve + + D2130E!wave0 + + Disable extruder linearity compensation curve, (sine curve is used) + + D2130E!wave220 + + (sin(x))^1.1 extruder microstep compensation curve used + + Notes: + For more information see https://www.trinamic.com/fileadmin/assets/Products/ICs_Documents/TMC2130_datasheet.pdf + * + */ void dcode_2130() { - printf_P(PSTR("D2130 - TMC2130\n")); + puts_P(PSTR("D2130 - TMC2130")); uint8_t axis = 0xff; switch (strchr_pointer[1+4]) { @@ -765,20 +865,19 @@ void dcode_2130() } #endif //TMC2130 -#ifdef PAT9125 +#if defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125) /*! - * ### D9125 - PAT9125 filament sensor D9125: PAT9125 filament sensor - - D9125 [ ? | ! | R | X | Y | L ] - - - `?` - Print values - - `!` - Print values - - `R` - Resolution. Not active in code - - `X` - X values - - `Y` - Y values - - `L` - Activate filament sensor log - * + #### Usage + + D9125 [ ? | ! | R | X | Y | L ] + + #### Parameters + - `?` - Print values + - `!` - Print values + - `R` - Resolution. Not active in code + - `X` - X values + - `Y` - Y values */ void dcode_9125() { @@ -812,13 +911,102 @@ void dcode_9125() pat9125_y = (int)code_value(); LOG("pat9125_y=%d\n", pat9125_y); } - if (code_seen('L')) - { - fsensor_log = (int)code_value(); - LOG("fsensor_log=%d\n", fsensor_log); - } } -#endif //PAT9125 - +#endif //defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125) #endif //DEBUG_DCODES + +#ifdef XFLASH_DUMP +#include "xflash_dump.h" + +void dcode_20() +{ + if(code_seen('E')) + xfdump_full_dump_and_reset(); + else + { + unsigned long ts = _millis(); + xfdump_dump(); + ts = _millis() - ts; + DBG(_N("dump completed in %lums\n"), ts); + } +} + +void dcode_21() +{ + if(!xfdump_check_state()) + DBG(_N("no dump available\n")); + else + { + KEEPALIVE_STATE(NOT_BUSY); + DBG(_N("D21 - read crash dump\n")); + print_mem(DUMP_OFFSET, sizeof(dump_t), dcode_mem_t::xflash); + } +} + +void dcode_22() +{ + if(!xfdump_check_state()) + DBG(_N("no dump available\n")); + else + { + xfdump_reset(); + DBG(_N("dump cleared\n")); + } +} +#endif + +#ifdef EMERGENCY_SERIAL_DUMP +#include "asm.h" +#include "xflash_dump.h" + +bool emergency_serial_dump = false; + +void dcode_23() +{ + if(code_seen('E')) + serial_dump_and_reset(dump_crash_reason::manual); + else + { + emergency_serial_dump = !code_seen('R'); + SERIAL_ECHOPGM("serial dump "); + SERIAL_ECHOLNRPGM(emergency_serial_dump? _N("enabled"): _N("disabled")); + } +} + +void __attribute__((noinline)) serial_dump_and_reset(dump_crash_reason reason) +{ + uint16_t sp; + uint32_t pc; + + // we're being called from a live state, so shut off interrupts ... + cli(); + + // sample SP/PC + sp = SP; + pc = GETPC(); + + // extend WDT long enough to allow writing the entire stream + wdt_enable(WDTO_8S); + + // ... and heaters + WRITE(FAN_PIN, HIGH); + disable_heater(); + + // this function can also be called from within a corrupted state, so not use + // printf family of functions that use the heap or grow the stack. + SERIAL_ECHOLNPGM("D23 - emergency serial dump"); + SERIAL_ECHOPGM("error: "); + MYSERIAL.print((uint8_t)reason, DEC); + SERIAL_ECHOPGM(" 0x"); + MYSERIAL.print(pc, HEX); + SERIAL_ECHOPGM(" 0x"); + MYSERIAL.println(sp, HEX); + + print_mem(0, RAMEND+1, dcode_mem_t::sram); + SERIAL_ECHOLNRPGM(MSG_OK); + + // reset soon + softReset(); +} +#endif diff --git a/Firmware/Dcodes.h b/Firmware/Dcodes.h index eaf849edb..c2762830d 100644 --- a/Firmware/Dcodes.h +++ b/Firmware/Dcodes.h @@ -2,27 +2,60 @@ #define DCODES_H extern void dcode__1(); //D-1 - Endless loop (to simulate deadlock) - extern void dcode_0(); //D0 - Reset extern void dcode_1(); //D1 - Clear EEPROM + +#if defined DEBUG_DCODE2 || defined DEBUG_DCODES extern void dcode_2(); //D2 - Read/Write RAM +#endif + +#if defined DEBUG_DCODE3 || defined DEBUG_DCODES extern void dcode_3(); //D3 - Read/Write EEPROM +#endif //DEBUG_DCODE3 + extern void dcode_4(); //D4 - Read/Write PIN + +#if defined DEBUG_DCODE5 || defined DEBUG_DCODES extern void dcode_5(); //D5 - Read/Write FLASH +#endif //DEBUG_DCODE5 + +#if defined DEBUG_DCODE6 || defined DEBUG_DCODES extern void dcode_6(); //D6 - Read/Write external FLASH +#endif + extern void dcode_7(); //D7 - Read/Write Bootloader extern void dcode_8(); //D8 - Read/Write PINDA extern void dcode_9(); //D9 - Read/Write ADC (Write=enable simulated, Read=disable simulated) - extern void dcode_10(); //D10 - XYZ calibration = OK +extern void dcode_12(); //D12 - Log time. Writes the current time in the log file. + +#ifdef XFLASH_DUMP +extern void dcode_20(); //D20 - Generate an offline crash dump +extern void dcode_21(); //D21 - Print crash dump to serial +extern void dcode_22(); //D22 - Clear crash dump state +#endif + +#ifdef EMERGENCY_SERIAL_DUMP +#include "xflash_dump.h" +extern void dcode_23(); //D23 - Request/generate an online serial crash dump +extern bool emergency_serial_dump; //emergency dump enabled flag +extern void serial_dump_and_reset(dump_crash_reason); +#endif + +#ifdef HEATBED_ANALYSIS +extern void dcode_80(); //D80 - Bed check. This command will log data to SD card file "mesh.txt". +extern void dcode_81(); //D81 - Bed analysis. This command will log data to SD card file "wldsd.txt". +#endif //HEATBED_ANALYSIS + + extern void dcode_106(); //D106 - Print measured fan speed for different pwm values #ifdef TMC2130 -extern void dcode_2130(); //D2130 - TMC2130 + extern void dcode_2130(); //D2130 - TMC2130 #endif //TMC2130 -#ifdef PAT9125 -extern void dcode_9125(); //D9125 - PAT9125 -#endif //PAT9125 +#if defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125) + extern void dcode_9125(); //D9125 - PAT9125 +#endif //defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125) #endif //DCODES_H diff --git a/Firmware/Filament_sensor.cpp b/Firmware/Filament_sensor.cpp new file mode 100644 index 000000000..dbccf2760 --- /dev/null +++ b/Firmware/Filament_sensor.cpp @@ -0,0 +1,531 @@ +#include +#include +#include + +#include "Filament_sensor.h" +#include "Timer.h" +#include "cardreader.h" +#include "eeprom.h" +#include "menu.h" +#include "planner.h" +#include "temperature.h" +#include "ultralcd.h" + +#ifdef FILAMENT_SENSOR +FSensorBlockRunout::FSensorBlockRunout() { + fsensor.setRunoutEnabled(false); //suppress filament runouts while loading filament. + fsensor.setAutoLoadEnabled(false); //suppress filament autoloads while loading filament. +#if (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125) + fsensor.setJamDetectionEnabled(false); //suppress filament jam detection while loading filament. +#endif //(FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125) +// SERIAL_ECHOLNPGM("FSBlockRunout"); +} + +FSensorBlockRunout::~FSensorBlockRunout() { + fsensor.settings_init(); // restore filament runout state. +// SERIAL_ECHOLNPGM("FSUnBlockRunout"); +} + +# if FILAMENT_SENSOR_TYPE == FSENSOR_IR +IR_sensor fsensor; +# elif FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG +IR_sensor_analog fsensor; +# elif FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125 +PAT9125_sensor fsensor; +# endif + +#else // FILAMENT_SENSOR +FSensorBlockRunout::FSensorBlockRunout() { } +FSensorBlockRunout::~FSensorBlockRunout() { } +#endif // FILAMENT_SENSOR + +void Filament_sensor::setEnabled(bool enabled) { + eeprom_update_byte((uint8_t *)EEPROM_FSENSOR, enabled); + if (enabled) { + fsensor.init(); + } else { + fsensor.deinit(); + } +} + +void Filament_sensor::setAutoLoadEnabled(bool state, bool updateEEPROM) { + autoLoadEnabled = state; + if (updateEEPROM) { + eeprom_update_byte((uint8_t *)EEPROM_FSENS_AUTOLOAD_ENABLED, state); + } +} + +void Filament_sensor::setRunoutEnabled(bool state, bool updateEEPROM) { + runoutEnabled = state; + if (updateEEPROM) { + eeprom_update_byte((uint8_t *)EEPROM_FSENS_RUNOUT_ENABLED, state); + } +} + +void Filament_sensor::setActionOnError(SensorActionOnError state, bool updateEEPROM) { + sensorActionOnError = state; + if (updateEEPROM) { + eeprom_update_byte((uint8_t *)EEPROM_FSENSOR_ACTION_NA, (uint8_t)state); + } +} + +void Filament_sensor::settings_init_common() { + bool enabled = eeprom_read_byte((uint8_t *)EEPROM_FSENSOR); + if ((state != State::disabled) != enabled) { + state = enabled ? State::initializing : State::disabled; + } + + autoLoadEnabled = eeprom_read_byte((uint8_t *)EEPROM_FSENS_AUTOLOAD_ENABLED); + runoutEnabled = eeprom_read_byte((uint8_t *)EEPROM_FSENS_RUNOUT_ENABLED); + sensorActionOnError = (SensorActionOnError)eeprom_read_byte((uint8_t *)EEPROM_FSENSOR_ACTION_NA); + if (sensorActionOnError == SensorActionOnError::_Undef) { + sensorActionOnError = SensorActionOnError::_Continue; + } +} + +bool Filament_sensor::checkFilamentEvents() { + if (state != State::ready) + return false; + if (eventBlankingTimer.running() && !eventBlankingTimer.expired(100)) { // event blanking for 100ms + return false; + } + + bool newFilamentPresent = fsensor.getFilamentPresent(); + if (oldFilamentPresent != newFilamentPresent) { + oldFilamentPresent = newFilamentPresent; + eventBlankingTimer.start(); + if (newFilamentPresent) { // filament insertion +// puts_P(PSTR("filament inserted")); + triggerFilamentInserted(); + postponedLoadEvent = true; + } else { // filament removal +// puts_P(PSTR("filament removed")); + triggerFilamentRemoved(); + } + return true; + } + return false; +} + +void Filament_sensor::triggerFilamentInserted() { + if (autoLoadEnabled + && (eFilamentAction == FilamentAction::None) + && (! MMU2::mmu2.Enabled() ) // quick and dirty hack to prevent spurious runouts while the MMU is in charge + && !( + moves_planned() != 0 + || IS_SD_PRINTING + || usb_timer.running() + || (lcd_commands_type == LcdCommands::Layer1Cal) + || eeprom_read_byte((uint8_t *)EEPROM_WIZARD_ACTIVE) + ) + ) { + filAutoLoad(); + } +} + +void Filament_sensor::triggerFilamentRemoved() { +// SERIAL_ECHOLNPGM("triggerFilamentRemoved"); + if (runoutEnabled + && (! MMU2::mmu2.Enabled() ) // quick and dirty hack to prevent spurious runouts just before the toolchange + && (eFilamentAction == FilamentAction::None) + && !saved_printing + && ( + moves_planned() != 0 + || IS_SD_PRINTING + || usb_timer.running() + || (lcd_commands_type == LcdCommands::Layer1Cal) + || eeprom_read_byte((uint8_t *)EEPROM_WIZARD_ACTIVE) + ) + ){ +// SERIAL_ECHOPGM("runoutEnabled="); SERIAL_ECHOLN((int)runoutEnabled); +// SERIAL_ECHOPGM("eFilamentAction="); SERIAL_ECHOLN((int)eFilamentAction); +// SERIAL_ECHOPGM("saved_printing="); SERIAL_ECHOLN((int)saved_printing); + filRunout(); + } +} + +void Filament_sensor::filAutoLoad() { + eFilamentAction = FilamentAction::AutoLoad; + if (target_temperature[0] >= EXTRUDE_MINTEMP) { + bFilamentPreheatState = true; + menu_submenu(mFilamentItemForce); + } else { + menu_submenu(lcd_generic_preheat_menu); + lcd_timeoutToStatus.start(); + } +} + +void Filament_sensor::filRunout() { +// SERIAL_ECHOLNPGM("filRunout"); + runoutEnabled = false; + autoLoadEnabled = false; + stop_and_save_print_to_ram(0, 0); + restore_print_from_ram_and_continue(0); + eeprom_increment_byte((uint8_t *)EEPROM_FERROR_COUNT); + eeprom_increment_word((uint16_t *)EEPROM_FERROR_COUNT_TOT); + enquecommand_front_P((PSTR("M600"))); +} + +void Filament_sensor::triggerError() { + state = State::error; + + /// some message, idk + ; // +} + +#if (FILAMENT_SENSOR_TYPE == FSENSOR_IR) || (FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG) +void IR_sensor::init() { + if (state == State::error) { + fsensor.deinit(); // deinit first if there was an error. + } +// puts_P(PSTR("fsensor::init()")); + SET_INPUT(IR_SENSOR_PIN); // input mode + WRITE(IR_SENSOR_PIN, 1); // pullup + settings_init(); // also sets the state to State::initializing +} + +void IR_sensor::deinit() { +// puts_P(PSTR("fsensor::deinit()")); + SET_INPUT(IR_SENSOR_PIN); // input mode + WRITE(IR_SENSOR_PIN, 0); // no pullup + state = State::disabled; +} + +bool IR_sensor::update() { + switch (state) { + case State::initializing: + state = State::ready; // the IR sensor gets ready instantly as it's just a gpio read operation. + // initialize the current filament state so that we don't create a switching event right after the sensor is ready. + oldFilamentPresent = fsensor.getFilamentPresent(); + [[fallthrough]]; + case State::ready: { + postponedLoadEvent = false; + return checkFilamentEvents(); + } break; + case State::disabled: + case State::error: + default: + return false; + } + return false; +} + + + +#ifdef FSENSOR_PROBING +bool IR_sensor::probeOtherType() { return pat9125_probe(); } +#endif + +void IR_sensor::settings_init() { Filament_sensor::settings_init_common(); } + +#if (FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG) +void IR_sensor_analog::init() { + IR_sensor::init(); + IR_sensor::settings_init(); + sensorRevision = (SensorRevision)eeprom_read_byte((uint8_t *)EEPROM_FSENSOR_PCB); +} + +bool IR_sensor_analog::update() { + bool event = IR_sensor::update(); + if (state == State::ready) { + if (getVoltReady()) { + clearVoltReady(); + uint16_t volt = getVoltRaw(); +// printf_P(PSTR("newVoltRaw:%u\n"), volt / OVERSAMPLENR); + + // detect min-max, some long term sliding window for filtration may be added + // avoiding floating point operations, thus computing in raw + if (volt > maxVolt) { + maxVolt = volt; + } else if (volt < minVolt) { + minVolt = volt; + } + //! The trouble is, I can hold the filament in the hole in such a way, that it creates the exact voltage + //! to be detected as the new fsensor + //! We can either fake it by extending the detection window to a looooong time + //! or do some other countermeasures + + //! what we want to detect: + //! if minvolt gets below ~0.3V, it means there is an old fsensor + //! if maxvolt gets above 4.6V, it means we either have an old fsensor or broken cables/fsensor + //! So I'm waiting for a situation, when minVolt gets to range <0, 1.5> and maxVolt gets into range <3.0, 5> + //! If and only if minVolt is in range <0.3, 1.5> and maxVolt is in range <3.0, 4.6>, I'm considering a situation with the new fsensor + if (minVolt >= IRsensor_Ldiode_TRESHOLD && minVolt <= IRsensor_Lmax_TRESHOLD && maxVolt >= IRsensor_Hmin_TRESHOLD && + maxVolt <= IRsensor_Hopen_TRESHOLD) { + IR_ANALOG_Check(SensorRevision::_Old, SensorRevision::_Rev04); + } + //! If and only if minVolt is in range <0.0, 0.3> and maxVolt is in range <4.6, 5.0V>, I'm considering a situation with the old fsensor + //! Note, we are not relying on one voltage here - getting just +5V can mean an old fsensor or a broken new sensor - that's why + //! we need to have both voltages detected correctly to allow switching back to the old fsensor. + else if (minVolt < IRsensor_Ldiode_TRESHOLD && maxVolt > IRsensor_Hopen_TRESHOLD && maxVolt <= IRsensor_VMax_TRESHOLD) { + IR_ANALOG_Check(SensorRevision::_Rev04, SensorRevision::_Old); + } + + if (!checkVoltage(volt)) { + triggerError(); + } + } + } + + ; // + + return event; +} + +void IR_sensor_analog::voltUpdate(uint16_t raw) { // to be called from the ADC ISR when a cycle is finished + voltRaw = raw; + voltReady = true; +} + +uint16_t IR_sensor_analog::getVoltRaw() { + uint16_t ret; + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ret = voltRaw; } + return ret; +} + +const char *IR_sensor_analog::getIRVersionText() { + switch (sensorRevision) { + case SensorRevision::_Old: + return _T(MSG_IR_03_OR_OLDER); + case SensorRevision::_Rev04: + return _T(MSG_IR_04_OR_NEWER); + default: + return _T(MSG_IR_UNKNOWN); + } +} + +void IR_sensor_analog::setSensorRevision(SensorRevision rev, bool updateEEPROM) { + sensorRevision = rev; + if (updateEEPROM) { + eeprom_update_byte((uint8_t *)EEPROM_FSENSOR_PCB, (uint8_t)rev); + } +} + +bool IR_sensor_analog::checkVoltage(uint16_t raw) { + if (IRsensor_Lmax_TRESHOLD <= raw && raw <= IRsensor_Hmin_TRESHOLD) { + /// If the voltage is in forbidden range, the fsensor is ok, but the lever is mounted improperly. + /// Or the user is so creative so that he can hold a piece of fillament in the hole in such a genius way, + /// that the IR fsensor reading is within 1.5 and 3V ... this would have been highly unusual + /// and would have been considered more like a sabotage than normal printer operation + if (voltageErrorCnt++ > 4) { + puts_P(PSTR("fsensor in forbidden range 1.5-3V - check sensor")); + return false; + } + } else { + voltageErrorCnt = 0; + } + if (sensorRevision == SensorRevision::_Rev04) { + /// newer IR sensor cannot normally produce 4.6-5V, this is considered a failure/bad mount + if (IRsensor_Hopen_TRESHOLD <= raw && raw <= IRsensor_VMax_TRESHOLD) { + puts_P(PSTR("fsensor v0.4 in fault range 4.6-5V - unconnected")); + return false; + } + /// newer IR sensor cannot normally produce 0-0.3V, this is considered a failure +#if 0 // Disabled as it has to be decided if we gonna use this or not. + if(IRsensor_Hopen_TRESHOLD <= raw && raw <= IRsensor_VMax_TRESHOLD) { + puts_P(PSTR("fsensor v0.4 in fault range 0.0-0.3V - wrong IR sensor")); + return false; + } +#endif + } + /// If IR sensor is "uknown state" and filament is not loaded > 1.5V return false +#if 0 +#error "I really think this code can't be enabled anymore because we are constantly checking this voltage." + if((sensorRevision == SensorRevision::_Undef) && (raw > IRsensor_Lmax_TRESHOLD)) { + puts_P(PSTR("Unknown IR sensor version and no filament loaded detected.")); + return false; + } +#endif + // otherwise the IR fsensor is considered working correctly + return true; +} + +bool IR_sensor_analog::getVoltReady() const { + bool ret; + ATOMIC_BLOCK(ATOMIC_RESTORESTATE){ ret = voltReady; } + return ret; +} + +void IR_sensor_analog::clearVoltReady(){ + ATOMIC_BLOCK(ATOMIC_RESTORESTATE){ voltReady = false; } +} + +void IR_sensor_analog::IR_ANALOG_Check(SensorRevision isVersion, SensorRevision switchTo) { + bool bTemp = (!CHECK_ALL_HEATERS); + bTemp = bTemp && (menu_menu == lcd_status_screen); + bTemp = bTemp && ((sensorRevision == isVersion) || (sensorRevision == SensorRevision::_Undef)); + bTemp = bTemp && (state == State::ready); + if (bTemp) { + nFSCheckCount++; + if (nFSCheckCount > FS_CHECK_COUNT) { + nFSCheckCount = 0; // not necessary + setSensorRevision(switchTo, true); + printf_IRSensorAnalogBoardChange(); + switch (switchTo) { + case SensorRevision::_Old: + lcd_setstatuspgm(_T(MSG_IR_03_OR_OLDER)); + break; + case SensorRevision::_Rev04: + lcd_setstatuspgm(_T(MSG_IR_04_OR_NEWER)); + break; + default: + break; + } + } + } else { + nFSCheckCount = 0; + } +} +#endif //(FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG) +#endif //(FILAMENT_SENSOR_TYPE == FSENSOR_IR) || (FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG) + +#if (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125) +void PAT9125_sensor::init() { + if (state == State::error) { + deinit(); // deinit first if there was an error. + } +// puts_P(PSTR("fsensor::init()")); + + settings_init(); // also sets the state to State::initializing + + calcChunkSteps(cs.axis_steps_per_unit[E_AXIS]); // for jam detection + + if (!pat9125_init()) { + deinit(); + triggerError(); + ; // + } +#ifdef IR_SENSOR_PIN + else if (!READ(IR_SENSOR_PIN)) { + ; // MK3 fw on MK3S printer + } +#endif // IR_SENSOR_PIN +} + +void PAT9125_sensor::deinit() { +// puts_P(PSTR("fsensor::deinit()")); + ; // + state = State::disabled; + filter = 0; +} + +bool PAT9125_sensor::update() { + switch (state) { + case State::initializing: + if (!updatePAT9125()) { + break; // still not stable. Stay in the initialization state. + } + oldFilamentPresent = + getFilamentPresent(); // initialize the current filament state so that we don't create a switching event right after the sensor is ready. + oldPos = pat9125_y; + state = State::ready; + break; + case State::ready: { + updatePAT9125(); + postponedLoadEvent = false; + bool event = checkFilamentEvents(); + + ; // + + return event; + } break; + case State::disabled: + case State::error: + default: + return false; + } + return false; +} + +#ifdef FSENSOR_PROBING +bool PAT9125_sensor::probeOtherType() { + SET_INPUT(IR_SENSOR_PIN); // input mode + WRITE(IR_SENSOR_PIN, 1); // pullup + _delay_us(100); // wait for the pullup to pull the line high (might be needed, not really sure. The internal pullups are quite weak and there might be a + // long wire attached). + bool fsensorDetected = !READ(IR_SENSOR_PIN); + WRITE(IR_SENSOR_PIN, 0); // no pullup + return fsensorDetected; +} +#endif + +void PAT9125_sensor::setJamDetectionEnabled(bool state, bool updateEEPROM) { + jamDetection = state; + oldPos = pat9125_y; + resetStepCount(); + jamErrCnt = 0; + if (updateEEPROM) { + eeprom_update_byte((uint8_t *)EEPROM_FSENSOR_JAM_DETECTION, state); + } +} + +void PAT9125_sensor::settings_init() { +// puts_P(PSTR("settings_init")); + Filament_sensor::settings_init_common(); + setJamDetectionEnabled(eeprom_read_byte((uint8_t *)EEPROM_FSENSOR_JAM_DETECTION)); +} + +int16_t PAT9125_sensor::getStepCount() { + int16_t ret; + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ret = stepCount; } + return ret; +} + +void PAT9125_sensor::resetStepCount() { + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { stepCount = 0; } +} + +void PAT9125_sensor::filJam() { + runoutEnabled = false; + autoLoadEnabled = false; + jamDetection = false; + stop_and_save_print_to_ram(0, 0); + restore_print_from_ram_and_continue(0); + eeprom_increment_byte((uint8_t *)EEPROM_FERROR_COUNT); + eeprom_increment_word((uint16_t *)EEPROM_FERROR_COUNT_TOT); + enquecommand_front_P((PSTR("M600"))); +} + +bool PAT9125_sensor::updatePAT9125() { + if (jamDetection) { + int16_t _stepCount = getStepCount(); + if (abs(_stepCount) >= chunkSteps) { // end of chunk. Check distance + resetStepCount(); + if (!pat9125_update()) { // get up to date data. reinit on error. + init(); // try to reinit. + } + bool fsDir = (pat9125_y - oldPos) > 0; + bool stDir = _stepCount > 0; + if (fsDir != stDir) { + jamErrCnt++; + } else if (jamErrCnt) { + jamErrCnt--; + } + oldPos = pat9125_y; + } + if (jamErrCnt > 10) { + jamErrCnt = 0; + filJam(); + } + } + + if (!pollingTimer.running() || pollingTimer.expired(pollingPeriod)) { + pollingTimer.start(); + if (!pat9125_update()) { + init(); // try to reinit. + } + + bool present = (pat9125_s < 17) || (pat9125_s >= 17 && pat9125_b >= 50); + if (present != filterFilPresent) { + filter++; + } else if (filter) { + filter--; + } + if (filter >= filterCnt) { + filter = 0; + filterFilPresent = present; + } + } + return (filter == 0); // return stability +} +#endif // #if (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125) diff --git a/Firmware/Filament_sensor.h b/Firmware/Filament_sensor.h new file mode 100644 index 000000000..e5c488a3a --- /dev/null +++ b/Firmware/Filament_sensor.h @@ -0,0 +1,214 @@ +#pragma once +#include + +#include "cmdqueue.h" +#include "pins.h" +#include "fastio.h" +#include "adc.h" +#include "pat9125.h" + +#define FSENSOR_IR 1 +#define FSENSOR_IR_ANALOG 2 +#define FSENSOR_PAT9125 3 + +/// Can be used to block printer's filament sensor handling - to avoid errorneous injecting of M600 +/// while doing a toolchange with the MMU +/// In case of "no filament sensor" these methods default to an empty implementation +class FSensorBlockRunout { +public: + FSensorBlockRunout(); + ~FSensorBlockRunout(); +}; + +/// Base class Filament sensor +/// +/// Ideally, there could have been a nice class hierarchy of filament sensor types with common functionality +/// extracted into this base class. +/// But: +/// - virtual methods take more space +/// - we don't need to switch among different filament sensors at runtime +/// Therefore the class hierarchy carefully avoids using virtual methods and doesn't look too fancy. +#ifdef FILAMENT_SENSOR +class Filament_sensor { +public: + enum class State : uint8_t { + disabled = 0, + initializing, + ready, + error, + }; + + enum class SensorActionOnError : uint8_t { + _Continue = 0, + _Pause = 1, + _Undef = EEPROM_EMPTY_VALUE + }; + + static void setEnabled(bool enabled); + + void setAutoLoadEnabled(bool state, bool updateEEPROM = false); + bool getAutoLoadEnabled() const { return autoLoadEnabled; } + + void setRunoutEnabled(bool state, bool updateEEPROM = false); + bool getRunoutEnabled() const { return runoutEnabled; } + + void setActionOnError(SensorActionOnError state, bool updateEEPROM = false); + SensorActionOnError getActionOnError() const { return sensorActionOnError; } + + bool getFilamentLoadEvent() const { return postponedLoadEvent; } + + bool isError() const { return state == State::error; } + bool isReady() const { return state == State::ready; } + bool isEnabled() const { return state != State::disabled; } + +protected: + void settings_init_common(); + + bool checkFilamentEvents(); + + void triggerFilamentInserted(); + + void triggerFilamentRemoved(); + + static void filAutoLoad(); + + void filRunout(); + + void triggerError(); + + State state; + bool autoLoadEnabled; + bool runoutEnabled; + bool oldFilamentPresent; //for creating filament presence switching events. + bool postponedLoadEvent; //this event lasts exactly one update cycle. It is long enough to be able to do polling for load event. + ShortTimer eventBlankingTimer; + SensorActionOnError sensorActionOnError; +}; + +#if (FILAMENT_SENSOR_TYPE == FSENSOR_IR) || (FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG) +class IR_sensor: public Filament_sensor { +public: + void init(); + void deinit(); + bool update(); + bool getFilamentPresent() const { return !READ(IR_SENSOR_PIN); } +#ifdef FSENSOR_PROBING + static bool probeOtherType(); //checks if the wrong fsensor type is detected. +#endif + void settings_init(); +}; + +#if (FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG) +constexpr static uint16_t Voltage2Raw(float V) { + return (V * 1023 * OVERSAMPLENR / VOLT_DIV_REF ) + 0.5F; +} +constexpr static float Raw2Voltage(uint16_t raw) { + return VOLT_DIV_REF * (raw / (1023.F * OVERSAMPLENR)); +} + +class IR_sensor_analog: public IR_sensor { +public: + void init(); + bool update(); + void voltUpdate(uint16_t raw); + + uint16_t __attribute__((noinline)) getVoltRaw(); + + enum class SensorRevision : uint8_t { + _Old = 0, + _Rev04 = 1, + _Undef = EEPROM_EMPTY_VALUE + }; + + SensorRevision getSensorRevision() const { return sensorRevision; } + + const char* __attribute__((noinline)) getIRVersionText(); + + void setSensorRevision(SensorRevision rev, bool updateEEPROM = false); + + constexpr static uint16_t IRsensor_Ldiode_TRESHOLD = Voltage2Raw(0.3F); // ~0.3V, raw value=982 + constexpr static uint16_t IRsensor_Lmax_TRESHOLD = Voltage2Raw(1.5F); // ~1.5V (0.3*Vcc), raw value=4910 + constexpr static uint16_t IRsensor_Hmin_TRESHOLD = Voltage2Raw(3.0F); // ~3.0V (0.6*Vcc), raw value=9821 + constexpr static uint16_t IRsensor_Hopen_TRESHOLD = Voltage2Raw(4.6F); // ~4.6V (N.C. @ Ru~20-50k, Rd'=56k, Ru'=10k), raw value=15059 + constexpr static uint16_t IRsensor_VMax_TRESHOLD = Voltage2Raw(5.F); // ~5V, raw value=16368 + +private: + SensorRevision sensorRevision; + + bool voltReady; // set by the adc ISR, therefore avoid accessing the variable directly but use getVoltReady() + bool getVoltReady()const; + void clearVoltReady(); + + uint16_t voltRaw; // set by the adc ISR, therefore avoid accessing the variable directly but use getVoltRaw() + bool checkVoltage(uint16_t raw); + + uint16_t minVolt = Voltage2Raw(6.F); + uint16_t maxVolt = 0; + uint16_t nFSCheckCount; + uint8_t voltageErrorCnt; + + static constexpr uint16_t FS_CHECK_COUNT = 4; + /// Switching mechanism of the fsensor type. + /// Called from 2 spots which have a very similar behavior + /// 1: SensorRevision::_Old -> SensorRevision::_Rev04 and print _i("FS v0.4 or newer") + /// 2: SensorRevision::_Rev04 -> sensorRevision=SensorRevision::_Old and print _i("FS v0.3 or older") + void IR_ANALOG_Check(SensorRevision isVersion, SensorRevision switchTo); +}; +#endif //(FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG) +#endif //(FILAMENT_SENSOR_TYPE == FSENSOR_IR) || (FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG) + +#if (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125) +class PAT9125_sensor: public Filament_sensor { +public: + void init(); + void deinit(); + bool update(); + bool getFilamentPresent() const { return filterFilPresent; } +#ifdef FSENSOR_PROBING + bool probeOtherType(); //checks if the wrong fsensor type is detected. +#endif + + void setJamDetectionEnabled(bool state, bool updateEEPROM = false); + bool getJamDetectionEnabled() const { return jamDetection; } + + void stStep(bool rev) { //from stepper isr + stepCount += rev ? -1 : 1; + } + + void settings_init(); +private: + static constexpr uint16_t pollingPeriod = 10; //[ms] + static constexpr uint8_t filterCnt = 5; //how many checks need to be done in order to determine the filament presence precisely. + ShortTimer pollingTimer; + uint8_t filter; + uint8_t filterFilPresent; + + bool jamDetection; + int16_t oldPos; + int16_t stepCount; + int16_t chunkSteps; + uint8_t jamErrCnt; + + constexpr void calcChunkSteps(float u) { + chunkSteps = (int16_t)(1.25 * u); //[mm] + } + + int16_t getStepCount(); + + void resetStepCount(); + + void filJam(); + + bool updatePAT9125(); +}; +#endif //(FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125) + +#if FILAMENT_SENSOR_TYPE == FSENSOR_IR +extern IR_sensor fsensor; +#elif FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG +extern IR_sensor_analog fsensor; +#elif FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125 +extern PAT9125_sensor fsensor; +#endif + +#endif //FILAMENT_SENSOR diff --git a/Firmware/Marlin.h b/Firmware/Marlin.h index 19e5df57b..ac55a7874 100755 --- a/Firmware/Marlin.h +++ b/Firmware/Marlin.h @@ -4,7 +4,7 @@ #ifndef MARLIN_H #define MARLIN_H -#define FORCE_INLINE __attribute__((always_inline)) inline +#include "macros.h" #include #include @@ -21,6 +21,7 @@ #include "Configuration.h" #include "pins.h" #include "Timer.h" +#include "mmu2.h" extern uint8_t mbl_z_probe_nr; #ifndef AT90USB @@ -79,9 +80,9 @@ extern FILE _uartout; #define SERIAL_PROTOCOL_F(x,y) (MYSERIAL.print(x,y)) #define SERIAL_PROTOCOLPGM(x) (serialprintPGM(PSTR(x))) #define SERIAL_PROTOCOLRPGM(x) (serialprintPGM((x))) -#define SERIAL_PROTOCOLLN(x) (MYSERIAL.println(x)/*,MYSERIAL.write('\n')*/) -#define SERIAL_PROTOCOLLNPGM(x) (serialprintPGM(PSTR(x)),MYSERIAL.println()/*write('\n')*/) -#define SERIAL_PROTOCOLLNRPGM(x) (serialprintPGM((x)),MYSERIAL.println()/*write('\n')*/) +#define SERIAL_PROTOCOLLN(x) (MYSERIAL.println(x)) +#define SERIAL_PROTOCOLLNPGM(x) (serialprintlnPGM(PSTR(x))) +#define SERIAL_PROTOCOLLNRPGM(x) (serialprintlnPGM((x))) extern const char errormagic[] PROGMEM; @@ -115,8 +116,10 @@ void serial_echopair_P(const char *s_P, unsigned long v); // I'd rather skip a few CPU ticks than 5.5KB (!!) of FLASH void serialprintPGM(const char *str); +//The "ln" variant of the function above. +void serialprintlnPGM(const char *str); + bool is_buffer_empty(); -void get_command(); void process_commands(); void ramming(); @@ -146,40 +149,39 @@ void manage_inactivity(bool ignore_stepper_queue=false); #if defined(Z_ENABLE_PIN) && Z_ENABLE_PIN > -1 #if defined(Z_AXIS_ALWAYS_ON) #ifdef Z_DUAL_STEPPER_DRIVERS - #define enable_z() { WRITE(Z_ENABLE_PIN, Z_ENABLE_ON); WRITE(Z2_ENABLE_PIN, Z_ENABLE_ON); } - #define disable_z() { WRITE(Z_ENABLE_PIN,!Z_ENABLE_ON); WRITE(Z2_ENABLE_PIN,!Z_ENABLE_ON); axis_known_position[Z_AXIS] = false; } + #define poweron_z() { WRITE(Z_ENABLE_PIN, Z_ENABLE_ON); WRITE(Z2_ENABLE_PIN, Z_ENABLE_ON); } + #define poweroff_z() { WRITE(Z_ENABLE_PIN,!Z_ENABLE_ON); WRITE(Z2_ENABLE_PIN,!Z_ENABLE_ON); axis_known_position[Z_AXIS] = false; } #else - #define enable_z() WRITE(Z_ENABLE_PIN, Z_ENABLE_ON) - #define disable_z() {} + #define poweron_z() WRITE(Z_ENABLE_PIN, Z_ENABLE_ON) + #define poweroff_z() {} #endif #else #ifdef Z_DUAL_STEPPER_DRIVERS - #define enable_z() { WRITE(Z_ENABLE_PIN, Z_ENABLE_ON); WRITE(Z2_ENABLE_PIN, Z_ENABLE_ON); } - #define disable_z() { WRITE(Z_ENABLE_PIN,!Z_ENABLE_ON); WRITE(Z2_ENABLE_PIN,!Z_ENABLE_ON); axis_known_position[Z_AXIS] = false; } + #define poweron_z() { WRITE(Z_ENABLE_PIN, Z_ENABLE_ON); WRITE(Z2_ENABLE_PIN, Z_ENABLE_ON); } + #define poweroff_z() { WRITE(Z_ENABLE_PIN,!Z_ENABLE_ON); WRITE(Z2_ENABLE_PIN,!Z_ENABLE_ON); axis_known_position[Z_AXIS] = false; } #else - #define enable_z() WRITE(Z_ENABLE_PIN, Z_ENABLE_ON) - #define disable_z() { WRITE(Z_ENABLE_PIN,!Z_ENABLE_ON); axis_known_position[Z_AXIS] = false; } + #define poweron_z() WRITE(Z_ENABLE_PIN, Z_ENABLE_ON) + #define poweroff_z() { WRITE(Z_ENABLE_PIN,!Z_ENABLE_ON); axis_known_position[Z_AXIS] = false; } #endif #endif #else - #define enable_z() {} - #define disable_z() {} + #define poweron_z() {} + #define poweroff_z() {} #endif -#ifdef PSU_Delta +#ifndef PSU_Delta + #define enable_z() poweron_z() + #define disable_z() poweroff_z() +#else void init_force_z(); void check_force_z(); - #undef disable_z - #define disable_z() disable_force_z() - void disable_force_z(); - #undef enable_z - #define enable_z() enable_force_z() void enable_force_z(); + void disable_force_z(); + #define enable_z() enable_force_z() + #define disable_z() disable_force_z() #endif // PSU_Delta - - //#if defined(Z_ENABLE_PIN) && Z_ENABLE_PIN > -1 //#ifdef Z_DUAL_STEPPER_DRIVERS //#define enable_z() { WRITE(Z_ENABLE_PIN, Z_ENABLE_ON); WRITE(Z2_ENABLE_PIN, Z_ENABLE_ON); } @@ -219,9 +221,6 @@ void manage_inactivity(bool ignore_stepper_queue=false); #endif -#define FARM_FILAMENT_COLOR_NONE 99; - - enum AxisEnum {X_AXIS=0, Y_AXIS=1, Z_AXIS=2, E_AXIS=3, X_HEAD=4, Y_HEAD=5}; #define X_AXIS_MASK 1 #define Y_AXIS_MASK 2 @@ -235,130 +234,90 @@ void FlushSerialRequestResend(); void ClearToSend(); void update_currents(); -void get_coordinates(); -void prepare_move(); void kill(const char *full_screen_message = NULL, unsigned char id = 0); -void Stop(); +void finishAndDisableSteppers(); -bool IsStopped(); - -//put an ASCII command at the end of the current buffer. -void enquecommand(const char *cmd, bool from_progmem = false); +void UnconditionalStop(); // Stop heaters, motion and clear current print status +void ThermalStop(bool allow_pause = false); // Emergency stop used by overtemp functions which allows + // recovery (with pause=true) +bool IsStopped(); // Returns true if the print has been stopped //put an ASCII command at the end of the current buffer, read from flash #define enquecommand_P(cmd) enquecommand(cmd, true) -//put an ASCII command at the begin of the current buffer -void enquecommand_front(const char *cmd, bool from_progmem = false); - //put an ASCII command at the begin of the current buffer, read from flash #define enquecommand_front_P(cmd) enquecommand_front(cmd, true) -void repeatcommand_front(); - -// Remove all lines from the command queue. -void cmdqueue_reset(); - -void prepare_arc_move(char isclockwise); void clamp_to_software_endstops(float target[3]); void refresh_cmd_timeout(void); -// Timer counter, incremented by the 1ms Arduino timer. -// The standard Arduino timer() function returns this value atomically -// by disabling / enabling interrupts. This is costly, if the interrupts are known -// to be disabled. -#ifdef SYSTEM_TIMER_2 -extern volatile unsigned long timer2_millis; -#else //SYSTEM_TIMER_2 -extern volatile unsigned long timer0_millis; -#endif //SYSTEM_TIMER_2 - -// An unsynchronized equivalent to a standard Arduino _millis() function. -// To be used inside an interrupt routine. - -FORCE_INLINE unsigned long millis_nc() { -#ifdef SYSTEM_TIMER_2 - return timer2_millis; -#else //SYSTEM_TIMER_2 - return timer0_millis; -#endif //SYSTEM_TIMER_2 -} - #ifdef FAST_PWM_FAN void setPwmFrequency(uint8_t pin, int val); #endif -#ifndef CRITICAL_SECTION_START - #define CRITICAL_SECTION_START unsigned char _sreg = SREG; cli(); - #define CRITICAL_SECTION_END SREG = _sreg; -#endif //CRITICAL_SECTION_START +enum class HeatingStatus : uint8_t +{ + NO_HEATING = 0, + EXTRUDER_HEATING = 1, + EXTRUDER_HEATING_COMPLETE = 2, + BED_HEATING = 3, + BED_HEATING_COMPLETE = 4, +}; + +extern HeatingStatus heating_status; extern bool fans_check_enabled; extern float homing_feedrate[]; -extern bool axis_relative_modes[]; +extern uint8_t axis_relative_modes; extern float feedrate; extern int feedmultiply; extern int extrudemultiply; // Sets extrude multiply factor (in percent) for all extruders -extern int extruder_multiply[EXTRUDERS]; // sets extrude multiply factor (in percent) for each extruder individually -extern float volumetric_multiplier[EXTRUDERS]; // reciprocal of cross-sectional area of filament (in square millimeters), stored this way to reduce computational burden in planner +extern float extruder_multiplier[EXTRUDERS]; // reciprocal of cross-sectional area of filament (in square millimeters), stored this way to reduce computational burden in planner extern float current_position[NUM_AXIS] ; extern float destination[NUM_AXIS] ; extern float min_pos[3]; extern float max_pos[3]; extern bool axis_known_position[3]; extern int fanSpeed; -extern int8_t lcd_change_fil_state; +extern uint8_t newFanSpeed; extern float default_retraction; +void get_coordinates(); +void prepare_move(uint16_t start_segment_idx = 0); +void prepare_arc_move(bool isclockwise, uint16_t start_segment_idx = 0); +uint16_t restore_interrupted_gcode(); + #ifdef TMC2130 -void homeaxis(int axis, uint8_t cnt = 1, uint8_t* pstep = 0); +void homeaxis(uint8_t axis, uint8_t cnt = 1, uint8_t* pstep = 0); #else -void homeaxis(int axis, uint8_t cnt = 1); +void homeaxis(uint8_t axis, uint8_t cnt = 1); #endif //TMC2130 - -#ifdef FAN_SOFT_PWM -extern unsigned char fanSpeedSoftPwm; -#endif - #ifdef FWRETRACT extern bool retracted[EXTRUDERS]; extern float retract_length_swap; extern float retract_recover_length_swap; #endif - extern uint8_t host_keepalive_interval; extern unsigned long starttime; extern unsigned long stoptime; -extern int bowden_length[4]; -extern bool is_usb_printing; +extern ShortTimer usb_timer; +extern bool processing_tcode; extern bool homing_flag; -extern bool temp_cal_active; extern bool loading_flag; -extern unsigned int usb_printing_counter; - -extern unsigned long kicktime; - extern unsigned long total_filament_used; void save_statistics(unsigned long _total_filament_used, unsigned long _total_print_time); -extern unsigned int heating_status; -extern unsigned int status_number; -extern unsigned int heating_status_counter; -extern char snmm_filaments_used; -extern unsigned long PingTime; -extern unsigned long NcTime; -extern bool no_response; -extern uint8_t important_status; -extern uint8_t saved_filament_type; +extern uint8_t heating_status_counter; extern bool fan_state[2]; extern int fan_edge_counter[2]; extern int fan_speed[2]; -// Handling multiple extruders pins -extern uint8_t active_extruder; +// Active extruder becomes a #define to make the whole firmware compilable. +// We may even remove the references to it wherever possible in the future +#define active_extruder 0 //Long pause extern unsigned long pause_time; @@ -366,13 +325,7 @@ extern unsigned long start_pause_print; extern unsigned long t_fan_rising_edge; extern bool mesh_bed_leveling_flag; -extern bool mesh_bed_run_from_menu; -extern bool sortAlpha; - -extern char dir_names[3][9]; - -extern int8_t lcd_change_fil_state; // save/restore printing extern bool saved_printing; extern uint8_t saved_printing_type; @@ -380,14 +333,17 @@ extern uint8_t saved_printing_type; #define PRINTING_TYPE_USB 1 #define PRINTING_TYPE_NONE 2 -//save/restore printing in case that mmu is not responding -extern bool mmu_print_saved; +extern float saved_extruder_temperature; //!< Active extruder temperature +extern float saved_bed_temperature; //!< Bed temperature +extern int saved_fan_speed; //!< Print fan speed //estimated time to end of the print extern uint8_t print_percent_done_normal; -extern uint16_t print_time_remaining_normal; extern uint8_t print_percent_done_silent; +extern uint16_t print_time_remaining_normal; extern uint16_t print_time_remaining_silent; +extern uint16_t print_time_to_change_normal; +extern uint16_t print_time_to_change_silent; #define PRINT_TIME_REMAINING_INIT 0xffff @@ -396,8 +352,9 @@ extern uint16_t gcode_in_progress; extern LongTimer safetyTimer; -#define PRINT_PERCENT_DONE_INIT 0xff -#define PRINTER_ACTIVE (IS_SD_PRINTING || is_usb_printing || isPrintPaused || (custom_message_type == CustomMsg::TempCal) || saved_printing || (lcd_commands_type == LcdCommands::Layer1Cal) || mmu_print_saved) +#define PRINT_PERCENT_DONE_INIT 0xff + +extern bool printer_active(); //! Beware - mcode_in_progress is set as soon as the command gets really processed, //! which is not the same as posting the M600 command into the command queue @@ -406,7 +363,7 @@ extern LongTimer safetyTimer; //! Instead, the fsensor uses another state variable :( , which is set to true, when the M600 command is enqued //! and is reset to false when the fsensor returns into its filament runout finished handler //! I'd normally change this macro, but who knows what would happen in the MMU :) -#define CHECK_FSENSOR ((IS_SD_PRINTING || is_usb_printing) && (mcode_in_progress != 600) && !saved_printing && e_active()) +bool check_fsensor(); extern void calculate_extruder_multipliers(); @@ -426,7 +383,9 @@ void bed_analysis(float x_dimension, float y_dimension, int x_points_num, int y_ void bed_check(float x_dimension, float y_dimension, int x_points_num, int y_points_num, float shift_x, float shift_y); #endif //HEATBED_ANALYSIS float temp_comp_interpolation(float temperature); +#if 0 void show_fw_version_warnings(); +#endif uint8_t check_printer_version(); #ifdef PINDA_THERMISTOR @@ -445,9 +404,8 @@ void setup_uvlo_interrupt(); void setup_fan_interrupt(); #endif -//extern void recover_machine_state_after_power_panic(); -extern void recover_machine_state_after_power_panic(bool bTiny); -extern void restore_print_from_eeprom(); +extern bool recover_machine_state_after_power_panic(); +extern void restore_print_from_eeprom(bool mbl_was_active); extern void position_menu(); extern void print_world_coordinates(); @@ -455,12 +413,12 @@ extern void print_physical_coordinates(); extern void print_mesh_bed_leveling_table(); extern void stop_and_save_print_to_ram(float z_move, float e_move); +void restore_extruder_temperature_from_ram(); extern void restore_print_from_ram_and_continue(float e_move); extern void cancel_saved_printing(); //estimated time to end of the print -extern uint16_t print_time_remaining(); extern uint8_t calc_percent_done(); @@ -483,6 +441,7 @@ extern uint8_t calc_percent_done(); #define KEEPALIVE_STATE(n) do { busy_state = n;} while (0) extern void host_keepalive(); +extern void host_autoreport(); //extern MarlinBusyState busy_state; extern int8_t busy_state; @@ -500,12 +459,13 @@ void force_high_power_mode(bool start_high_power_section); bool gcode_M45(bool onlyZ, int8_t verbosity_level); void gcode_M114(); -void gcode_M701(); +#if (defined(FANCHECK) && (((defined(TACH_0) && (TACH_0 >-1)) || (defined(TACH_1) && (TACH_1 > -1))))) +void gcode_M123(); +#endif //FANCHECK and TACH_0 and TACH_1 +void gcode_M701(float fastLoadLength, uint8_t mmuSlotIndex); #define UVLO !(PINE & (1<<4)) -void proc_commands(); - void M600_load_filament(); void M600_load_filament_movements(); @@ -513,6 +473,12 @@ void M600_wait_for_user(float HotendTempBckp); void M600_check_state(float nozzle_temp); void load_filament_final_feed(); void marlin_wait_for_click(); -void raise_z_above(float target, bool plan=true); +float raise_z(float delta); +void raise_z_above(float target); + +extern "C" void softReset(); +void stack_error(); + +extern uint32_t IP_address; #endif diff --git a/Firmware/MarlinSerial.cpp b/Firmware/MarlinSerial.cpp index d3ffdfbee..7eccd8487 100644 --- a/Firmware/MarlinSerial.cpp +++ b/Firmware/MarlinSerial.cpp @@ -30,7 +30,7 @@ uint8_t selectedSerialPort = 0; // this is so I can support Attiny series and any other chip without a UART #if defined(UBRRH) || defined(UBRR0H) || defined(UBRR1H) || defined(UBRR2H) || defined(UBRR3H) -#if UART_PRESENT(SERIAL_PORT) +#ifdef HAS_UART ring_buffer rx_buffer = { { 0 }, 0, 0 }; #endif @@ -75,7 +75,7 @@ ISR(M_USARTx_RX_vect) #endif //DEBUG_DUMP_TO_2ND_SERIAL } } -#ifndef SNMM + ISR(USART1_RX_vect) { // Test for a framing error. @@ -97,7 +97,6 @@ ISR(USART1_RX_vect) } } #endif -#endif // Public Methods ////////////////////////////////////////////////////////////// @@ -131,8 +130,6 @@ void MarlinSerial::begin(long baud) sbi(M_UCSRxB, M_TXENx); sbi(M_UCSRxB, M_RXCIEx); -#ifndef SNMM - if (selectedSerialPort == 1) { //set up also the second serial port if (useU2X) { UCSR1A = 1 << U2X1; @@ -148,9 +145,8 @@ void MarlinSerial::begin(long baud) sbi(UCSR1B, RXEN1); sbi(UCSR1B, TXEN1); - sbi(UCSR1B, RXCIE1); + sbi(UCSR1B, RXCIE1); } -#endif } void MarlinSerial::end() @@ -159,11 +155,9 @@ void MarlinSerial::end() cbi(M_UCSRxB, M_TXENx); cbi(M_UCSRxB, M_RXCIEx); -#ifndef SNMM cbi(UCSR1B, RXEN1); cbi(UCSR1B, TXEN1); cbi(UCSR1B, RXCIE1); -#endif } @@ -255,7 +249,7 @@ void MarlinSerial::print(double n, int digits) void MarlinSerial::println(void) { - print('\r'); + // print('\r'); print('\n'); } @@ -318,7 +312,7 @@ void MarlinSerial::println(double n, int digits) void MarlinSerial::printNumber(unsigned long n, uint8_t base) { unsigned char buf[8 * sizeof(long)]; // Assumes 8-bit chars. - unsigned long i = 0; + uint8_t i = 0; if (n == 0) { print('0'); @@ -359,7 +353,7 @@ void MarlinSerial::printFloat(double number, uint8_t digits) // Print the decimal point, but only if there are digits beyond if (digits > 0) - print("."); + print('.'); // Extract digits from the remainder one at a time while (digits-- > 0) diff --git a/Firmware/MarlinSerial.h b/Firmware/MarlinSerial.h index 27e722bc0..61da27603 100644 --- a/Firmware/MarlinSerial.h +++ b/Firmware/MarlinSerial.h @@ -28,17 +28,19 @@ #endif // The presence of the UBRRH register is used to detect a UART. -#define UART_PRESENT(port) ((port == 0 && (defined(UBRRH) || defined(UBRR0H))) || \ - (port == 1 && defined(UBRR1H)) || (port == 2 && defined(UBRR2H)) || \ - (port == 3 && defined(UBRR3H))) +#if ((SERIAL_PORT == 0 && (defined(UBRRH) || defined(UBRR0H))) || \ + (SERIAL_PORT == 1 && defined(UBRR1H)) || \ + (SERIAL_PORT == 2 && defined(UBRR2H)) || \ + (SERIAL_PORT == 3 && defined(UBRR3H))) +#define HAS_UART +#endif // These are macros to build serial port register names for the selected SERIAL_PORT (C preprocessor // requires two levels of indirection to expand macro values properly) -#define SERIAL_REGNAME(registerbase,number,suffix) SERIAL_REGNAME_INTERNAL(registerbase,number,suffix) #if SERIAL_PORT == 0 && (!defined(UBRR0H) || !defined(UDR0)) // use un-numbered registers if necessary -#define SERIAL_REGNAME_INTERNAL(registerbase,number,suffix) registerbase##suffix +#define SERIAL_REGNAME(registerbase,number,suffix) _REGNAME_SHORT(registerbase, suffix) #else -#define SERIAL_REGNAME_INTERNAL(registerbase,number,suffix) registerbase##number##suffix +#define SERIAL_REGNAME(registerbase,number,suffix) _REGNAME(registerbase, number, suffix) #endif // Registers used by MarlinSerial class (these are expanded @@ -82,7 +84,7 @@ struct ring_buffer int tail; }; -#if UART_PRESENT(SERIAL_PORT) +#ifdef HAS_UART extern ring_buffer rx_buffer; #endif diff --git a/Firmware/Marlin_main.cpp b/Firmware/Marlin_main.cpp index 1dd4246f2..d3d9ee8bd 100644 --- a/Firmware/Marlin_main.cpp +++ b/Firmware/Marlin_main.cpp @@ -46,7 +46,10 @@ //-// #include "Configuration.h" #include "Marlin.h" - +#include "config.h" + +#include "macros.h" + #ifdef ENABLE_AUTO_BED_LEVELING #include "vector_3.h" #ifdef AUTO_BED_LEVELING_GRID @@ -68,57 +71,48 @@ #include "planner.h" #include "stepper.h" #include "temperature.h" +#include "fancheck.h" #include "motion_control.h" #include "cardreader.h" #include "ConfigurationStore.h" #include "language.h" -#include "pins_arduino.h" #include "math.h" #include "util.h" #include "Timer.h" +#include "Prusa_farm.h" #include +#include #include +#include "Tcodes.h" #include "Dcodes.h" -#include "AutoDeplete.h" +#include "SpoolJoin.h" #ifndef LA_NOCOMPAT #include "la10compat.h" #endif -#ifdef SWSPI -#include "swspi.h" -#endif //SWSPI - #include "spi.h" -#ifdef SWI2C -#include "swi2c.h" -#endif //SWI2C - -#ifdef FILAMENT_SENSOR -#include "fsensor.h" -#endif //FILAMENT_SENSOR +#include "Filament_sensor.h" #ifdef TMC2130 #include "tmc2130.h" #endif //TMC2130 -#ifdef W25X20CL -#include "w25x20cl.h" -#include "optiboot_w25x20cl.h" -#endif //W25X20CL +#ifdef XFLASH +#include "xflash.h" +#include "optiboot_xflash.h" +#endif //XFLASH + +#include "xflash_dump.h" #ifdef BLINKM #include "BlinkM.h" #include "Wire.h" #endif -#ifdef ULTRALCD -#include "ultralcd.h" -#endif - #if NUM_SERVOS > 0 #include "Servo.h" #endif @@ -127,21 +121,13 @@ #include #endif -#include "mmu.h" +#include "mmu2.h" #define VERSION_STRING "1.0.2" - -#include "ultralcd.h" #include "sound.h" #include "cmdqueue.h" -#include "io_atmega2560.h" - -// Macros for bit masks -#define BIT(b) (1<<(b)) -#define TEST(n,b) (((n)&BIT(b))!=0) -#define SET_BIT(n,b,value) (n) ^= ((-value)^(n)) & (BIT(b)) //Macro for print fan speed #define FAN_PULSE_WIDTH_LIMIT ((fanSpeed > 100) ? 3 : 4) //time in ms @@ -165,9 +151,6 @@ CardReader card; #endif -unsigned long PingTime = _millis(); -unsigned long NcTime; - uint8_t mbl_z_probe_nr = 3; //numer of Z measurements for each point in mesh bed leveling calibration //used for PINDA temp calibration and pause print @@ -178,33 +161,18 @@ float default_retraction = DEFAULT_RETRACTION; float homing_feedrate[] = HOMING_FEEDRATE; -// Currently only the extruder axis may be switched to a relative mode. -// Other axes are always absolute or relative based on the common relative_mode flag. -bool axis_relative_modes[] = AXIS_RELATIVE_MODES; + +//Although this flag and many others like this could be represented with a struct/bitfield for each axis (more readable and efficient code), the implementation +//would not be standard across all platforms. That being said, the code will continue to use bitmasks for independent axis. +//Moreover, according to C/C++ standard, the ordering of bits is platform/compiler dependent and the compiler is allowed to align the bits arbitrarily, +//thus bit operations like shifting and masking may stop working and will be very hard to fix. +uint8_t axis_relative_modes = 0; + int feedmultiply=100; //100->1 200->2 int extrudemultiply=100; //100->1 200->2 -int extruder_multiply[EXTRUDERS] = {100 - #if EXTRUDERS > 1 - , 100 - #if EXTRUDERS > 2 - , 100 - #endif - #endif -}; -int bowden_length[4] = {385, 385, 385, 385}; - -bool is_usb_printing = false; bool homing_flag = false; -bool temp_cal_active = false; - -unsigned long kicktime = _millis()+100000; - -unsigned int usb_printing_counter; - -int8_t lcd_change_fil_state = 0; - unsigned long pause_time = 0; unsigned long start_pause_print = _millis(); unsigned long t_fan_rising_edge = _millis(); @@ -214,30 +182,19 @@ static LongTimer crashDetTimer; //unsigned long load_filament_time; bool mesh_bed_leveling_flag = false; -bool mesh_bed_run_from_menu = false; - -bool prusa_sd_card_upload = false; - -unsigned int status_number = 0; unsigned long total_filament_used; -unsigned int heating_status; -unsigned int heating_status_counter; +HeatingStatus heating_status; +uint8_t heating_status_counter; bool loading_flag = false; - - -char snmm_filaments_used = 0; +#define XY_NO_RESTORE_FLAG (mesh_bed_leveling_flag || homing_flag) bool fan_state[2]; int fan_edge_counter[2]; int fan_speed[2]; -char dir_names[3][9]; - -bool sortAlpha = false; - float extruder_multiplier[EXTRUDERS] = {1.0 #if EXTRUDERS > 1 @@ -259,18 +216,8 @@ float min_pos[3] = { X_MIN_POS, Y_MIN_POS, Z_MIN_POS }; float max_pos[3] = { X_MAX_POS, Y_MAX_POS, Z_MAX_POS }; bool axis_known_position[3] = {false, false, false}; -// Extruder offset -#if EXTRUDERS > 1 - #define NUM_EXTRUDER_OFFSETS 2 // only in XY plane -float extruder_offset[NUM_EXTRUDER_OFFSETS][EXTRUDERS] = { -#if defined(EXTRUDER_OFFSET_X) && defined(EXTRUDER_OFFSET_Y) - EXTRUDER_OFFSET_X, EXTRUDER_OFFSET_Y -#endif -}; -#endif - -uint8_t active_extruder = 0; int fanSpeed=0; +uint8_t newFanSpeed = 0; #ifdef FWRETRACT bool retracted[EXTRUDERS]={false @@ -300,7 +247,7 @@ int fanSpeed=0; bool powersupply = true; #endif -bool cancel_heatup = false ; +bool cancel_heatup = false; int8_t busy_state = NOT_BUSY; static long prev_busy_signal_ms = -1; @@ -308,22 +255,26 @@ uint8_t host_keepalive_interval = HOST_KEEPALIVE_INTERVAL; const char errormagic[] PROGMEM = "Error:"; const char echomagic[] PROGMEM = "echo:"; +const char G28W0[] PROGMEM = "G28 W0"; -bool no_response = false; -uint8_t important_status; -uint8_t saved_filament_type; +// Define some coordinates outside the clamp limits (making them invalid past the parsing stage) so +// that they can be used later for various logical checks +#define X_COORD_INVALID (X_MIN_POS-1) -#define SAVED_TARGET_UNSET (X_MIN_POS-1) -float saved_target[NUM_AXIS] = {SAVED_TARGET_UNSET, 0, 0, 0}; +#define SAVED_START_POSITION_UNSET X_COORD_INVALID +float saved_start_position[NUM_AXIS] = {SAVED_START_POSITION_UNSET, 0, 0, 0}; -// save/restore printing in case that mmu was not responding -bool mmu_print_saved = false; +uint16_t saved_segment_idx = 0; // storing estimated time to end of print counted by slicer uint8_t print_percent_done_normal = PRINT_PERCENT_DONE_INIT; -uint16_t print_time_remaining_normal = PRINT_TIME_REMAINING_INIT; //estimated remaining print time in minutes uint8_t print_percent_done_silent = PRINT_PERCENT_DONE_INIT; +uint16_t print_time_remaining_normal = PRINT_TIME_REMAINING_INIT; //estimated remaining print time in minutes uint16_t print_time_remaining_silent = PRINT_TIME_REMAINING_INIT; //estimated remaining print time in minutes +uint16_t print_time_to_change_normal = PRINT_TIME_REMAINING_INIT; //estimated remaining time to next change in minutes +uint16_t print_time_to_change_silent = PRINT_TIME_REMAINING_INIT; //estimated remaining time to next change in minutes + +uint32_t IP_address = 0; //=========================================================================== //=============================Private Variables============================= @@ -345,22 +296,23 @@ static float next_feedrate; // Original feedrate saved during homing moves static float saved_feedrate; -const int sensitive_pins[] = SENSITIVE_PINS; // Sensitive pin list for M42 +const int8_t sensitive_pins[] PROGMEM = SENSITIVE_PINS; // Sensitive pin list for M42 //static float tt = 0; //static float bt = 0; //Inactivity shutdown variables -static unsigned long previous_millis_cmd = 0; +static LongTimer previous_millis_cmd; unsigned long max_inactive_time = 0; static unsigned long stepper_inactive_time = DEFAULT_STEPPER_DEACTIVE_TIME*1000l; static unsigned long safetytimer_inactive_time = DEFAULT_SAFETYTIMER_TIME_MINS*60*1000ul; unsigned long starttime=0; unsigned long stoptime=0; -unsigned long _usb_timer = 0; +ShortTimer usb_timer; bool Stopped=false; +bool processing_tcode; // Helper variable to block certain functions while T-code is being processed #if NUM_SERVOS > 0 Servo servos[NUM_SERVOS]; @@ -371,7 +323,7 @@ bool target_direction; //Insert variables if CHDK is defined #ifdef CHDK unsigned long chdkHigh = 0; -boolean chdkActive = false; +bool chdkActive = false; #endif //! @name RAM save/restore printing @@ -379,29 +331,87 @@ boolean chdkActive = false; bool saved_printing = false; //!< Print is paused and saved in RAM static uint32_t saved_sdpos = 0; //!< SD card position, or line number in case of USB printing uint8_t saved_printing_type = PRINTING_TYPE_SD; -static float saved_pos[4] = { 0, 0, 0, 0 }; +static float saved_pos[4] = { X_COORD_INVALID, 0, 0, 0 }; static uint16_t saved_feedrate2 = 0; //!< Default feedrate (truncated from float) static int saved_feedmultiply2 = 0; -static uint8_t saved_active_extruder = 0; -static float saved_extruder_temperature = 0.0; //!< Active extruder temperature +float saved_extruder_temperature = 0.0; //!< Active extruder temperature +float saved_bed_temperature = 0.0; static bool saved_extruder_relative_mode = false; -static int saved_fanSpeed = 0; //!< Print fan speed +int saved_fan_speed = 0; //!< Print fan speed //! @} static int saved_feedmultiply_mm = 100; +class AutoReportFeatures { + union { + struct { + uint8_t temp : 1; //Temperature flag + uint8_t fans : 1; //Fans flag + uint8_t pos: 1; //Position flag + uint8_t ar4 : 1; //Unused + uint8_t ar5 : 1; //Unused + uint8_t ar6 : 1; //Unused + uint8_t ar7 : 1; //Unused + } __attribute__((packed)) bits; + uint8_t byte; + } arFunctionsActive; + uint8_t auto_report_period; +public: + LongTimer auto_report_timer; + AutoReportFeatures():auto_report_period(0){ +#if defined(AUTO_REPORT) + arFunctionsActive.byte = 0xff; +#else + arFunctionsActive.byte = 0; +#endif //AUTO_REPORT + } + + inline bool Temp()const { return arFunctionsActive.bits.temp != 0; } + inline void SetTemp(uint8_t v){ arFunctionsActive.bits.temp = v; } + + inline bool Fans()const { return arFunctionsActive.bits.fans != 0; } + inline void SetFans(uint8_t v){ arFunctionsActive.bits.fans = v; } + + inline bool Pos()const { return arFunctionsActive.bits.pos != 0; } + inline void SetPos(uint8_t v){ arFunctionsActive.bits.pos = v; } + + inline void SetMask(uint8_t mask){ arFunctionsActive.byte = mask; } + + /// sets the autoreporting timer's period + /// setting it to zero stops the timer + void SetPeriod(uint8_t p){ + auto_report_period = p; + if (auto_report_period != 0){ + auto_report_timer.start(); + } else{ + auto_report_timer.stop(); + } + } + + inline void TimerStart() { auto_report_timer.start(); } + inline bool TimerRunning()const { return auto_report_timer.running(); } + inline bool TimerExpired() { return auto_report_timer.expired(auto_report_period * 1000ul); } +}; + +AutoReportFeatures autoReportFeatures; + //=========================================================================== //=============================Routines====================================== //=========================================================================== -static void get_arc_coordinates(); -static bool setTargetedHotend(int code, uint8_t &extruder); static void print_time_remaining_init(); static void wait_for_heater(long codenum, uint8_t extruder); static void gcode_G28(bool home_x_axis, bool home_y_axis, bool home_z_axis); +static void gcode_M105(); + +#ifndef PINDA_THERMISTOR static void temp_compensation_start(); static void temp_compensation_apply(); +#endif +#ifdef PRUSA_SN_SUPPORT +static uint8_t get_PRUSA_SN(char* SN); +#endif //PRUSA_SN_SUPPORT uint16_t gcode_in_progress = 0; uint16_t mcode_in_progress = 0; @@ -413,22 +423,16 @@ void serial_echopair_P(const char *s_P, double v) void serial_echopair_P(const char *s_P, unsigned long v) { serialprintPGM(s_P); SERIAL_ECHO(v); } -/*FORCE_INLINE*/ void serialprintPGM(const char *str) -{ -#if 0 - char ch=pgm_read_byte(str); - while(ch) - { - MYSERIAL.write(ch); - ch=pgm_read_byte(++str); - } -#else - // hmm, same size as the above version, the compiler did a good job optimizing the above - while( uint8_t ch = pgm_read_byte(str) ){ - MYSERIAL.write((char)ch); - ++str; - } -#endif +void serialprintPGM(const char *str) { + while(uint8_t ch = pgm_read_byte(str)) { + MYSERIAL.write((char)ch); + ++str; + } +} + +void serialprintlnPGM(const char *str) { + serialprintPGM(str); + MYSERIAL.println(); } #ifdef SDSUPPORT @@ -521,6 +525,25 @@ void servo_init() #endif } +bool __attribute__((noinline)) printer_active() { + return IS_SD_PRINTING + || usb_timer.running() + || isPrintPaused + || (custom_message_type == CustomMsg::TempCal) + || saved_printing + || (lcd_commands_type == LcdCommands::Layer1Cal) + || MMU2::mmu2.MMU_PRINT_SAVED() + || homing_flag + || mesh_bed_leveling_flag; +} + +// Currently only used in one place, allowed to be inlined +bool check_fsensor() { + return (IS_SD_PRINTING || usb_timer.running()) + && mcode_in_progress != 600 + && !saved_printing + && e_active(); +} bool fans_check_enabled = true; @@ -537,40 +560,30 @@ void crashdet_restore_print_and_continue() // babystep_apply(); } - -void crashdet_stop_and_save_print2() +void crashdet_fmt_error(char* buf, uint8_t mask) { - cli(); - planner_abort_hard(); //abort printing - cmdqueue_reset(); //empty cmdqueue - card.sdprinting = false; - card.closefile(); - // Reset and re-enable the stepper timer just before the global interrupts are enabled. - st_reset_timer(); - sei(); + if(mask & X_AXIS_MASK) *buf++ = axis_codes[X_AXIS]; + if(mask & Y_AXIS_MASK) *buf++ = axis_codes[Y_AXIS]; + *buf++ = ' '; + strcpy_P(buf, _T(MSG_CRASH_DETECTED)); } void crashdet_detected(uint8_t mask) { st_synchronize(); static uint8_t crashDet_counter = 0; + static uint8_t crashDet_axes = 0; bool automatic_recovery_after_crash = true; + char msg[LCD_WIDTH+1] = ""; - if (crashDet_counter++ == 0) { - crashDetTimer.start(); - } - else if (crashDetTimer.expired(CRASHDET_TIMER * 1000ul)){ - crashDetTimer.stop(); - crashDet_counter = 0; - } - else if(crashDet_counter == CRASHDET_COUNTER_MAX){ - automatic_recovery_after_crash = false; - crashDetTimer.stop(); - crashDet_counter = 0; - } - else { - crashDetTimer.start(); - } + if (crashDetTimer.expired(CRASHDET_TIMER * 1000ul)) { + crashDet_counter = 0; + } + if(++crashDet_counter >= CRASHDET_COUNTER_MAX) { + automatic_recovery_after_crash = false; + } + crashDetTimer.start(); + crashDet_axes |= mask; lcd_update_enable(true); lcd_clear(); @@ -578,34 +591,46 @@ void crashdet_detected(uint8_t mask) if (mask & X_AXIS_MASK) { - eeprom_update_byte((uint8_t*)EEPROM_CRASH_COUNT_X, eeprom_read_byte((uint8_t*)EEPROM_CRASH_COUNT_X) + 1); - eeprom_update_word((uint16_t*)EEPROM_CRASH_COUNT_X_TOT, eeprom_read_word((uint16_t*)EEPROM_CRASH_COUNT_X_TOT) + 1); + eeprom_increment_byte((uint8_t*)EEPROM_CRASH_COUNT_X); + eeprom_increment_word((uint16_t*)EEPROM_CRASH_COUNT_X_TOT); } if (mask & Y_AXIS_MASK) { - eeprom_update_byte((uint8_t*)EEPROM_CRASH_COUNT_Y, eeprom_read_byte((uint8_t*)EEPROM_CRASH_COUNT_Y) + 1); - eeprom_update_word((uint16_t*)EEPROM_CRASH_COUNT_Y_TOT, eeprom_read_word((uint16_t*)EEPROM_CRASH_COUNT_Y_TOT) + 1); + eeprom_increment_byte((uint8_t*)EEPROM_CRASH_COUNT_Y); + eeprom_increment_word((uint16_t*)EEPROM_CRASH_COUNT_Y_TOT); } - - lcd_update_enable(true); lcd_update(2); - lcd_setstatuspgm(_T(MSG_CRASH_DETECTED)); + + // prepare the status message with the _current_ axes status + crashdet_fmt_error(msg, mask); + lcd_setstatus(msg); + gcode_G28(true, true, false); //home X and Y - st_synchronize(); if (automatic_recovery_after_crash) { enquecommand_P(PSTR("CRASH_RECOVER")); }else{ - setTargetHotend(0, active_extruder); - bool yesno = lcd_show_fullscreen_message_yes_no_and_wait_P(_i("Crash detected. Resume print?"), false); - lcd_update_enable(true); - if (yesno) + setTargetHotend(0); + + // notify the user of *all* the axes previously affected, not just the last one + lcd_update_enable(false); + lcd_clear(); + crashdet_fmt_error(msg, crashDet_axes); + crashDet_axes = 0; + lcd_print(msg); + + // ask whether to resume printing + lcd_set_cursor(0, 1); + lcd_puts_P(_T(MSG_RESUME_PRINT)); + lcd_putc('?'); + uint8_t yesno = lcd_show_yes_no_and_wait(false); + if (yesno == LCD_LEFT_BUTTON_CHOICE) { enquecommand_P(PSTR("CRASH_RECOVER")); } - else + else // LCD_MIDDLE_BUTTON_CHOICE { enquecommand_P(PSTR("CRASH_CANCEL")); } @@ -623,10 +648,10 @@ void crashdet_cancel() saved_printing = false; tmc2130_sg_stop_on_crash = true; if (saved_printing_type == PRINTING_TYPE_SD) { - lcd_print_stop(); + print_stop(); }else if(saved_printing_type == PRINTING_TYPE_USB){ SERIAL_ECHOLNRPGM(MSG_OCTOPRINT_CANCEL); //for Octoprint: works the same as clicking "Abort" button in Octoprint GUI - SERIAL_PROTOCOLLNRPGM(MSG_OK); + cmdqueue_reset(); } } @@ -642,6 +667,42 @@ void failstats_reset_print() eeprom_update_byte((uint8_t *)EEPROM_MMU_LOAD_FAIL, 0); } +void watchdogEarlyDisable(void) { + // Regardless if the watchdog support is enabled or not, disable the watchdog very early + // after the program starts since there's no danger in doing this. + // The reason for this is because old bootloaders might not handle the watchdog timer at all, + // leaving it enabled when jumping to the program. This could cause another watchdog reset + // during setup() if not handled properly. So to avoid any issue of this kind, stop the + // watchdog timer manually. + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + wdt_reset(); + MCUSR &= ~_BV(WDRF); + wdt_disable(); + } +} + +void softReset(void) { + cli(); +#ifdef WATCHDOG + // If the watchdog support is enabled, use that for resetting. The timeout value is customized + // for each board since the miniRambo ships with a bootloader which doesn't properly handle the + // WDT. In order to avoid bootlooping, the watchdog is set to a value large enough for the + // usual timeout of the bootloader to pass. + wdt_enable(WATCHDOG_SOFT_RESET_VALUE); +#else + #warning WATCHDOG not defined. See the following comment for more details about the implications + // In case the watchdog is not enabled, the reset is acomplished by jumping to the bootloader + // vector manually. This however is somewhat dangerous since the peripherals don't get reset + // by this operation. Considering this is not going to be used in any production firmware, + // it can be left as is and just be cautious with it. The only way to accomplish a peripheral + // reset is by an external reset, by a watchdog reset or by a power cycle. All of these options + // can't be accomplished just from software. One way to minimize the dangers of this is by + // setting all dangerous pins to INPUT before jumping to the bootloader, but that still doesn't + // reset other peripherals such as UART, timers, INT, PCINT, etc... + asm volatile("jmp 0x3E000"); +#endif + while(1); +} #ifdef MESH_BED_LEVELING @@ -649,119 +710,76 @@ void failstats_reset_print() #endif +static void factory_reset_stats(){ + eeprom_update_dword((uint32_t *)EEPROM_TOTALTIME, 0); + eeprom_update_dword((uint32_t *)EEPROM_FILAMENTUSED, 0); + + failstats_reset_print(); + + eeprom_update_word((uint16_t *)EEPROM_CRASH_COUNT_X_TOT, 0); + eeprom_update_word((uint16_t *)EEPROM_CRASH_COUNT_Y_TOT, 0); + eeprom_update_word((uint16_t *)EEPROM_FERROR_COUNT_TOT, 0); + eeprom_update_word((uint16_t *)EEPROM_POWER_COUNT_TOT, 0); + + eeprom_update_word((uint16_t *)EEPROM_MMU_FAIL_TOT, 0); + eeprom_update_word((uint16_t *)EEPROM_MMU_LOAD_FAIL_TOT, 0); + eeprom_update_dword((uint32_t *)EEPROM_MMU_MATERIAL_CHANGES, 0); +} + // Factory reset function // This function is used to erase parts or whole EEPROM memory which is used for storing calibration and and so on. // Level input parameter sets depth of reset -int er_progress = 0; static void factory_reset(char level) -{ +{ lcd_clear(); - switch (level) { - - // Level 0: Language reset - case 0: - Sound_MakeCustom(100,0,false); - lang_reset(); - break; - - //Level 1: Reset statistics - case 1: - Sound_MakeCustom(100,0,false); - eeprom_update_dword((uint32_t *)EEPROM_TOTALTIME, 0); - eeprom_update_dword((uint32_t *)EEPROM_FILAMENTUSED, 0); + Sound_MakeCustom(100,0,false); + switch (level) { - eeprom_update_byte((uint8_t *)EEPROM_CRASH_COUNT_X, 0); - eeprom_update_byte((uint8_t *)EEPROM_CRASH_COUNT_Y, 0); - eeprom_update_byte((uint8_t *)EEPROM_FERROR_COUNT, 0); - eeprom_update_byte((uint8_t *)EEPROM_POWER_COUNT, 0); + case 0: // Level 0: Language reset + lang_reset(); + break; - eeprom_update_word((uint16_t *)EEPROM_CRASH_COUNT_X_TOT, 0); - eeprom_update_word((uint16_t *)EEPROM_CRASH_COUNT_Y_TOT, 0); - eeprom_update_word((uint16_t *)EEPROM_FERROR_COUNT_TOT, 0); - eeprom_update_word((uint16_t *)EEPROM_POWER_COUNT_TOT, 0); + case 1: //Level 1: Reset statistics + factory_reset_stats(); + lcd_menu_statistics(); + break; - eeprom_update_word((uint16_t *)EEPROM_MMU_FAIL_TOT, 0); - eeprom_update_word((uint16_t *)EEPROM_MMU_LOAD_FAIL_TOT, 0); - eeprom_update_byte((uint8_t *)EEPROM_MMU_FAIL, 0); - eeprom_update_byte((uint8_t *)EEPROM_MMU_LOAD_FAIL, 0); + case 2: // Level 2: Prepare for shipping + factory_reset_stats(); + // FALLTHRU + case 3: // Level 3: Preparation after being serviced + // Force language selection at the next boot up. + lang_reset(); - lcd_menu_statistics(); - - break; - - // Level 2: Prepare for shipping - case 2: - //lcd_puts_P(PSTR("Factory RESET")); - //lcd_puts_at_P(1,2,PSTR("Shipping prep")); - - // Force language selection at the next boot up. - lang_reset(); - // Force the "Follow calibration flow" message at the next boot up. - calibration_status_store(CALIBRATION_STATUS_Z_CALIBRATION); - eeprom_write_byte((uint8_t*)EEPROM_WIZARD_ACTIVE, 1); //run wizard - farm_no = 0; - farm_mode = false; - eeprom_update_byte((uint8_t*)EEPROM_FARM_MODE, farm_mode); - EEPROM_save_B(EEPROM_FARM_NUMBER, &farm_no); - - eeprom_update_dword((uint32_t *)EEPROM_TOTALTIME, 0); - eeprom_update_dword((uint32_t *)EEPROM_FILAMENTUSED, 0); - eeprom_update_word((uint16_t *)EEPROM_CRASH_COUNT_X_TOT, 0); - eeprom_update_word((uint16_t *)EEPROM_CRASH_COUNT_Y_TOT, 0); - eeprom_update_word((uint16_t *)EEPROM_FERROR_COUNT_TOT, 0); - eeprom_update_word((uint16_t *)EEPROM_POWER_COUNT_TOT, 0); - - eeprom_update_word((uint16_t *)EEPROM_MMU_FAIL_TOT, 0); - eeprom_update_word((uint16_t *)EEPROM_MMU_LOAD_FAIL_TOT, 0); - eeprom_update_byte((uint8_t *)EEPROM_MMU_FAIL, 0); - eeprom_update_byte((uint8_t *)EEPROM_MMU_LOAD_FAIL, 0); + // Force the wizard in "Follow calibration flow" mode at the next boot up + calibration_status_clear(CALIBRATION_FORCE_PREP); + eeprom_write_byte((uint8_t*)EEPROM_WIZARD_ACTIVE, 2); + farm_disable(); #ifdef FILAMENT_SENSOR - fsensor_enable(); - fsensor_autoload_set(true); + fsensor.setEnabled(true); + fsensor.setAutoLoadEnabled(true, true); + fsensor.setRunoutEnabled(true, true); +#if (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125) + fsensor.setJamDetectionEnabled(true, true); +#endif //(FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125) #endif //FILAMENT_SENSOR - Sound_MakeCustom(100,0,false); - //_delay_ms(2000); - break; - - // Level 3: erase everything, whole EEPROM will be set to 0xFF - - case 3: - lcd_puts_P(PSTR("Factory RESET")); - lcd_puts_at_P(1, 2, PSTR("ERASING all data")); - - Sound_MakeCustom(100,0,false); - er_progress = 0; - lcd_puts_at_P(3, 3, PSTR(" ")); - lcd_set_cursor(3, 3); - lcd_print(er_progress); - - // Erase EEPROM - for (int i = 0; i < 4096; i++) { - eeprom_update_byte((uint8_t*)i, 0xFF); - - if (i % 41 == 0) { - er_progress++; - lcd_puts_at_P(3, 3, PSTR(" ")); - lcd_set_cursor(3, 3); - lcd_print(er_progress); - lcd_puts_P(PSTR("%")); - } - - } - - - break; - case 4: - bowden_menu(); - break; - - default: - break; - } - + break; + case 4: + menu_progressbar_init(EEPROM_TOP, PSTR("ERASING all data")); + // Erase EEPROM + for (uint16_t i = 0; i < EEPROM_TOP; i++) { + eeprom_update_byte((uint8_t*)i, 0xFF); + menu_progressbar_update(i); + } + menu_progressbar_finish(); + softReset(); + break; + default: + break; + } } extern "C" { @@ -778,7 +796,7 @@ int uart_putchar(char c, FILE *) void lcd_splash() { lcd_clear(); // clears display and homes screen - lcd_puts_P(PSTR("\n Original Prusa i3\n Prusa Research")); + lcd_printf_P(PSTR("\n Original Prusa i3\n Prusa Research\n%20.20S"), PSTR(FW_VERSION)); } @@ -792,81 +810,67 @@ void factory_reset() { lcd_clear(); - lcd_puts_P(PSTR("Factory RESET")); - SET_OUTPUT(BEEPER); - if(eSoundMode!=e_SOUND_MODE_SILENT) - WRITE(BEEPER, HIGH); + if(eSoundMode!=e_SOUND_MODE_SILENT) + WRITE(BEEPER, HIGH); while (!READ(BTN_ENC)); WRITE(BEEPER, LOW); - - _delay_ms(2000); char level = reset_menu(); factory_reset(level); switch (level) { - case 0: _delay_ms(0); break; - case 1: _delay_ms(0); break; - case 2: _delay_ms(0); break; - case 3: _delay_ms(0); break; + case 0: + case 1: + case 2: + case 3: + case 4: _delay_ms(0); break; } } } KEEPALIVE_STATE(IN_HANDLER); } - +#if 0 void show_fw_version_warnings() { if (FW_DEV_VERSION == FW_VERSION_GOLD || FW_DEV_VERSION == FW_VERSION_RC) return; switch (FW_DEV_VERSION) { - case(FW_VERSION_ALPHA): lcd_show_fullscreen_message_and_wait_P(_i("You are using firmware alpha version. This is development version. Using this version is not recommended and may cause printer damage.")); break;////MSG_FW_VERSION_ALPHA c=20 r=8 - case(FW_VERSION_BETA): lcd_show_fullscreen_message_and_wait_P(_i("You are using firmware beta version. This is development version. Using this version is not recommended and may cause printer damage.")); break;////MSG_FW_VERSION_BETA c=20 r=8 + case(FW_VERSION_BETA): lcd_show_fullscreen_message_and_wait_P(MSG_FW_VERSION_BETA); break; + case(FW_VERSION_ALPHA): case(FW_VERSION_DEVEL): case(FW_VERSION_DEBUG): lcd_update_enable(false); lcd_clear(); - #if FW_DEV_VERSION == FW_VERSION_DEVEL + #if (FW_DEV_VERSION == FW_VERSION_DEVEL || FW_DEV_VERSION == FW_VERSION_ALPHA) lcd_puts_at_P(0, 0, PSTR("Development build !!")); #else lcd_puts_at_P(0, 0, PSTR("Debbugging build !!!")); #endif lcd_puts_at_P(0, 1, PSTR("May destroy printer!")); - lcd_puts_at_P(0, 2, PSTR("ver ")); lcd_puts_P(PSTR(FW_VERSION_FULL)); - lcd_puts_at_P(0, 3, PSTR(FW_REPOSITORY)); + lcd_puts_at_P(0, 2, PSTR("FW")); lcd_puts_P(PSTR(FW_VERSION_FULL)); + lcd_puts_at_P(0, 3, PSTR("Repo: ")); lcd_puts_P(PSTR(FW_REPOSITORY)); lcd_wait_for_click(); break; // default: lcd_show_fullscreen_message_and_wait_P(_i("WARNING: This is an unofficial, unsupported build. Use at your own risk!")); break;////MSG_FW_VERSION_UNKNOWN c=20 r=8 } lcd_update_enable(true); } +#endif +#if defined(FILAMENT_SENSOR) && defined(FSENSOR_PROBING) //! @brief try to check if firmware is on right type of printer -static void check_if_fw_is_on_right_printer(){ -#ifdef FILAMENT_SENSOR - if((PRINTER_TYPE == PRINTER_MK3) || (PRINTER_TYPE == PRINTER_MK3S)){ - #ifdef IR_SENSOR - swi2c_init(); - const uint8_t pat9125_detected = swi2c_readByte_A8(PAT9125_I2C_ADDR,0x00,NULL); - if (pat9125_detected){ - lcd_show_fullscreen_message_and_wait_P(_i("MK3S firmware detected on MK3 printer"));} - #endif //IR_SENSOR - - #ifdef PAT9125 - //will return 1 only if IR can detect filament in bondtech extruder so this may fail even when we have IR sensor - const uint8_t ir_detected = !(PIN_GET(IR_SENSOR_PIN)); - if (ir_detected){ - lcd_show_fullscreen_message_and_wait_P(_i("MK3 firmware detected on MK3S printer"));} - #endif //PAT9125 - } -#endif //FILAMENT_SENSOR +static void check_if_fw_is_on_right_printer() { + if (fsensor.probeOtherType()) { + lcd_show_fullscreen_message_and_wait_P(_i(PRINTER_NAME " firmware detected on " PRINTER_NAME_ALTERNATE " printer"));////c=20 r=4 + } } +#endif //defined(FILAMENT_SENSOR) && defined(FSENSOR_PROBING) uint8_t check_printer_version() { @@ -891,7 +895,7 @@ uint8_t check_printer_version() #if (LANG_MODE != 0) //secondary language support -#ifdef W25X20CL +#ifdef XFLASH // language update from external flash @@ -902,8 +906,8 @@ void update_sec_lang_from_external_flash() { if ((boot_app_magic == BOOT_APP_MAGIC) && (boot_app_flags & BOOT_APP_FLG_USER0)) { - uint8_t lang = boot_reserved >> 4; - uint8_t state = boot_reserved & 0xf; + uint8_t lang = boot_reserved >> 3; + uint8_t state = boot_reserved & 0x07; lang_table_header_t header; uint32_t src_addr; if (lang_get_header(lang, &header, &src_addr)) @@ -911,13 +915,13 @@ void update_sec_lang_from_external_flash() lcd_puts_at_P(1,3,PSTR("Language update.")); for (uint8_t i = 0; i < state; i++) fputc('.', lcdout); _delay(100); - boot_reserved = (state + 1) | (lang << 4); + boot_reserved = (boot_reserved & 0xF8) | ((state + 1) & 0x07); if ((state * LANGBOOT_BLOCKSIZE) < header.size) { cli(); uint16_t size = header.size - state * LANGBOOT_BLOCKSIZE; if (size > LANGBOOT_BLOCKSIZE) size = LANGBOOT_BLOCKSIZE; - w25x20cl_rd_data(src_addr + state * LANGBOOT_BLOCKSIZE, (uint8_t*)LANGBOOT_RAMBUFFER, size); + xflash_rd_data(src_addr + state * LANGBOOT_BLOCKSIZE, (uint8_t*)LANGBOOT_RAMBUFFER, size); if (state == 0) { //TODO - check header integrity @@ -935,7 +939,7 @@ void update_sec_lang_from_external_flash() } -#ifdef DEBUG_W25X20CL +#ifdef DEBUG_XFLASH uint8_t lang_xflash_enum_codes(uint16_t* codes) { @@ -945,13 +949,13 @@ uint8_t lang_xflash_enum_codes(uint16_t* codes) while (1) { printf_P(_n("LANGTABLE%d:"), count); - w25x20cl_rd_data(addr, (uint8_t*)&header, sizeof(lang_table_header_t)); + xflash_rd_data(addr, (uint8_t*)&header, sizeof(lang_table_header_t)); if (header.magic != LANG_MAGIC) { - printf_P(_n("NG!\n")); + puts_P(_n("NG!")); break; } - printf_P(_n("OK\n")); + puts_P(_n("OK")); printf_P(_n(" _lt_magic = 0x%08lx %S\n"), header.magic, (header.magic==LANG_MAGIC)?_n("OK"):_n("NA")); printf_P(_n(" _lt_size = 0x%04x (%d)\n"), header.size, header.size); printf_P(_n(" _lt_count = 0x%04x (%d)\n"), header.count, header.count); @@ -973,17 +977,70 @@ void list_sec_lang_from_external_flash() printf_P(_n("XFlash lang count = %hhd\n"), count); } -#endif //DEBUG_W25X20CL +#endif //DEBUG_XFLASH -#endif //W25X20CL +#endif //XFLASH #endif //(LANG_MODE != 0) -static void w25x20cl_err_msg() +static void fw_crash_init() { - lcd_clear(); - lcd_puts_P(_n("External SPI flash\nW25X20CL is not res-\nponding. Language\nswitch unavailable.")); +#ifdef XFLASH_DUMP + dump_crash_reason crash_reason; + if(xfdump_check_state(&crash_reason)) + { + // always signal to the host that a dump is available for retrieval + puts_P(_N("// action:dump_available")); + +#ifdef EMERGENCY_DUMP + if(crash_reason != dump_crash_reason::manual && + eeprom_read_byte((uint8_t*)EEPROM_FW_CRASH_FLAG) != 0xFF) + { + lcd_show_fullscreen_message_and_wait_P( + _n("FW crash detected! " + "You can continue printing. " + "Debug data available for analysis. " + "Contact support to submit details.")); + } +#endif + } +#else //XFLASH_DUMP + dump_crash_reason crash_reason = (dump_crash_reason)eeprom_read_byte((uint8_t*)EEPROM_FW_CRASH_FLAG); + if(crash_reason != dump_crash_reason::manual && (uint8_t)crash_reason != 0xFF) + { + lcd_beeper_quick_feedback(); + lcd_clear(); + + lcd_puts_P(_n("FIRMWARE CRASH!\nCrash reason:\n")); + switch(crash_reason) + { + case dump_crash_reason::stack_error: + lcd_puts_P(_n("Static memory has\nbeen overwritten")); + break; + case dump_crash_reason::watchdog: + lcd_puts_P(_n("Watchdog timeout")); + break; + case dump_crash_reason::bad_isr: + lcd_puts_P(_n("Bad interrupt")); + break; + default: + lcd_print((uint8_t)crash_reason); + break; + } + lcd_wait_for_click(); + } +#endif //XFLASH_DUMP + + // prevent crash prompts to reappear once acknowledged + eeprom_update_byte((uint8_t*)EEPROM_FW_CRASH_FLAG, 0xFF); +} + + +static void xflash_err_msg() +{ + puts_P(_n("XFLASH not responding.")); + lcd_show_fullscreen_message_and_wait_P(_n("External SPI flash\nXFLASH is not res-\nponding. Language\nswitch unavailable.")); } // "Setup" function is called by the Arduino framework on startup. @@ -991,7 +1048,10 @@ static void w25x20cl_err_msg() // are initialized by the main() routine provided by the Arduino framework. void setup() { - mmu_init(); + watchdogEarlyDisable(); + + timer2_init(); // enables functional millis + ultralcd_init(); @@ -1000,62 +1060,73 @@ void setup() lcd_splash(); Sound_Init(); // also guarantee "SET_OUTPUT(BEEPER)" -#ifdef W25X20CL - bool w25x20cl_success = w25x20cl_init(); - if (w25x20cl_success) + selectedSerialPort = eeprom_init_default_byte((uint8_t *)EEPROM_SECOND_SERIAL_ACTIVE, 0); + MYSERIAL.begin(BAUDRATE); + fdev_setup_stream(uartout, uart_putchar, NULL, _FDEV_SETUP_WRITE); //setup uart out stream + stdout = uartout; + +#ifdef XFLASH + bool xflash_success = xflash_init(); + uint8_t optiboot_status = 1; + if (xflash_success) { - optiboot_w25x20cl_enter(); + optiboot_status = optiboot_xflash_enter(); #if (LANG_MODE != 0) //secondary language support update_sec_lang_from_external_flash(); #endif //(LANG_MODE != 0) } - else - { - w25x20cl_err_msg(); - } #else - const bool w25x20cl_success = true; -#endif //W25X20CL + const bool xflash_success = true; +#endif //XFLASH setup_killpin(); setup_powerhold(); - farm_mode = eeprom_read_byte((uint8_t*)EEPROM_FARM_MODE); - EEPROM_read_B(EEPROM_FARM_NUMBER, &farm_no); - if ((farm_mode == 0xFF && farm_no == 0) || ((uint16_t)farm_no == 0xFFFF)) - farm_mode = false; //if farm_mode has not been stored to eeprom yet and farm number is set to zero or EEPROM is fresh, deactivate farm mode - if ((uint16_t)farm_no == 0xFFFF) farm_no = 0; - - selectedSerialPort = eeprom_read_byte((uint8_t*)EEPROM_SECOND_SERIAL_ACTIVE); - if (selectedSerialPort == 0xFF) selectedSerialPort = 0; - if (farm_mode) - { - no_response = true; //we need confirmation by recieving PRUSA thx - important_status = 8; - prusa_statistics(8); - selectedSerialPort = 1; + farm_mode_init(); + #ifdef TMC2130 + if( FarmOrUserECool() ){ //increased extruder current (PFW363) - tmc2130_current_h[E_AXIS] = 36; - tmc2130_current_r[E_AXIS] = 36; + tmc2130_current_h[E_AXIS] = TMC2130_CURRENTS_FARM; + tmc2130_current_r[E_AXIS] = TMC2130_CURRENTS_FARM; + } #endif //TMC2130 -#ifdef FILAMENT_SENSOR - //disabled filament autoload (PFW360) - fsensor_autoload_set(false); -#endif //FILAMENT_SENSOR - // ~ FanCheck -> on - if(!(eeprom_read_byte((uint8_t*)EEPROM_FAN_CHECK_ENABLED))) - eeprom_update_byte((unsigned char *)EEPROM_FAN_CHECK_ENABLED,true); - } - MYSERIAL.begin(BAUDRATE); - fdev_setup_stream(uartout, uart_putchar, NULL, _FDEV_SETUP_WRITE); //setup uart out stream -#ifndef W25X20CL + +#ifdef PRUSA_SN_SUPPORT + //Check for valid SN in EEPROM. Try to retrieve it in case it's invalid. + //SN is valid only if it is NULL terminated and starts with "CZPX". + { + char SN[20]; + eeprom_read_block(SN, (uint8_t*)EEPROM_PRUSA_SN, 20); + if (SN[19] || strncmp_P(SN, PSTR("CZPX"), 4)) + { + if (!get_PRUSA_SN(SN)) + { + eeprom_update_block(SN, (uint8_t*)EEPROM_PRUSA_SN, 20); + puts_P(PSTR("SN updated")); + } + else + puts_P(PSTR("SN update failed")); + } + } +#endif //PRUSA_SN_SUPPORT + + +#ifndef XFLASH SERIAL_PROTOCOLLNPGM("start"); -#endif //W25X20CL - stdout = uartout; +#else + if ((optiboot_status != 0) || (selectedSerialPort != 0)) + SERIAL_PROTOCOLLNPGM("start"); +#endif SERIAL_ECHO_START; - printf_P(PSTR(" " FW_VERSION_FULL "\n")); + puts_P(PSTR(" " FW_VERSION_FULL)); + + // by default the MMU shall remain disabled - PFW-1418 + if (eeprom_init_default_byte((uint8_t *)EEPROM_MMU_ENABLED, 0)) { + MMU2::mmu2.Start(); + } + SpoolJoin::spooljoin.initSpoolJoinStatus(); //SERIAL_ECHOPAIR("Active sheet before:", static_cast(eeprom_read_byte(&(EEPROM_Sheets_base->active_sheet)))); @@ -1064,23 +1135,6 @@ void setup() uint32_t src_addr = 0x00000; if (lang_get_header(1, &header, &src_addr)) { -//this is comparsion of some printing-methods regarding to flash space usage and code size/readability -#define LT_PRINT_TEST 2 -// flash usage -// total p.test -//0 252718 t+c text code -//1 253142 424 170 254 -//2 253040 322 164 158 -//3 253248 530 135 395 -#if (LT_PRINT_TEST==1) //not optimized printf - printf_P(_n(" _src_addr = 0x%08lx\n"), src_addr); - printf_P(_n(" _lt_magic = 0x%08lx %S\n"), header.magic, (header.magic==LANG_MAGIC)?_n("OK"):_n("NA")); - printf_P(_n(" _lt_size = 0x%04x (%d)\n"), header.size, header.size); - printf_P(_n(" _lt_count = 0x%04x (%d)\n"), header.count, header.count); - printf_P(_n(" _lt_chsum = 0x%04x\n"), header.checksum); - printf_P(_n(" _lt_code = 0x%04x (%c%c)\n"), header.code, header.code >> 8, header.code & 0xff); - printf_P(_n(" _lt_sign = 0x%08lx\n"), header.signature); -#elif (LT_PRINT_TEST==2) //optimized printf printf_P( _n( " _src_addr = 0x%08lx\n" @@ -1099,37 +1153,9 @@ void setup() header.code, header.code >> 8, header.code & 0xff, header.signature ); -#elif (LT_PRINT_TEST==3) //arduino print/println (leading zeros not solved) - MYSERIAL.print(" _src_addr = 0x"); - MYSERIAL.println(src_addr, 16); - MYSERIAL.print(" _lt_magic = 0x"); - MYSERIAL.print(header.magic, 16); - MYSERIAL.println((header.magic==LANG_MAGIC)?" OK":" NA"); - MYSERIAL.print(" _lt_size = 0x"); - MYSERIAL.print(header.size, 16); - MYSERIAL.print(" ("); - MYSERIAL.print(header.size, 10); - MYSERIAL.println(")"); - MYSERIAL.print(" _lt_count = 0x"); - MYSERIAL.print(header.count, 16); - MYSERIAL.print(" ("); - MYSERIAL.print(header.count, 10); - MYSERIAL.println(")"); - MYSERIAL.print(" _lt_chsum = 0x"); - MYSERIAL.println(header.checksum, 16); - MYSERIAL.print(" _lt_code = 0x"); - MYSERIAL.print(header.code, 16); - MYSERIAL.print(" ("); - MYSERIAL.print((char)(header.code >> 8), 0); - MYSERIAL.print((char)(header.code & 0xff), 0); - MYSERIAL.println(")"); - MYSERIAL.print(" _lt_resv1 = 0x"); - MYSERIAL.println(header.signature, 16); -#endif //(LT_PRINT_TEST==) -#undef LT_PRINT_TEST #if 0 - w25x20cl_rd_data(0x25ba, (uint8_t*)&block_buffer, 1024); + xflash_rd_data(0x25ba, (uint8_t*)&block_buffer, 1024); for (uint16_t i = 0; i < 1024; i++) { if ((i % 16) == 0) printf_P(_n("%04x:"), 0x25ba+i); @@ -1145,12 +1171,12 @@ void setup() printf_P(_n("_SEC_LANG_TABLE checksum = %04x\n"), sum); sum = (sum >> 8) | ((sum & 0xff) << 8); //swap bytes if (sum == header.checksum) - printf_P(_n("Checksum OK\n"), sum); + puts_P(_n("Checksum OK")); else - printf_P(_n("Checksum NG\n"), sum); + puts_P(_n("Checksum NG")); } else - printf_P(_n("lang_get_header failed!\n")); + puts_P(_n("lang_get_header failed!")); #if 0 for (uint16_t i = 0; i < 1024*10; i++) @@ -1201,8 +1227,6 @@ void setup() SERIAL_ECHOPGM(STRING_VERSION_CONFIG_H); SERIAL_ECHORPGM(_n(" | Author: "));////MSG_AUTHOR SERIAL_ECHOLNPGM(STRING_CONFIG_H_AUTHOR); - SERIAL_ECHOPGM("Compiled: "); - SERIAL_ECHOLNPGM(__DATE__); #endif #endif @@ -1222,36 +1246,32 @@ void setup() else { //printer version was changed so use default settings Config_ResetDefault(); } - SdFatUtil::set_stack_guard(); //writes magic number at the end of static variables to protect against overwriting static memory by stack - tp_init(); // Initialize temperature loop + // writes a magic number at the end of static variables to monitor against incorrect overwriting + // of static memory by stack (this needs to be done before soft_pwm_init, since the check is + // performed inside the soft_pwm_isr) + SdFatUtil::set_stack_guard(); - if (w25x20cl_success) lcd_splash(); // we need to do this again, because tp_init() kills lcd - else - { - w25x20cl_err_msg(); - printf_P(_n("W25X20CL not responding.\n")); - } + // Initialize pwm/temperature loops + soft_pwm_init(); + temp_mgr_init(); + +#ifdef EXTRUDER_ALTFAN_DETECT + SERIAL_ECHORPGM(_n("Hotend fan type: ")); + if (extruder_altfan_detect()) + SERIAL_ECHOLNRPGM(PSTR("ALTFAN")); + else + SERIAL_ECHOLNRPGM(PSTR("NOCTUA")); +#endif //EXTRUDER_ALTFAN_DETECT plan_init(); // Initialize planner; factory_reset(); - if (eeprom_read_dword((uint32_t*)(EEPROM_TOP - 4)) == 0x0ffffffff && - eeprom_read_dword((uint32_t*)(EEPROM_TOP - 8)) == 0x0ffffffff) - { - // Maiden startup. The firmware has been loaded and first started on a virgin RAMBo board, - // where all the EEPROM entries are set to 0x0ff. - // Once a firmware boots up, it forces at least a language selection, which changes - // EEPROM_LANG to number lower than 0x0ff. - // 1) Set a high power mode. - eeprom_update_byte((uint8_t*)EEPROM_SILENT, SILENT_MODE_OFF); -#ifdef TMC2130 - tmc2130_mode = TMC2130_MODE_NORMAL; -#endif //TMC2130 - eeprom_write_byte((uint8_t*)EEPROM_WIZARD_ACTIVE, 1); //run wizard - } - lcd_encoder_diff=0; + eeprom_init_default_byte((uint8_t*)EEPROM_SILENT, SILENT_MODE_OFF); + eeprom_init_default_byte((uint8_t*)EEPROM_WIZARD_ACTIVE, 1); //run wizard if uninitialized + + lcd_encoder_diff=0; #ifdef TMC2130 uint8_t silentMode = eeprom_read_byte((uint8_t*)EEPROM_SILENT); @@ -1295,17 +1315,12 @@ void setup() #endif //TMC2130_VARIABLE_RESOLUTION #endif //TMC2130 - st_init(); // Initialize stepper, this enables interrupts! -#ifdef UVLO_SUPPORT - setup_uvlo_interrupt(); -#endif //UVLO_SUPPORT - #ifdef TMC2130 tmc2130_mode = silentMode?TMC2130_MODE_SILENT:TMC2130_MODE_NORMAL; update_mode_profile(); - tmc2130_init(); + tmc2130_init(TMCInitParams(false, FarmOrUserECool() )); #endif //TMC2130 #ifdef PSU_Delta init_force_z(); // ! important for correct Z-axis initialization @@ -1313,13 +1328,25 @@ void setup() setup_photpin(); +#if 0 servo_init(); +#endif + // Reset the machine correction matrix. // It does not make sense to load the correction matrix until the machine is homed. world2machine_reset(); - + + // Initialize current_position accounting for software endstops to + // avoid unexpected initial shifts on the first move + clamp_to_software_endstops(current_position); + plan_set_position_curposXYZE(); + + // Show the xflash error message now that serial, lcd and encoder are available + if (!xflash_success) + xflash_err_msg(); + #ifdef FILAMENT_SENSOR - fsensor_init(); + fsensor.init(); #endif //FILAMENT_SENSOR @@ -1327,37 +1354,15 @@ void setup() SET_OUTPUT(CONTROLLERFAN_PIN); //Set pin used for driver cooling fan #endif - setup_homepin(); -#ifdef TMC2130 - - if (1) { - // try to run to zero phase before powering the Z motor. - // Move in negative direction - WRITE(Z_DIR_PIN,INVERT_Z_DIR); - // Round the current micro-micro steps to micro steps. - for (uint16_t phase = (tmc2130_rd_MSCNT(Z_AXIS) + 8) >> 4; phase > 0; -- phase) { - // Until the phase counter is reset to zero. - WRITE(Z_STEP_PIN, !INVERT_Z_STEP_PIN); - _delay(2); - WRITE(Z_STEP_PIN, INVERT_Z_STEP_PIN); - _delay(2); - } - } -#endif //TMC2130 - -#if defined(Z_AXIS_ALWAYS_ON) && !defined(PSU_Delta) - enable_z(); +#if defined(Z_AXIS_ALWAYS_ON) + enable_z(); #endif - farm_mode = eeprom_read_byte((uint8_t*)EEPROM_FARM_MODE); - EEPROM_read_B(EEPROM_FARM_NUMBER, &farm_no); - if ((farm_mode == 0xFF && farm_no == 0) || (farm_no == static_cast(0xFFFF))) farm_mode = false; //if farm_mode has not been stored to eeprom yet and farm number is set to zero or EEPROM is fresh, deactivate farm mode - if (farm_no == static_cast(0xFFFF)) farm_no = 0; - if (farm_mode) - { - prusa_statistics(8); - } + + // The farm monitoring SW may accidentally expect + // 2 messages of "printer started" to consider a printer working. + prusa_statistics(8); // Enable Toshiba FlashAir SD card / WiFi enahanced card. card.ToshibaFlashAir_enable(eeprom_read_byte((unsigned char*)EEPROM_TOSHIBA_FLASH_AIR_COMPATIBLITY) == 1); @@ -1411,13 +1416,6 @@ void setup() #endif //DEBUG_SD_SPEED_TEST eeprom_init(); -#ifdef SNMM - if (eeprom_read_dword((uint32_t*)EEPROM_BOWDEN_LENGTH) == 0x0ffffffff) { //bowden length used for SNMM - int _z = BOWDEN_LENGTH; - for(int i = 0; i<4; i++) EEPROM_save_B(EEPROM_BOWDEN_LENGTH + i * 2, &_z); - } -#endif - // In the future, somewhere here would one compare the current firmware version against the firmware version stored in the EEPROM. // If they differ, an update procedure may need to be performed. At the end of this block, the current firmware version // is being written into the EEPROM, so the update procedure will be triggered only once. @@ -1425,51 +1423,44 @@ void setup() #if (LANG_MODE != 0) //secondary language support -#ifdef DEBUG_W25X20CL - W25X20CL_SPI_ENTER(); +#ifdef DEBUG_XFLASH + XFLASH_SPI_ENTER(); uint8_t uid[8]; // 64bit unique id - w25x20cl_rd_uid(uid); - puts_P(_n("W25X20CL UID=")); + xflash_rd_uid(uid); + puts_P(_n("XFLASH UID=")); for (uint8_t i = 0; i < 8; i ++) - printf_P(PSTR("%02hhx"), uid[i]); + printf_P(PSTR("%02x"), uid[i]); putchar('\n'); list_sec_lang_from_external_flash(); -#endif //DEBUG_W25X20CL +#endif //DEBUG_XFLASH // lang_reset(); if (!lang_select(eeprom_read_byte((uint8_t*)EEPROM_LANG))) lcd_language(); #ifdef DEBUG_SEC_LANG - uint16_t sec_lang_code = lang_get_code(1); uint16_t ui = _SEC_LANG_TABLE; //table pointer printf_P(_n("lang_selected=%d\nlang_table=0x%04x\nSEC_LANG_CODE=0x%04x (%c%c)\n"), lang_selected, ui, sec_lang_code, sec_lang_code >> 8, sec_lang_code & 0xff); - lang_print_sec_lang(uartout); #endif //DEBUG_SEC_LANG #endif //(LANG_MODE != 0) - if (eeprom_read_byte((uint8_t*)EEPROM_TEMP_CAL_ACTIVE) == 255) { - eeprom_write_byte((uint8_t*)EEPROM_TEMP_CAL_ACTIVE, 0); - temp_cal_active = false; - } else temp_cal_active = eeprom_read_byte((uint8_t*)EEPROM_TEMP_CAL_ACTIVE); + eeprom_init_default_byte((uint8_t*)EEPROM_TEMP_CAL_ACTIVE, 0); if (eeprom_read_byte((uint8_t*)EEPROM_CALIBRATION_STATUS_PINDA) == 255) { //eeprom_write_byte((uint8_t*)EEPROM_CALIBRATION_STATUS_PINDA, 0); eeprom_write_byte((uint8_t*)EEPROM_CALIBRATION_STATUS_PINDA, 1); int16_t z_shift = 0; - for (uint8_t i = 0; i < 5; i++) EEPROM_save_B(EEPROM_PROBE_TEMP_SHIFT + i * 2, &z_shift); + for (uint8_t i = 0; i < 5; i++) { + eeprom_update_word((uint16_t*)EEPROM_PROBE_TEMP_SHIFT + i, z_shift); + } eeprom_write_byte((uint8_t*)EEPROM_TEMP_CAL_ACTIVE, 0); - temp_cal_active = false; - } - if (eeprom_read_byte((uint8_t*)EEPROM_UVLO) == 255) { - eeprom_write_byte((uint8_t*)EEPROM_UVLO, 0); - } - if (eeprom_read_byte((uint8_t*)EEPROM_SD_SORT) == 255) { - eeprom_write_byte((uint8_t*)EEPROM_SD_SORT, 0); } + eeprom_init_default_byte((uint8_t*)EEPROM_UVLO, 0); + eeprom_init_default_byte((uint8_t*)EEPROM_SD_SORT, 0); + //mbl_mode_init(); mbl_settings_init(); SilentModeMenu_MMU = eeprom_read_byte((uint8_t*)EEPROM_MMU_STEALTH); @@ -1482,17 +1473,16 @@ void setup() setup_fan_interrupt(); #endif //DEBUG_DISABLE_FANCHECK -#ifdef PAT9125 - fsensor_setup_interrupt(); -#endif //PAT9125 - for (int i = 0; i<4; i++) EEPROM_read_B(EEPROM_BOWDEN_LENGTH + i * 2, &bowden_length[i]); - #ifndef DEBUG_DISABLE_STARTMSGS KEEPALIVE_STATE(PAUSED_FOR_USER); if (!farm_mode) { +#if defined(FILAMENT_SENSOR) && defined(FSENSOR_PROBING) check_if_fw_is_on_right_printer(); +#endif //defined(FILAMENT_SENSOR) && defined(FSENSOR_PROBING) +#if 0 show_fw_version_warnings(); +#endif } switch (hw_changed) { @@ -1515,190 +1505,190 @@ void setup() } if (!previous_settings_retrieved) { - lcd_show_fullscreen_message_and_wait_P(_i("Old settings found. Default PID, Esteps etc. will be set.")); //if EEPROM version or printer type was changed, inform user that default setting were loaded////MSG_DEFAULT_SETTINGS_LOADED c=20 r=4 + lcd_show_fullscreen_message_and_wait_P(_i("Old settings found. Default PID, Esteps etc. will be set.")); //if EEPROM version or printer type was changed, inform user that default setting were loaded////MSG_DEFAULT_SETTINGS_LOADED c=20 r=6 Config_StoreSettings(); } - if (eeprom_read_byte((uint8_t*)EEPROM_WIZARD_ACTIVE) == 1) { - lcd_wizard(WizState::Run); - } - if (eeprom_read_byte((uint8_t*)EEPROM_WIZARD_ACTIVE) == 0) { //dont show calibration status messages if wizard is currently active - if (calibration_status() == CALIBRATION_STATUS_ASSEMBLED || - calibration_status() == CALIBRATION_STATUS_UNKNOWN || - calibration_status() == CALIBRATION_STATUS_XYZ_CALIBRATION) { - // Reset the babystepping values, so the printer will not move the Z axis up when the babystepping is enabled. - eeprom_update_word(reinterpret_cast(&(EEPROM_Sheets_base->s[(eeprom_read_byte(&(EEPROM_Sheets_base->active_sheet)))].z_offset)),0); - // Show the message. - lcd_show_fullscreen_message_and_wait_P(_T(MSG_FOLLOW_CALIBRATION_FLOW)); - } - else if (calibration_status() == CALIBRATION_STATUS_LIVE_ADJUST) { - // Show the message. - lcd_show_fullscreen_message_and_wait_P(_T(MSG_BABYSTEP_Z_NOT_SET)); - lcd_update_enable(true); - } - else if (calibration_status() == CALIBRATION_STATUS_CALIBRATED && temp_cal_active == true && calibration_status_pinda() == false) { - //lcd_show_fullscreen_message_and_wait_P(_i("Temperature calibration has not been run yet"));////MSG_PINDA_NOT_CALIBRATED c=20 r=4 - lcd_update_enable(true); - } - else if (calibration_status() == CALIBRATION_STATUS_Z_CALIBRATION) { - // Show the message. - lcd_show_fullscreen_message_and_wait_P(_T(MSG_FOLLOW_Z_CALIBRATION_FLOW)); - } - } -#if !defined (DEBUG_DISABLE_FORCE_SELFTEST) && defined (TMC2130) - if (force_selftest_if_fw_version() && calibration_status() < CALIBRATION_STATUS_ASSEMBLED) { - lcd_show_fullscreen_message_and_wait_P(_i("Selftest will be run to calibrate accurate sensorless rehoming."));////MSG_FORCE_SELFTEST c=20 r=8 - update_current_firmware_version_to_eeprom(); - lcd_selftest(); + // handle FW and calibration status upgrade + bool run_wizard = false; + if (calibration_status_get(CALIBRATION_STATUS_UNKNOWN)) { + CalibrationStatus calibration_status = 0; + if (eeprom_read_byte((uint8_t*)EEPROM_CALIBRATION_STATUS_V1) == 1) { + // calibrated printer upgraded from FW<3.12 + calibration_status |= (CALIBRATION_STATUS_SELFTEST | CALIBRATION_STATUS_XYZ | CALIBRATION_STATUS_Z | CALIBRATION_STATUS_LIVE_ADJUST); + + static const uint16_t v3_2_0_4[] PROGMEM = {3, 2, 0, 4}; + if (eeprom_fw_version_older_than_p(v3_2_0_4)) { + // printer upgraded from FW<3.2.0.4 and requires re-running selftest + lcd_show_fullscreen_message_and_wait_P(_i("Selftest will be run to calibrate accurate sensorless rehoming."));////MSG_FORCE_SELFTEST c=20 r=8 + calibration_status &= ~CALIBRATION_STATUS_SELFTEST; + } + } + eeprom_update_byte((uint8_t*)EEPROM_CALIBRATION_STATUS_V2, calibration_status); + } + if (eeprom_fw_version_older_than_p(FW_VERSION_NR)) { + if (!calibration_status_get(CALIBRATION_WIZARD_STEPS)) { + // we just did a FW upgrade and some (new) wizard step is missing: resume the wizard + run_wizard = true; + } + } + update_current_firmware_version_to_eeprom(); + + if (eeprom_read_byte((uint8_t*)EEPROM_WIZARD_ACTIVE)) { + // first time run of wizard or service prep + lcd_wizard(WizState::Run); + } + else if (run_wizard) { + // some wizard steps required by the upgrade checks + lcd_wizard(WizState::Restore); + } + else { + if (!calibration_status_get(CALIBRATION_STATUS_SELFTEST)) { + // aborted or missing wizard: show a single warning + lcd_show_fullscreen_message_and_wait_P(_T(MSG_FOLLOW_CALIBRATION_FLOW)); + } + else if (!calibration_status_get(CALIBRATION_STATUS_Z)) { + // wizard reset after service prep + lcd_show_fullscreen_message_and_wait_P(_T(MSG_FOLLOW_Z_CALIBRATION_FLOW)); + } else { + // warn about other important steps individually + if (!calibration_status_get(CALIBRATION_STATUS_LIVE_ADJUST)) + lcd_show_fullscreen_message_and_wait_P(_T(MSG_BABYSTEP_Z_NOT_SET)); +#ifdef TEMP_MODEL + if (!calibration_status_get(CALIBRATION_STATUS_TEMP_MODEL) && temp_model_enabled()) + lcd_show_fullscreen_message_and_wait_P(_T(MSG_TM_NOT_CAL)); +#endif //TEMP_MODEL + } } -#endif //TMC2130 && !DEBUG_DISABLE_FORCE_SELFTEST KEEPALIVE_STATE(IN_PROCESS); #endif //DEBUG_DISABLE_STARTMSGS lcd_update_enable(true); lcd_clear(); lcd_update(2); - // Store the currently running firmware into an eeprom, - // so the next time the firmware gets updated, it will know from which version it has been updated. - update_current_firmware_version_to_eeprom(); #ifdef TMC2130 - tmc2130_home_origin[X_AXIS] = eeprom_read_byte((uint8_t*)EEPROM_TMC2130_HOME_X_ORIGIN); - tmc2130_home_bsteps[X_AXIS] = eeprom_read_byte((uint8_t*)EEPROM_TMC2130_HOME_X_BSTEPS); - tmc2130_home_fsteps[X_AXIS] = eeprom_read_byte((uint8_t*)EEPROM_TMC2130_HOME_X_FSTEPS); - if (tmc2130_home_origin[X_AXIS] == 0xff) tmc2130_home_origin[X_AXIS] = 0; - if (tmc2130_home_bsteps[X_AXIS] == 0xff) tmc2130_home_bsteps[X_AXIS] = 48; - if (tmc2130_home_fsteps[X_AXIS] == 0xff) tmc2130_home_fsteps[X_AXIS] = 48; + tmc2130_home_origin[X_AXIS] = eeprom_init_default_byte((uint8_t*)EEPROM_TMC2130_HOME_X_ORIGIN, 0); + tmc2130_home_bsteps[X_AXIS] = eeprom_init_default_byte((uint8_t*)EEPROM_TMC2130_HOME_X_BSTEPS, 48); + tmc2130_home_fsteps[X_AXIS] = eeprom_init_default_byte((uint8_t*)EEPROM_TMC2130_HOME_X_FSTEPS, 48); - tmc2130_home_origin[Y_AXIS] = eeprom_read_byte((uint8_t*)EEPROM_TMC2130_HOME_Y_ORIGIN); - tmc2130_home_bsteps[Y_AXIS] = eeprom_read_byte((uint8_t*)EEPROM_TMC2130_HOME_Y_BSTEPS); - tmc2130_home_fsteps[Y_AXIS] = eeprom_read_byte((uint8_t*)EEPROM_TMC2130_HOME_Y_FSTEPS); - if (tmc2130_home_origin[Y_AXIS] == 0xff) tmc2130_home_origin[Y_AXIS] = 0; - if (tmc2130_home_bsteps[Y_AXIS] == 0xff) tmc2130_home_bsteps[Y_AXIS] = 48; - if (tmc2130_home_fsteps[Y_AXIS] == 0xff) tmc2130_home_fsteps[Y_AXIS] = 48; + tmc2130_home_origin[Y_AXIS] = eeprom_init_default_byte((uint8_t*)EEPROM_TMC2130_HOME_Y_ORIGIN, 0); + tmc2130_home_bsteps[Y_AXIS] = eeprom_init_default_byte((uint8_t*)EEPROM_TMC2130_HOME_Y_BSTEPS, 48); + tmc2130_home_fsteps[Y_AXIS] = eeprom_init_default_byte((uint8_t*)EEPROM_TMC2130_HOME_Y_FSTEPS, 48); - tmc2130_home_enabled = eeprom_read_byte((uint8_t*)EEPROM_TMC2130_HOME_ENABLED); - if (tmc2130_home_enabled == 0xff) tmc2130_home_enabled = 0; + tmc2130_home_enabled = eeprom_init_default_byte((uint8_t*)EEPROM_TMC2130_HOME_ENABLED, 0); #endif //TMC2130 + // report crash failures + fw_crash_init(); + #ifdef UVLO_SUPPORT if (eeprom_read_byte((uint8_t*)EEPROM_UVLO) != 0) { //previous print was terminated by UVLO /* - if (lcd_show_fullscreen_message_yes_no_and_wait_P(_T(MSG_RECOVER_PRINT), false)) recover_print(); + if (!lcd_show_fullscreen_message_yes_no_and_wait_P(_T(MSG_RECOVER_PRINT), false)) recover_print(); else { eeprom_update_byte((uint8_t*)EEPROM_UVLO, 0); lcd_update_enable(true); lcd_update(2); - lcd_setstatuspgm(_T(WELCOME_MSG)); + lcd_setstatuspgm(MSG_WELCOME); } */ manage_heater(); // Update temperatures #ifdef DEBUG_UVLO_AUTOMATIC_RECOVER printf_P(_N("Power panic detected!\nCurrent bed temp:%d\nSaved bed temp:%d\n"), (int)degBed(), eeprom_read_byte((uint8_t*)EEPROM_UVLO_TARGET_BED)); -#endif +#endif if ( degBed() > ( (float)eeprom_read_byte((uint8_t*)EEPROM_UVLO_TARGET_BED) - AUTOMATIC_UVLO_BED_TEMP_OFFSET) ){ #ifdef DEBUG_UVLO_AUTOMATIC_RECOVER puts_P(_N("Automatic recovery!")); - #endif + #endif recover_print(1); } else{ #ifdef DEBUG_UVLO_AUTOMATIC_RECOVER puts_P(_N("Normal recovery!")); - #endif - if ( lcd_show_fullscreen_message_yes_no_and_wait_P(_T(MSG_RECOVER_PRINT), false) ) recover_print(0); - else { + #endif + if ( lcd_show_fullscreen_message_yes_no_and_wait_P(_T(MSG_RECOVER_PRINT), false) == LCD_LEFT_BUTTON_CHOICE) { + recover_print(0); + } else { eeprom_update_byte((uint8_t*)EEPROM_UVLO, 0); lcd_update_enable(true); lcd_update(2); - lcd_setstatuspgm(_T(WELCOME_MSG)); + lcd_setstatuspgm(MSG_WELCOME); } - } - - } + + // Only arm the uvlo interrupt _after_ a recovering print has been initialized and + // the entire state machine initialized. + setup_uvlo_interrupt(); #endif //UVLO_SUPPORT + fCheckModeInit(); - fSetMmuMode(mmu_enabled); KEEPALIVE_STATE(NOT_BUSY); #ifdef WATCHDOG wdt_enable(WDTO_4S); +#ifdef EMERGENCY_HANDLERS + WDTCSR |= (1 << WDIE); +#endif //EMERGENCY_HANDLERS #endif //WATCHDOG } +static inline void crash_and_burn(dump_crash_reason reason) +{ + WRITE(BEEPER, HIGH); + eeprom_update_byte((uint8_t*)EEPROM_FW_CRASH_FLAG, (uint8_t)reason); +#ifdef EMERGENCY_DUMP + xfdump_full_dump_and_reset(reason); +#elif defined(EMERGENCY_SERIAL_DUMP) + if(emergency_serial_dump) + serial_dump_and_reset(reason); +#endif + softReset(); +} -void trace(); +#ifdef EMERGENCY_HANDLERS +#ifdef WATCHDOG +ISR(WDT_vect) +{ + crash_and_burn(dump_crash_reason::watchdog); +} +#endif -#define CHUNK_SIZE 64 // bytes -#define SAFETY_MARGIN 1 -char chunk[CHUNK_SIZE+SAFETY_MARGIN]; -int chunkHead = 0; +ISR(BADISR_vect) +{ + crash_and_burn(dump_crash_reason::bad_isr); +} +#endif //EMERGENCY_HANDLERS -void serial_read_stream() { +void stack_error() { + crash_and_burn(dump_crash_reason::stack_error); +} - setAllTargetHotends(0); - setTargetBed(0); - lcd_clear(); - lcd_puts_P(PSTR(" Upload in progress")); - - // first wait for how many bytes we will receive - uint32_t bytesToReceive; - - // receive the four bytes - char bytesToReceiveBuffer[4]; - for (int i=0; i<4; i++) { - int data; - while ((data = MYSERIAL.read()) == -1) {}; - bytesToReceiveBuffer[i] = data; - - } - - // make it a uint32 - memcpy(&bytesToReceive, &bytesToReceiveBuffer, 4); - - // we're ready, notify the sender - MYSERIAL.write('+'); - - // lock in the routine - uint32_t receivedBytes = 0; - while (prusa_sd_card_upload) { - int i; - for (i=0; i-1)) || (defined(TACH_1) && (TACH_1 > -1))))) + if(autoReportFeatures.Fans()){ + gcode_M123(); + } +#endif //AUTO_REPORT and (FANCHECK and TACH_0 or TACH_1) + autoReportFeatures.TimerStart(); } } +#endif //AUTO_REPORT + /** * Output a "busy" message at regular intervals @@ -1710,6 +1700,7 @@ void host_keepalive() { #endif //HOST_KEEPALIVE_FEATURE if (farm_mode) return; long ms = _millis(); + if (host_keepalive_interval && busy_state != NOT_BUSY) { if ((ms - prev_busy_signal_ms) < (long)(1000L * host_keepalive_interval)) return; switch (busy_state) { @@ -1738,37 +1729,33 @@ void host_keepalive() { // Before loop(), the setup() function is called by the main() routine. void loop() { - KEEPALIVE_STATE(NOT_BUSY); + // Reset a previously aborted command, we can now start processing motion again + planner_aborted = false; - if ((usb_printing_counter > 0) && ((_millis()-_usb_timer) > 1000)) - { - is_usb_printing = true; - usb_printing_counter--; - _usb_timer = _millis(); + if(Stopped) { + // Currently Stopped (possibly due to an error) and not accepting new serial commands. + // Signal to the host that we're currently busy waiting for supervision. + KEEPALIVE_STATE(PAUSED_FOR_USER); + } else { + // Printer is available for processing, reset state + KEEPALIVE_STATE(NOT_BUSY); + } + + if (isPrintPaused && saved_printing_type == PRINTING_TYPE_USB) { //keep believing that usb is being printed. Prevents accessing dangerous menus while pausing. + usb_timer.start(); } - if (usb_printing_counter == 0) - { - is_usb_printing = false; - } - if (isPrintPaused && saved_printing_type == PRINTING_TYPE_USB) //keep believing that usb is being printed. Prevents accessing dangerous menus while pausing. - { - is_usb_printing = true; + else if (usb_timer.expired(10000)) { //just need to check if it expired. Nothing else is needed to be done. + ; } -#ifdef FANCHECK - if (fan_check_error && isPrintPaused) - { - KEEPALIVE_STATE(PAUSED_FOR_USER); - host_keepalive(); //prevent timeouts since usb processing is disabled until print is resumed. This is for a crude way of pausing a print on all hosts. - } -#endif - +#ifdef PRUSA_M28 if (prusa_sd_card_upload) { //we read byte-by byte serial_read_stream(); } - else + else +#endif { get_command(); @@ -1807,7 +1794,7 @@ void loop() // The first character in the block is the block type. char *ptr = cmdbuffer + bufindr; if (*ptr == CMDBUFFER_CURRENT_TYPE_SDCARD) { - // To support power panic, move the lenght of the command on the SD card to a planner buffer. + // To support power panic, move the length of the command on the SD card to a planner buffer. union { struct { char lo; @@ -1851,7 +1838,7 @@ void loop() } //check heater every n milliseconds manage_heater(); - isPrintPaused ? manage_inactivity(true) : manage_inactivity(false); + manage_inactivity(isPrintPaused); checkHitEndstops(); lcd_update(0); #ifdef TMC2130 @@ -1869,7 +1856,7 @@ void loop() } } #endif //TMC2130 - mmu_loop(); + MMU2::mmu2.mmu_loop(); } #define DEFINE_PGM_READ_ANY(type, reader) \ @@ -1882,9 +1869,9 @@ DEFINE_PGM_READ_ANY(signed char, byte); #define XYZ_CONSTS_FROM_CONFIG(type, array, CONFIG) \ static const PROGMEM type array##_P[3] = \ { X_##CONFIG, Y_##CONFIG, Z_##CONFIG }; \ -static inline type array(int axis) \ +static inline type array(uint8_t axis) \ { return pgm_read_any(&array##_P[axis]); } \ -type array##_ext(int axis) \ +type array##_ext(uint8_t axis) \ { return pgm_read_any(&array##_P[axis]); } XYZ_CONSTS_FROM_CONFIG(float, base_min_pos, MIN_POS); @@ -1894,22 +1881,18 @@ XYZ_CONSTS_FROM_CONFIG(float, max_length, MAX_LENGTH); XYZ_CONSTS_FROM_CONFIG(float, home_retract_mm, HOME_RETRACT_MM); XYZ_CONSTS_FROM_CONFIG(signed char, home_dir, HOME_DIR); -static void axis_is_at_home(int axis) { +static void axis_is_at_home(uint8_t axis) { current_position[axis] = base_home_pos(axis) + cs.add_homing[axis]; min_pos[axis] = base_min_pos(axis) + cs.add_homing[axis]; max_pos[axis] = base_max_pos(axis) + cs.add_homing[axis]; } - -inline void set_current_to_destination() { memcpy(current_position, destination, sizeof(current_position)); } -inline void set_destination_to_current() { memcpy(destination, current_position, sizeof(destination)); } - //! @return original feedmultiply static int setup_for_endstop_move(bool enable_endstops_now = true) { saved_feedrate = feedrate; int l_feedmultiply = feedmultiply; feedmultiply = 100; - previous_millis_cmd = _millis(); + previous_millis_cmd.start(); enable_endstops(enable_endstops_now); return l_feedmultiply; @@ -1923,7 +1906,7 @@ static void clean_up_after_endstop_move(int original_feedmultiply) { feedrate = saved_feedrate; feedmultiply = original_feedmultiply; - previous_millis_cmd = _millis(); + previous_millis_cmd.start(); } @@ -1950,7 +1933,7 @@ static void set_bed_level_equation_lsq(double *plane_equation_coefficients) // put the bed at 0 so we don't go below it. current_position[Z_AXIS] = cs.zprobe_zoffset; // in the lsq we reach here after raising the extruder due to the loop structure - plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); + plan_set_position_curposXYZE(); } #else // not AUTO_BED_LEVELING_GRID @@ -1978,7 +1961,7 @@ static void set_bed_level_equation_3pts(float z_at_pt_1, float z_at_pt_2, float // put the bed at 0 so we don't go below it. current_position[Z_AXIS] = cs.zprobe_zoffset; - plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); + plan_set_position_curposXYZE(); } @@ -1990,7 +1973,7 @@ static void run_z_probe() { // move down until you find the bed float zPosition = -10; - plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], zPosition, current_position[E_AXIS], feedrate/60, active_extruder); + plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], zPosition, current_position[E_AXIS], feedrate/60); st_synchronize(); // we have to let the planner know where we are right now as it is not where we said to go. @@ -1999,18 +1982,18 @@ static void run_z_probe() { // move up the retract distance zPosition += home_retract_mm(Z_AXIS); - plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], zPosition, current_position[E_AXIS], feedrate/60, active_extruder); + plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], zPosition, current_position[E_AXIS], feedrate/60); st_synchronize(); // move back down slowly to find bed feedrate = homing_feedrate[Z_AXIS]/4; zPosition -= home_retract_mm(Z_AXIS) * 2; - plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], zPosition, current_position[E_AXIS], feedrate/60, active_extruder); + plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], zPosition, current_position[E_AXIS], feedrate/60); st_synchronize(); current_position[Z_AXIS] = st_get_position_mm(Z_AXIS); // make sure the planner knows where we are as it may be a bit different than we last said to move to - plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); + plan_set_position_curposXYZE(); } static void do_blocking_move_to(float x, float y, float z) { @@ -2066,17 +2049,18 @@ static float probe_pt(float x, float y, float z_before) { * K Set advance K factor */ inline void gcode_M900() { - float newK = code_seen('K') ? code_value_float() : -2; + float newK = code_seen('K') ? code_value() : -2; #ifdef LA_NOCOMPAT - if (newK >= 0 && newK < 10) + if (newK >= 0 && newK < LA_K_MAX) extruder_advance_K = newK; else SERIAL_ECHOLNPGM("K out of allowed range!"); #else if (newK == 0) + { extruder_advance_K = 0; - else if (newK == -1) la10c_reset(); + } else { newK = la10c_value(newK); @@ -2098,7 +2082,7 @@ bool check_commands() { while (buflen) { - if ((code_seen("M84")) || (code_seen("M 84"))) end_command_found = true; + if ((code_seen_P(PSTR("M84"))) || (code_seen_P(PSTR("M 84")))) end_command_found = true; if (!cmdbuffer_front_already_processed) cmdqueue_pop_front(); cmdbuffer_front_already_processed = false; @@ -2107,19 +2091,15 @@ bool check_commands() { } - -// raise_z_above: slowly raise Z to the requested height -// -// contrarily to a simple move, this function will carefully plan a move -// when the current Z position is unknown. In such cases, stallguard is -// enabled and will prevent prolonged pushing against the Z tops -void raise_z_above(float target, bool plan) +/// @brief Safely move Z-axis by distance delta (mm) +/// @param delta travel distance in mm +/// @returns The actual travel distance in mm. Endstop may limit the requested move. +float raise_z(float delta) { - if (current_position[Z_AXIS] >= target) - return; + float travel_z = current_position[Z_AXIS]; - // Z needs raising - current_position[Z_AXIS] = target; + // Prepare to move Z axis + current_position[Z_AXIS] += delta; #if defined(Z_MIN_PIN) && (Z_MIN_PIN > -1) && !defined(DEBUG_DISABLE_ZMINLIMIT) bool z_min_endstop = (READ(Z_MIN_PIN) != Z_MIN_ENDSTOP_INVERTING); @@ -2130,32 +2110,54 @@ void raise_z_above(float target, bool plan) if (axis_known_position[Z_AXIS] || z_min_endstop) { // current position is known or very low, it's safe to raise Z - if(plan) plan_buffer_line_curposXYZE(max_feedrate[Z_AXIS], active_extruder); + clamp_to_software_endstops(current_position); + plan_buffer_line_curposXYZE(max_feedrate[Z_AXIS]); + st_synchronize(); + + // Get the final travel distance + travel_z = current_position[Z_AXIS] - travel_z; + } else { + // ensure Z is powered in normal mode to overcome initial load + enable_z(); + st_synchronize(); + + // rely on crashguard to limit damage + bool z_endstop_enabled = enable_z_endstop(true); +#ifdef TMC2130 + tmc2130_home_enter(Z_AXIS_MASK); +#endif //TMC2130 + plan_buffer_line_curposXYZE(homing_feedrate[Z_AXIS] / 60); + st_synchronize(); + + // Get the final travel distance + travel_z = st_get_position_mm(Z_AXIS) - travel_z; +#ifdef TMC2130 + if (endstop_z_hit_on_purpose()) + { + // not necessarily exact, but will avoid further vertical moves + current_position[Z_AXIS] = max_pos[Z_AXIS]; + plan_set_position_curposXYZE(); + } + tmc2130_home_exit(); +#endif //TMC2130 + enable_z_endstop(z_endstop_enabled); + } + + return travel_z; +} + +// raise_z_above: slowly raise Z to the requested height +// +// contrarily to a simple move, this function will carefully plan a move +// when the current Z position is unknown. In such cases, stallguard is +// enabled and will prevent prolonged pushing against the Z tops +void raise_z_above(float target) +{ + if (current_position[Z_AXIS] >= target) return; - } - // ensure Z is powered in normal mode to overcome initial load - enable_z(); - st_synchronize(); - - // rely on crashguard to limit damage - bool z_endstop_enabled = enable_z_endstop(true); -#ifdef TMC2130 - tmc2130_home_enter(Z_AXIS_MASK); -#endif //TMC2130 - plan_buffer_line_curposXYZE(homing_feedrate[Z_AXIS] / 60, active_extruder); - st_synchronize(); -#ifdef TMC2130 - if (endstop_z_hit_on_purpose()) - { - // not necessarily exact, but will avoid further vertical moves - current_position[Z_AXIS] = max_pos[Z_AXIS]; - plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], - current_position[Z_AXIS], current_position[E_AXIS]); - } - tmc2130_home_exit(); -#endif //TMC2130 - enable_z_endstop(z_endstop_enabled); + // Use absolute value in case the current position is unknown + raise_z(fabs(current_position[Z_AXIS] - target)); } @@ -2169,22 +2171,22 @@ bool calibrate_z_auto() int axis_up_dir = -home_dir(Z_AXIS); tmc2130_home_enter(Z_AXIS_MASK); current_position[Z_AXIS] = 0; - plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); + plan_set_position_curposXYZE(); set_destination_to_current(); destination[Z_AXIS] += (1.1 * max_length(Z_AXIS) * axis_up_dir); feedrate = homing_feedrate[Z_AXIS]; - plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate / 60, active_extruder); + plan_buffer_line_destinationXYZE(feedrate / 60); st_synchronize(); // current_position[axis] = 0; - // plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); + // plan_set_position_curposXYZE(); tmc2130_home_exit(); enable_endstops(false); current_position[Z_AXIS] = 0; - plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); + plan_set_position_curposXYZE(); set_destination_to_current(); destination[Z_AXIS] += 10 * axis_up_dir; //10mm up feedrate = homing_feedrate[Z_AXIS] / 2; - plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate / 60, active_extruder); + plan_buffer_line_destinationXYZE(feedrate / 60); st_synchronize(); enable_endstops(endstops_enabled); if (PRINTER_TYPE == PRINTER_MK3) { @@ -2193,15 +2195,30 @@ bool calibrate_z_auto() else { current_position[Z_AXIS] = Z_MAX_POS + 9.0; } - plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); + plan_set_position_curposXYZE(); return true; } #endif //TMC2130 #ifdef TMC2130 -void homeaxis(int axis, uint8_t cnt, uint8_t* pstep) +static void check_Z_crash(void) +{ + if (!READ(Z_TMC2130_DIAG)) { //Z crash + FORCE_HIGH_POWER_END; + current_position[Z_AXIS] = 0; + plan_set_position_curposXYZE(); + current_position[Z_AXIS] += MESH_HOME_Z_SEARCH; + plan_buffer_line_curposXYZE(max_feedrate[Z_AXIS]); + st_synchronize(); + kill(_T(MSG_BED_LEVELING_FAILED_POINT_LOW)); + } +} +#endif //TMC2130 + +#ifdef TMC2130 +void homeaxis(uint8_t axis, uint8_t cnt, uint8_t* pstep) #else -void homeaxis(int axis, uint8_t cnt) +void homeaxis(uint8_t axis, uint8_t cnt) #endif //TMC2130 { bool endstops_enabled = enable_endstops(true); //RP: endstops should be allways enabled durring homing @@ -2221,24 +2238,24 @@ void homeaxis(int axis, uint8_t cnt) // and the following movement to endstop has a chance to achieve the required velocity // for the stall guard to work. current_position[axis] = 0; - plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); + plan_set_position_curposXYZE(); set_destination_to_current(); // destination[axis] = 11.f; destination[axis] = -3.f * axis_home_dir; - plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate/60, active_extruder); + plan_buffer_line_destinationXYZE(feedrate/60); st_synchronize(); // Move away from the possible collision with opposite endstop with the collision detection disabled. endstops_hit_on_purpose(); enable_endstops(false); current_position[axis] = 0; - plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); + plan_set_position_curposXYZE(); destination[axis] = 1. * axis_home_dir; - plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate/60, active_extruder); + plan_buffer_line_destinationXYZE(feedrate/60); st_synchronize(); // Now continue to move up to the left end stop with the collision detection enabled. enable_endstops(true); destination[axis] = 1.1 * axis_home_dir * max_length(axis); - plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate/60, active_extruder); + plan_buffer_line_destinationXYZE(feedrate/60); st_synchronize(); for (uint8_t i = 0; i < cnt; i++) { @@ -2246,9 +2263,9 @@ void homeaxis(int axis, uint8_t cnt) endstops_hit_on_purpose(); enable_endstops(false); current_position[axis] = 0; - plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); + plan_set_position_curposXYZE(); destination[axis] = -10.f * axis_home_dir; - plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate/60, active_extruder); + plan_buffer_line_destinationXYZE(feedrate/60); st_synchronize(); endstops_hit_on_purpose(); // Now move left up to the collision, this time with a repeatable velocity. @@ -2259,7 +2276,7 @@ void homeaxis(int axis, uint8_t cnt) #else //TMC2130 feedrate = homing_feedrate[axis] / 2; #endif //TMC2130 - plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate/60, active_extruder); + plan_buffer_line_destinationXYZE(feedrate/60); st_synchronize(); #ifdef TMC2130 uint16_t mscnt = tmc2130_rd_MSCNT(axis); @@ -2293,10 +2310,10 @@ void homeaxis(int axis, uint8_t cnt) float dist = - axis_home_dir * 0.01f * 64; #endif //TMC2130 current_position[axis] -= dist; - plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); + plan_set_position_curposXYZE(); current_position[axis] += dist; destination[axis] = current_position[axis]; - plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], 0.5f*feedrate/60, active_extruder); + plan_buffer_line_destinationXYZE(0.5f*feedrate/60); st_synchronize(); feedrate = 0.0; @@ -2308,33 +2325,25 @@ void homeaxis(int axis, uint8_t cnt) #endif int axis_home_dir = home_dir(axis); current_position[axis] = 0; - plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); + plan_set_position_curposXYZE(); destination[axis] = 1.5 * max_length(axis) * axis_home_dir; feedrate = homing_feedrate[axis]; - plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate/60, active_extruder); + plan_buffer_line_destinationXYZE(feedrate/60); st_synchronize(); #ifdef TMC2130 - if (READ(Z_TMC2130_DIAG) != 0) { //Z crash - FORCE_HIGH_POWER_END; - kill(_T(MSG_BED_LEVELING_FAILED_POINT_LOW)); - return; - } + check_Z_crash(); #endif //TMC2130 current_position[axis] = 0; - plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); + plan_set_position_curposXYZE(); destination[axis] = -home_retract_mm(axis) * axis_home_dir; - plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate/60, active_extruder); + plan_buffer_line_destinationXYZE(feedrate/60); st_synchronize(); destination[axis] = 2*home_retract_mm(axis) * axis_home_dir; feedrate = homing_feedrate[axis]/2 ; - plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate/60, active_extruder); + plan_buffer_line_destinationXYZE(feedrate/60); st_synchronize(); #ifdef TMC2130 - if (READ(Z_TMC2130_DIAG) != 0) { //Z crash - FORCE_HIGH_POWER_END; - kill(_T(MSG_BED_LEVELING_FAILED_POINT_LOW)); - return; - } + check_Z_crash(); #endif //TMC2130 axis_is_at_home(axis); destination[axis] = current_position[axis]; @@ -2354,137 +2363,57 @@ void home_xy() set_destination_to_current(); homeaxis(X_AXIS); homeaxis(Y_AXIS); - plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); + plan_set_position_curposXYZE(); endstops_hit_on_purpose(); } void refresh_cmd_timeout(void) { - previous_millis_cmd = _millis(); + previous_millis_cmd.start(); } #ifdef FWRETRACT - void retract(bool retracting, bool swapretract = false) { +void retract(bool retracting, bool swapretract = false) { + // Perform FW retraction, just if needed, but behave as if the move has never took place in + // order to keep E/Z coordinates unchanged. This is done by manipulating the internal planner + // position, which requires a sync if(retracting && !retracted[active_extruder]) { - destination[X_AXIS]=current_position[X_AXIS]; - destination[Y_AXIS]=current_position[Y_AXIS]; - destination[Z_AXIS]=current_position[Z_AXIS]; - destination[E_AXIS]=current_position[E_AXIS]; - current_position[E_AXIS]+=(swapretract?retract_length_swap:cs.retract_length)*float(extrudemultiply)*0.01f; - plan_set_e_position(current_position[E_AXIS]); - float oldFeedrate = feedrate; - feedrate=cs.retract_feedrate*60; - retracted[active_extruder]=true; - prepare_move(); - current_position[Z_AXIS]-=cs.retract_zlift; - plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); - prepare_move(); - feedrate = oldFeedrate; + st_synchronize(); + set_destination_to_current(); + current_position[E_AXIS]+=(swapretract?retract_length_swap:cs.retract_length)*float(extrudemultiply)*0.01f; + plan_set_e_position(current_position[E_AXIS]); + float oldFeedrate = feedrate; + feedrate=cs.retract_feedrate*60; + retracted[active_extruder]=true; + prepare_move(); + if(cs.retract_zlift) { + st_synchronize(); + current_position[Z_AXIS]-=cs.retract_zlift; + plan_set_position_curposXYZE(); + prepare_move(); + } + feedrate = oldFeedrate; } else if(!retracting && retracted[active_extruder]) { - destination[X_AXIS]=current_position[X_AXIS]; - destination[Y_AXIS]=current_position[Y_AXIS]; - destination[Z_AXIS]=current_position[Z_AXIS]; - destination[E_AXIS]=current_position[E_AXIS]; - current_position[Z_AXIS]+=cs.retract_zlift; - plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); - current_position[E_AXIS]-=(swapretract?(retract_length_swap+retract_recover_length_swap):(cs.retract_length+cs.retract_recover_length))*float(extrudemultiply)*0.01f; - plan_set_e_position(current_position[E_AXIS]); - float oldFeedrate = feedrate; - feedrate=cs.retract_recover_feedrate*60; - retracted[active_extruder]=false; - prepare_move(); - feedrate = oldFeedrate; + st_synchronize(); + set_destination_to_current(); + float oldFeedrate = feedrate; + feedrate=cs.retract_recover_feedrate*60; + if(cs.retract_zlift) { + current_position[Z_AXIS]+=cs.retract_zlift; + plan_set_position_curposXYZE(); + prepare_move(); + st_synchronize(); + } + current_position[E_AXIS]-=(swapretract?(retract_length_swap+retract_recover_length_swap):(cs.retract_length+cs.retract_recover_length))*float(extrudemultiply)*0.01f; + plan_set_e_position(current_position[E_AXIS]); + retracted[active_extruder]=false; + prepare_move(); + feedrate = oldFeedrate; } - } //retract +} //retract #endif //FWRETRACT -void trace() { - Sound_MakeCustom(25,440,true); -} -/* -void ramming() { -// float tmp[4] = DEFAULT_MAX_FEEDRATE; - if (current_temperature[0] < 230) { - //PLA - max_feedrate[E_AXIS] = 50; - //current_position[E_AXIS] -= 8; - //plan_buffer_line_curposXYZE(2100 / 60, active_extruder); - //current_position[E_AXIS] += 8; - //plan_buffer_line_curposXYZE(2100 / 60, active_extruder); - current_position[E_AXIS] += 5.4; - plan_buffer_line_curposXYZE(2800 / 60, active_extruder); - current_position[E_AXIS] += 3.2; - plan_buffer_line_curposXYZE(3000 / 60, active_extruder); - current_position[E_AXIS] += 3; - plan_buffer_line_curposXYZE(3400 / 60, active_extruder); - st_synchronize(); - max_feedrate[E_AXIS] = 80; - current_position[E_AXIS] -= 82; - plan_buffer_line_curposXYZE(9500 / 60, active_extruder); - max_feedrate[E_AXIS] = 50;//tmp[E_AXIS]; - current_position[E_AXIS] -= 20; - plan_buffer_line_curposXYZE(1200 / 60, active_extruder); - current_position[E_AXIS] += 5; - plan_buffer_line_curposXYZE(400 / 60, active_extruder); - current_position[E_AXIS] += 5; - plan_buffer_line_curposXYZE(600 / 60, active_extruder); - current_position[E_AXIS] -= 10; - st_synchronize(); - plan_buffer_line_curposXYZE(600 / 60, active_extruder); - current_position[E_AXIS] += 10; - plan_buffer_line_curposXYZE(600 / 60, active_extruder); - current_position[E_AXIS] -= 10; - plan_buffer_line_curposXYZE(800 / 60, active_extruder); - current_position[E_AXIS] += 10; - plan_buffer_line_curposXYZE(800 / 60, active_extruder); - current_position[E_AXIS] -= 10; - plan_buffer_line_curposXYZE(800 / 60, active_extruder); - st_synchronize(); - } - else { - //ABS - max_feedrate[E_AXIS] = 50; - //current_position[E_AXIS] -= 8; - //plan_buffer_line_curposXYZE(2100 / 60, active_extruder); - //current_position[E_AXIS] += 8; - //plan_buffer_line_curposXYZE(2100 / 60, active_extruder); - current_position[E_AXIS] += 3.1; - plan_buffer_line_curposXYZE(2000 / 60, active_extruder); - current_position[E_AXIS] += 3.1; - plan_buffer_line_curposXYZE(2500 / 60, active_extruder); - current_position[E_AXIS] += 4; - plan_buffer_line_curposXYZE(3000 / 60, active_extruder); - st_synchronize(); - //current_position[X_AXIS] += 23; //delay - //plan_buffer_line_curposXYZE(600/60, active_extruder); //delay - //current_position[X_AXIS] -= 23; //delay - //plan_buffer_line_curposXYZE(600/60, active_extruder); //delay - _delay(4700); - max_feedrate[E_AXIS] = 80; - current_position[E_AXIS] -= 92; - plan_buffer_line_curposXYZE(9900 / 60, active_extruder); - max_feedrate[E_AXIS] = 50;//tmp[E_AXIS]; - current_position[E_AXIS] -= 5; - plan_buffer_line_curposXYZE(800 / 60, active_extruder); - current_position[E_AXIS] += 5; - plan_buffer_line_curposXYZE(400 / 60, active_extruder); - current_position[E_AXIS] -= 5; - plan_buffer_line_curposXYZE(600 / 60, active_extruder); - st_synchronize(); - current_position[E_AXIS] += 5; - plan_buffer_line_curposXYZE(600 / 60, active_extruder); - current_position[E_AXIS] -= 5; - plan_buffer_line_curposXYZE(600 / 60, active_extruder); - current_position[E_AXIS] += 5; - plan_buffer_line_curposXYZE(600 / 60, active_extruder); - current_position[E_AXIS] -= 5; - plan_buffer_line_curposXYZE(600 / 60, active_extruder); - st_synchronize(); - - } - } -*/ #ifdef TMC2130 void force_high_power_mode(bool start_high_power_section) { @@ -2501,7 +2430,7 @@ void force_high_power_mode(bool start_high_power_section) { cli(); tmc2130_mode = (start_high_power_section == true) ? TMC2130_MODE_NORMAL : TMC2130_MODE_SILENT; update_mode_profile(); - tmc2130_init(); + tmc2130_init(TMCInitParams(FarmOrUserECool())); // We may have missed a stepper timer interrupt due to the time spent in the tmc2130_init() routine. // Be safe than sorry, reset the stepper timer before re-enabling interrupts. st_reset_timer(); @@ -2510,22 +2439,98 @@ void force_high_power_mode(bool start_high_power_section) { } #endif //TMC2130 +void gcode_M105() +{ +#if defined(TEMP_0_PIN) && TEMP_0_PIN > -1 + SERIAL_PROTOCOLPGM("T:"); + SERIAL_PROTOCOL_F(degHotend(active_extruder),1); + SERIAL_PROTOCOLPGM(" /"); + SERIAL_PROTOCOL_F(degTargetHotend(active_extruder),1); +#if defined(TEMP_BED_PIN) && TEMP_BED_PIN > -1 + SERIAL_PROTOCOLPGM(" B:"); + SERIAL_PROTOCOL_F(degBed(),1); + SERIAL_PROTOCOLPGM(" /"); + SERIAL_PROTOCOL_F(degTargetBed(),1); +#endif //TEMP_BED_PIN + SERIAL_PROTOCOLPGM(" T0:"); + SERIAL_PROTOCOL_F(degHotend(active_extruder),1); + SERIAL_PROTOCOLPGM(" /"); + SERIAL_PROTOCOL_F(degTargetHotend(active_extruder),1); +#else + SERIAL_ERROR_START; + SERIAL_ERRORLNRPGM(_n("No thermistors - no temperature"));////MSG_ERR_NO_THERMISTORS +#endif + + SERIAL_PROTOCOLPGM(" @:"); +#ifdef EXTRUDER_WATTS + SERIAL_PROTOCOL((EXTRUDER_WATTS * getHeaterPower(active_extruder))/127); + SERIAL_PROTOCOLPGM("W"); +#else + SERIAL_PROTOCOL(getHeaterPower(active_extruder)); +#endif + + SERIAL_PROTOCOLPGM(" B@:"); +#ifdef BED_WATTS + SERIAL_PROTOCOL((BED_WATTS * getHeaterPower(-1))/127); + SERIAL_PROTOCOLPGM("W"); +#else + SERIAL_PROTOCOL(getHeaterPower(-1)); +#endif + +#ifdef PINDA_THERMISTOR + SERIAL_PROTOCOLPGM(" P:"); + SERIAL_PROTOCOL_F(current_temperature_pinda,1); +#endif //PINDA_THERMISTOR + +#ifdef AMBIENT_THERMISTOR + SERIAL_PROTOCOLPGM(" A:"); + SERIAL_PROTOCOL_F(current_temperature_ambient,1); +#endif //AMBIENT_THERMISTOR + + +#ifdef SHOW_TEMP_ADC_VALUES + { + float raw = 0.0; +#if defined(TEMP_BED_PIN) && TEMP_BED_PIN > -1 + SERIAL_PROTOCOLPGM(" ADC B:"); + SERIAL_PROTOCOL_F(degBed(),1); + SERIAL_PROTOCOLPGM("C->"); + raw = rawBedTemp(); + SERIAL_PROTOCOL_F(raw/OVERSAMPLENR,5); + SERIAL_PROTOCOLPGM(" Rb->"); + SERIAL_PROTOCOL_F(100 * (1 + (PtA * (raw/OVERSAMPLENR)) + (PtB * sq((raw/OVERSAMPLENR)))), 5); + SERIAL_PROTOCOLPGM(" Rxb->"); + SERIAL_PROTOCOL_F(raw, 5); +#endif + SERIAL_PROTOCOLPGM(" T0:"); + SERIAL_PROTOCOL_F(degHotend(active_extruder),1); + SERIAL_PROTOCOLPGM("C->"); + raw = rawHotendTemp(active_extruder); + SERIAL_PROTOCOL_F(raw/OVERSAMPLENR,5); + SERIAL_PROTOCOLPGM(" Rt0->"); + SERIAL_PROTOCOL_F(100 * (1 + (PtA * (raw/OVERSAMPLENR)) + (PtB * sq((raw/OVERSAMPLENR)))), 5); + SERIAL_PROTOCOLPGM(" Rx0->"); + SERIAL_PROTOCOL_F(raw, 5); + } +#endif + SERIAL_PROTOCOLLN(); +} + #ifdef TMC2130 static void gcode_G28(bool home_x_axis, long home_x_value, bool home_y_axis, long home_y_value, bool home_z_axis, long home_z_value, bool calib, bool without_mbl) #else static void gcode_G28(bool home_x_axis, long home_x_value, bool home_y_axis, long home_y_value, bool home_z_axis, long home_z_value, bool without_mbl) #endif //TMC2130 { + // Flag for the display update routine and to disable the print cancelation during homing. st_synchronize(); + homing_flag = true; #if 0 SERIAL_ECHOPGM("G28, initial "); print_world_coordinates(); SERIAL_ECHOPGM("G28, initial "); print_physical_coordinates(); #endif - // Flag for the display update routine and to disable the print cancelation during homing. - homing_flag = true; - // Which axes should be homed? bool home_x = home_x_axis; bool home_y = home_y_axis; @@ -2540,7 +2545,6 @@ static void gcode_G28(bool home_x_axis, long home_x_value, bool home_y_axis, lon //if we are homing all axes, first move z higher to protect heatbed/steel sheet if (home_all_axes) { raise_z_above(MESH_HOME_Z_SEARCH); - st_synchronize(); } #ifdef ENABLE_AUTO_BED_LEVELING plan_bed_level_matrix.set_to_identity(); //Reset the plane ("erase" all leveling data) @@ -2561,19 +2565,13 @@ static void gcode_G28(bool home_x_axis, long home_x_value, bool home_y_axis, lon current_position[Z_AXIS] = st_get_position_mm(Z_AXIS); #endif - // Reset baby stepping to zero, if the babystepping has already been loaded before. The babystepsTodo value will be - // consumed during the first movements following this statement. + // Reset baby stepping to zero, if the babystepping has already been loaded before. if (home_z) babystep_undo(); - saved_feedrate = feedrate; - int l_feedmultiply = feedmultiply; - feedmultiply = 100; - previous_millis_cmd = _millis(); + int l_feedmultiply = setup_for_endstop_move(); - enable_endstops(true); - - memcpy(destination, current_position, sizeof(destination)); + set_destination_to_current(); feedrate = 0.0; #if Z_HOME_DIR > 0 // If homing away from BED do Z first @@ -2587,9 +2585,9 @@ static void gcode_G28(bool home_x_axis, long home_x_value, bool home_y_axis, lon { current_position[X_AXIS] = 0;current_position[Y_AXIS] = 0; - int x_axis_home_dir = home_dir(X_AXIS); + uint8_t x_axis_home_dir = home_dir(X_AXIS); - plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); + plan_set_position_curposXYZE(); destination[X_AXIS] = 1.5 * max_length(X_AXIS) * x_axis_home_dir;destination[Y_AXIS] = 1.5 * max_length(Y_AXIS) * home_dir(Y_AXIS); feedrate = homing_feedrate[X_AXIS]; if(homing_feedrate[Y_AXIS] 0) raise_z_above(Z_RAISE_BEFORE_HOMING); - st_synchronize(); #endif // defined (Z_RAISE_BEFORE_HOMING) && (Z_RAISE_BEFORE_HOMING > 0) - #if (defined(MESH_BED_LEVELING) && !defined(MK1BP)) // If Mesh bed leveling, move X&Y to safe position for home + #ifdef MESH_BED_LEVELING // If Mesh bed leveling, move X&Y to safe position for home raise_z_above(MESH_HOME_Z_SEARCH); - st_synchronize(); if (!axis_known_position[X_AXIS]) homeaxis(X_AXIS); if (!axis_known_position[Y_AXIS]) homeaxis(Y_AXIS); // 1st mesh bed leveling measurement point, corrected. @@ -2671,14 +2667,14 @@ static void gcode_G28(bool home_x_axis, long home_x_value, bool home_y_axis, lon MYSERIAL.println(current_position[X_AXIS]);MYSERIAL.println(current_position[Y_AXIS]); MYSERIAL.println(current_position[Z_AXIS]);MYSERIAL.println(current_position[E_AXIS]); #endif - plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); + plan_set_position_curposXYZE(); #ifdef DEBUG_BUILD SERIAL_ECHOLNPGM("plan_buffer_line()"); MYSERIAL.println(destination[X_AXIS]);MYSERIAL.println(destination[Y_AXIS]); MYSERIAL.println(destination[Z_AXIS]);MYSERIAL.println(destination[E_AXIS]); MYSERIAL.println(feedrate);MYSERIAL.println(active_extruder); #endif - plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate, active_extruder); + plan_buffer_line_destinationXYZE(feedrate); st_synchronize(); current_position[X_AXIS] = destination[X_AXIS]; current_position[Y_AXIS] = destination[Y_AXIS]; @@ -2697,8 +2693,8 @@ static void gcode_G28(bool home_x_axis, long home_x_value, bool home_y_axis, lon feedrate = XY_TRAVEL_SPEED/60; current_position[Z_AXIS] = 0; - plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); - plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate, active_extruder); + plan_set_position_curposXYZE(); + plan_buffer_line_destinationXYZE(feedrate); st_synchronize(); current_position[X_AXIS] = destination[X_AXIS]; current_position[Y_AXIS] = destination[Y_AXIS]; @@ -2714,10 +2710,10 @@ static void gcode_G28(bool home_x_axis, long home_x_value, bool home_y_axis, lon && (current_position[Y_AXIS]+Y_PROBE_OFFSET_FROM_EXTRUDER <= Y_MAX_POS)) { current_position[Z_AXIS] = 0; - plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); + plan_set_position_curposXYZE(); destination[Z_AXIS] = Z_RAISE_BEFORE_HOMING * home_dir(Z_AXIS) * (-1); // Set destination away from bed feedrate = max_feedrate[Z_AXIS]; - plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate, active_extruder); + plan_buffer_line_destinationXYZE(feedrate); st_synchronize(); homeaxis(Z_AXIS); @@ -2744,49 +2740,33 @@ static void gcode_G28(bool home_x_axis, long home_x_value, bool home_y_axis, lon // Set the planner and stepper routine positions. // At this point the mesh bed leveling and world2machine corrections are disabled and current_position // contains the machine coordinates. - plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); + plan_set_position_curposXYZE(); - #ifdef ENDSTOPS_ONLY_FOR_HOMING - enable_endstops(false); - #endif - - feedrate = saved_feedrate; - feedmultiply = l_feedmultiply; - previous_millis_cmd = _millis(); + clean_up_after_endstop_move(l_feedmultiply); endstops_hit_on_purpose(); -#ifndef MESH_BED_LEVELING -//-// Oct 2019 :: this part of code is (from) now probably un-compilable - // If MESH_BED_LEVELING is not active, then it is the original Prusa i3. - // Offer the user to load the baby step value, which has been adjusted at the previous print session. - if(card.sdprinting && eeprom_read_word((uint16_t *)EEPROM_BABYSTEP_Z)) - lcd_adjust_z(); -#endif // Load the machine correction matrix world2machine_initialize(); // and correct the current_position XY axes to match the transformed coordinate system. world2machine_update_current(); -#if (defined(MESH_BED_LEVELING) && !defined(MK1BP)) +#ifdef MESH_BED_LEVELING if (home_x_axis || home_y_axis || without_mbl || home_z_axis) - { + { if (! home_z && mbl_was_active) { // Re-enable the mesh bed leveling if only the X and Y axes were re-homed. mbl.active = true; // and re-adjust the current logical Z axis with the bed leveling offset applicable at the current XY position. current_position[Z_AXIS] -= mbl.get_z(st_get_position_mm(X_AXIS), st_get_position_mm(Y_AXIS)); } - } - else - { - st_synchronize(); - homing_flag = false; - } + } #endif - if (farm_mode) { prusa_statistics(20); }; + prusa_statistics(20); + st_synchronize(); homing_flag = false; + #if 0 SERIAL_ECHOPGM("G28, final "); print_world_coordinates(); SERIAL_ECHOPGM("G28, final "); print_physical_coordinates(); @@ -2803,13 +2783,418 @@ static void gcode_G28(bool home_x_axis, bool home_y_axis, bool home_z_axis) #endif //TMC2130 } -void adjust_bed_reset() + +// G80 - Automatic mesh bed leveling +static void gcode_G80() { - eeprom_update_byte((unsigned char*)EEPROM_BED_CORRECTION_VALID, 1); - eeprom_update_byte((unsigned char*)EEPROM_BED_CORRECTION_LEFT, 0); - eeprom_update_byte((unsigned char*)EEPROM_BED_CORRECTION_RIGHT, 0); - eeprom_update_byte((unsigned char*)EEPROM_BED_CORRECTION_FRONT, 0); - eeprom_update_byte((unsigned char*)EEPROM_BED_CORRECTION_REAR, 0); + st_synchronize(); + if (planner_aborted) + return; + + mesh_bed_leveling_flag = true; +#ifndef PINDA_THERMISTOR + static bool run = false; // thermistor-less PINDA temperature compensation is running +#endif // ndef PINDA_THERMISTOR + +#ifdef SUPPORT_VERBOSITY + int8_t verbosity_level = 0; + if (code_seen('V')) { + // Just 'V' without a number counts as V1. + char c = strchr_pointer[1]; + verbosity_level = (c == ' ' || c == '\t' || c == 0) ? 1 : code_value_short(); + } +#endif //SUPPORT_VERBOSITY + // Firstly check if we know where we are + if (!(axis_known_position[X_AXIS] && axis_known_position[Y_AXIS] && axis_known_position[Z_AXIS])) { + // We don't know where we are! HOME! + // Push the commands to the front of the message queue in the reverse order! + // There shall be always enough space reserved for these commands. + repeatcommand_front(); // repeat G80 with all its parameters + enquecommand_front_P(G28W0); + return; + } + + uint8_t nMeasPoints = MESH_MEAS_NUM_X_POINTS; + if (code_seen('N')) { + nMeasPoints = code_value_uint8(); + if (nMeasPoints != 7) { + nMeasPoints = 3; + } + } + else { + nMeasPoints = eeprom_read_byte((uint8_t*)EEPROM_MBL_POINTS_NR); + } + + uint8_t nProbeRetry = 3; + if (code_seen('R')) { + nProbeRetry = code_value_uint8(); + if (nProbeRetry > 10) { + nProbeRetry = 10; + } + } + else { + nProbeRetry = eeprom_read_byte((uint8_t*)EEPROM_MBL_PROBE_NR); + } + bool magnet_elimination = (eeprom_read_byte((uint8_t*)EEPROM_MBL_MAGNET_ELIMINATION) > 0); + +#ifndef PINDA_THERMISTOR + if (run == false && eeprom_read_byte((uint8_t *)EEPROM_TEMP_CAL_ACTIVE) && calibration_status_pinda() == true && target_temperature_bed >= 50) + { + temp_compensation_start(); + run = true; + repeatcommand_front(); // repeat G80 with all its parameters + enquecommand_front_P(G28W0); + return; + } + run = false; +#endif //PINDA_THERMISTOR + // Save custom message state, set a new custom message state to display: Calibrating point 9. + CustomMsg custom_message_type_old = custom_message_type; + uint8_t custom_message_state_old = custom_message_state; + custom_message_type = CustomMsg::MeshBedLeveling; + custom_message_state = (nMeasPoints * nMeasPoints) + 10; + lcd_update(1); + + mbl.reset(); //reset mesh bed leveling + + // Reset baby stepping to zero, if the babystepping has already been loaded before. + babystep_undo(); + + // Cycle through all points and probe them + // First move up. During this first movement, the babystepping will be reverted. + current_position[Z_AXIS] = MESH_HOME_Z_SEARCH; + plan_buffer_line_curposXYZE(homing_feedrate[Z_AXIS] / 60); + // The move to the first calibration point. + current_position[X_AXIS] = BED_X0; + current_position[Y_AXIS] = BED_Y0; + +#ifdef SUPPORT_VERBOSITY + if (verbosity_level >= 1) + { + bool clamped = world2machine_clamp(current_position[X_AXIS], current_position[Y_AXIS]); + clamped ? SERIAL_PROTOCOLPGM("First calibration point clamped.\n") : SERIAL_PROTOCOLPGM("No clamping for first calibration point.\n"); + } +#else //SUPPORT_VERBOSITY + world2machine_clamp(current_position[X_AXIS], current_position[Y_AXIS]); +#endif //SUPPORT_VERBOSITY + + int XY_AXIS_FEEDRATE = homing_feedrate[X_AXIS] / 20; + plan_buffer_line_curposXYZE(XY_AXIS_FEEDRATE); + // Wait until the move is finished. + st_synchronize(); + if (planner_aborted) + { + custom_message_type = custom_message_type_old; + custom_message_state = custom_message_state_old; + return; + } + + uint8_t mesh_point = 0; //index number of calibration point + int Z_LIFT_FEEDRATE = homing_feedrate[Z_AXIS] / 40; + bool has_z = is_bed_z_jitter_data_valid(); //checks if we have data from Z calibration (offsets of the Z heiths of the 8 calibration points from the first point) +#ifdef SUPPORT_VERBOSITY + if (verbosity_level >= 1) { + has_z ? SERIAL_PROTOCOLPGM("Z jitter data from Z cal. valid.\n") : SERIAL_PROTOCOLPGM("Z jitter data from Z cal. not valid.\n"); + } +#endif // SUPPORT_VERBOSITY + int l_feedmultiply = setup_for_endstop_move(false); //save feedrate and feedmultiply, sets feedmultiply to 100 + while (mesh_point != nMeasPoints * nMeasPoints) { + // Get coords of a measuring point. + uint8_t ix = mesh_point % nMeasPoints; // from 0 to MESH_NUM_X_POINTS - 1 + uint8_t iy = mesh_point / nMeasPoints; + /*if (!mbl_point_measurement_valid(ix, iy, nMeasPoints, true)) { + printf_P(PSTR("Skipping point [%d;%d] \n"), ix, iy); + custom_message_state--; + mesh_point++; + continue; //skip + }*/ + if (iy & 1) ix = (nMeasPoints - 1) - ix; // Zig zag + if (nMeasPoints == 7) //if we have 7x7 mesh, compare with Z-calibration for points which are in 3x3 mesh + { + has_z = ((ix % 3 == 0) && (iy % 3 == 0)) && is_bed_z_jitter_data_valid(); + } + float z0 = 0.f; + if (has_z && (mesh_point > 0)) { + uint16_t z_offset_u = 0; + if (nMeasPoints == 7) { + z_offset_u = eeprom_read_word((uint16_t*)(EEPROM_BED_CALIBRATION_Z_JITTER + 2 * ((ix/3) + iy - 1))); + } + else { + z_offset_u = eeprom_read_word((uint16_t*)(EEPROM_BED_CALIBRATION_Z_JITTER + 2 * (ix + iy * 3 - 1))); + } + z0 = mbl.z_values[0][0] + *reinterpret_cast(&z_offset_u) * 0.01; +#ifdef SUPPORT_VERBOSITY + if (verbosity_level >= 1) { + printf_P(PSTR("Bed leveling, point: %d, calibration Z stored in eeprom: %d, calibration z: %f \n"), mesh_point, z_offset_u, z0); + } +#endif // SUPPORT_VERBOSITY + } + + // Move Z up to MESH_HOME_Z_SEARCH. + if((ix == 0) && (iy == 0)) current_position[Z_AXIS] = MESH_HOME_Z_SEARCH; + else current_position[Z_AXIS] += 2.f / nMeasPoints; //use relative movement from Z coordinate where PINDa triggered on previous point. This makes calibration faster. + float init_z_bckp = current_position[Z_AXIS]; + plan_buffer_line_curposXYZE(Z_LIFT_FEEDRATE); + st_synchronize(); + + // Move to XY position of the sensor point. + current_position[X_AXIS] = BED_X(ix, nMeasPoints); + current_position[Y_AXIS] = BED_Y(iy, nMeasPoints); + + //printf_P(PSTR("[%f;%f]\n"), current_position[X_AXIS], current_position[Y_AXIS]); + + +#ifdef SUPPORT_VERBOSITY + if (verbosity_level >= 1) { + bool clamped = world2machine_clamp(current_position[X_AXIS], current_position[Y_AXIS]); + SERIAL_PROTOCOL(mesh_point); + clamped ? SERIAL_PROTOCOLPGM(": xy clamped.\n") : SERIAL_PROTOCOLPGM(": no xy clamping\n"); + } +#else //SUPPORT_VERBOSITY + world2machine_clamp(current_position[X_AXIS], current_position[Y_AXIS]); +#endif // SUPPORT_VERBOSITY + + //printf_P(PSTR("after clamping: [%f;%f]\n"), current_position[X_AXIS], current_position[Y_AXIS]); + plan_buffer_line_curposXYZE(XY_AXIS_FEEDRATE); + st_synchronize(); + if (planner_aborted) + { + custom_message_type = custom_message_type_old; + custom_message_state = custom_message_state_old; + return; + } + + // Go down until endstop is hit + const float Z_CALIBRATION_THRESHOLD = 1.f; + if (!find_bed_induction_sensor_point_z((has_z && mesh_point > 0) ? z0 - Z_CALIBRATION_THRESHOLD : -10.f, nProbeRetry)) { //if we have data from z calibration max allowed difference is 1mm for each point, if we dont have data max difference is 10mm from initial point + printf_P(_T(MSG_BED_LEVELING_FAILED_POINT_LOW)); + break; + } + if (init_z_bckp - current_position[Z_AXIS] < 0.1f) { //broken cable or initial Z coordinate too low. Go to MESH_HOME_Z_SEARCH and repeat last step (z-probe) again to distinguish between these two cases. + //printf_P(PSTR("Another attempt! Current Z position: %f\n"), current_position[Z_AXIS]); + current_position[Z_AXIS] = MESH_HOME_Z_SEARCH; + plan_buffer_line_curposXYZE(Z_LIFT_FEEDRATE); + st_synchronize(); + + if (!find_bed_induction_sensor_point_z((has_z && mesh_point > 0) ? z0 - Z_CALIBRATION_THRESHOLD : -10.f, nProbeRetry)) { //if we have data from z calibration max allowed difference is 1mm for each point, if we dont have data max difference is 10mm from initial point + printf_P(_T(MSG_BED_LEVELING_FAILED_POINT_LOW)); + break; + } + if (MESH_HOME_Z_SEARCH - current_position[Z_AXIS] < 0.1f) { + puts_P(PSTR("Bed leveling failed. Sensor disconnected or cable broken.")); + break; + } + } + if (has_z && fabs(z0 - current_position[Z_AXIS]) > Z_CALIBRATION_THRESHOLD) { //if we have data from z calibration, max. allowed difference is 1mm for each point + puts_P(PSTR("Bed leveling failed. Sensor triggered too high.")); + break; + } +#ifdef SUPPORT_VERBOSITY + if (verbosity_level >= 10) { + SERIAL_ECHOPGM("X: "); + MYSERIAL.print(current_position[X_AXIS], 5); + SERIAL_ECHOLNPGM(""); + SERIAL_ECHOPGM("Y: "); + MYSERIAL.print(current_position[Y_AXIS], 5); + SERIAL_PROTOCOLPGM("\n"); + } +#endif // SUPPORT_VERBOSITY + float offset_z = 0; + +#ifdef PINDA_THERMISTOR + offset_z = temp_compensation_pinda_thermistor_offset(current_temperature_pinda); +#endif //PINDA_THERMISTOR + // #ifdef SUPPORT_VERBOSITY + /* if (verbosity_level >= 1) + { + SERIAL_ECHOPGM("mesh bed leveling: "); + MYSERIAL.print(current_position[Z_AXIS], 5); + SERIAL_ECHOPGM(" offset: "); + MYSERIAL.print(offset_z, 5); + SERIAL_ECHOLNPGM(""); + }*/ + // #endif // SUPPORT_VERBOSITY + mbl.set_z(ix, iy, current_position[Z_AXIS] - offset_z); //store measured z values z_values[iy][ix] = z - offset_z; + + custom_message_state--; + mesh_point++; + lcd_update(1); + } + current_position[Z_AXIS] = MESH_HOME_Z_SEARCH; +#ifdef SUPPORT_VERBOSITY + if (verbosity_level >= 20) { + SERIAL_ECHOLNPGM("Mesh bed leveling while loop finished."); + SERIAL_ECHOLNPGM("MESH_HOME_Z_SEARCH: "); + MYSERIAL.print(current_position[Z_AXIS], 5); + } +#endif // SUPPORT_VERBOSITY + plan_buffer_line_curposXYZE(Z_LIFT_FEEDRATE); + st_synchronize(); + if (mesh_point != nMeasPoints * nMeasPoints) { + Sound_MakeSound(e_SOUND_TYPE_StandardAlert); + bool bState; + do { // repeat until Z-leveling o.k. + lcd_display_message_fullscreen_P(_i("Some problem encountered, Z-leveling enforced ...")); ////MSG_ZLEVELING_ENFORCED c=20 r=4 +#ifdef TMC2130 + lcd_wait_for_click_delay(MSG_BED_LEVELING_FAILED_TIMEOUT); + calibrate_z_auto(); // Z-leveling (X-assembly stay up!!!) +#else // TMC2130 + lcd_wait_for_click_delay(0); // ~ no timeout + lcd_calibrate_z_end_stop_manual(true); // Z-leveling (X-assembly stay up!!!) +#endif // TMC2130 + // ~ Z-homing (can not be used "G28", because X & Y-homing would have been done before (Z-homing)) + bState=enable_z_endstop(false); + raise_z(-1); + enable_z_endstop(true); +#ifdef TMC2130 + tmc2130_home_enter(Z_AXIS_MASK); +#endif // TMC2130 + current_position[Z_AXIS] = MESH_HOME_Z_SEARCH; + plan_buffer_line_curposXYZE(homing_feedrate[Z_AXIS] / 40); + st_synchronize(); +#ifdef TMC2130 + tmc2130_home_exit(); +#endif // TMC2130 + enable_z_endstop(bState); + } while (st_get_position_mm(Z_AXIS) > MESH_HOME_Z_SEARCH); // i.e. Z-leveling not o.k. + // plan_set_z_position(MESH_HOME_Z_SEARCH); // is not necessary ('do-while' loop always ends at the expected Z-position) + + custom_message_type = custom_message_type_old; + custom_message_state = custom_message_state_old; + lcd_update_enable(true); // display / status-line recovery + gcode_G28(true, true, true); // X & Y & Z-homing (must be after individual Z-homing (problem with spool-holder)!) + repeatcommand_front(); // re-run (i.e. of "G80") + return; + } + clean_up_after_endstop_move(l_feedmultiply); + // SERIAL_ECHOLNPGM("clean up finished "); + +#ifndef PINDA_THERMISTOR + if(eeprom_read_byte((uint8_t *)EEPROM_TEMP_CAL_ACTIVE) && calibration_status_pinda() == true) temp_compensation_apply(); //apply PINDA temperature compensation +#endif + babystep_apply(); // Apply Z height correction aka baby stepping before mesh bed leveing gets activated. + // SERIAL_ECHOLNPGM("babystep applied"); + bool eeprom_bed_correction_valid = eeprom_read_byte((unsigned char*)EEPROM_BED_CORRECTION_VALID) == 1; +#ifdef SUPPORT_VERBOSITY + if (verbosity_level >= 1) { + eeprom_bed_correction_valid ? SERIAL_PROTOCOLPGM("Bed correction data valid\n") : SERIAL_PROTOCOLPGM("Bed correction data not valid\n"); + } +#endif // SUPPORT_VERBOSITY + + for (uint8_t i = 0; i < 4; ++i) { + static const char codes[4] PROGMEM = { 'L', 'R', 'F', 'B' }; + static uint8_t *const eep_addresses[4] PROGMEM = { + (uint8_t*)EEPROM_BED_CORRECTION_LEFT, + (uint8_t*)EEPROM_BED_CORRECTION_RIGHT, + (uint8_t*)EEPROM_BED_CORRECTION_FRONT, + (uint8_t*)EEPROM_BED_CORRECTION_REAR, + }; + long correction = 0; + if (code_seen(pgm_read_byte(&codes[i]))) + correction = code_value_long(); + else if (eeprom_bed_correction_valid) + correction = (int8_t)eeprom_read_byte((uint8_t*)pgm_read_ptr(&eep_addresses[i])); + if (correction == 0) + continue; + + if (labs(correction) > BED_ADJUSTMENT_UM_MAX) { + SERIAL_ERROR_START; + SERIAL_ECHOPGM("Excessive bed leveling correction: "); + SERIAL_ECHO(correction); + SERIAL_ECHOLNPGM(" microns"); + } + else { + float offset = float(correction) * 0.001f; + switch (i) { + case 0: + for (uint8_t row = 0; row < nMeasPoints; ++row) { + for (uint8_t col = 0; col < nMeasPoints - 1; ++col) { + mbl.z_values[row][col] += offset * (nMeasPoints - 1 - col) / (nMeasPoints - 1); + } + } + break; + case 1: + for (uint8_t row = 0; row < nMeasPoints; ++row) { + for (uint8_t col = 1; col < nMeasPoints; ++col) { + mbl.z_values[row][col] += offset * col / (nMeasPoints - 1); + } + } + break; + case 2: + for (uint8_t col = 0; col < nMeasPoints; ++col) { + for (uint8_t row = 0; row < nMeasPoints; ++row) { + mbl.z_values[row][col] += offset * (nMeasPoints - 1 - row) / (nMeasPoints - 1); + } + } + break; + case 3: + for (uint8_t col = 0; col < nMeasPoints; ++col) { + for (uint8_t row = 1; row < nMeasPoints; ++row) { + mbl.z_values[row][col] += offset * row / (nMeasPoints - 1); + } + } + break; + } + } + } + // SERIAL_ECHOLNPGM("Bed leveling correction finished"); + if (nMeasPoints == 3) { + mbl.upsample_3x3(); //interpolation from 3x3 to 7x7 points using largrangian polynomials while using the same array z_values[iy][ix] for storing (just coppying measured data to new destination and interpolating between them) + } + /* + SERIAL_PROTOCOLPGM("Num X,Y: "); + SERIAL_PROTOCOL(MESH_NUM_X_POINTS); + SERIAL_PROTOCOLPGM(","); + SERIAL_PROTOCOL(MESH_NUM_Y_POINTS); + SERIAL_PROTOCOLPGM("\nZ search height: "); + SERIAL_PROTOCOL(MESH_HOME_Z_SEARCH); + SERIAL_PROTOCOLLNPGM("\nMeasured points:"); + for (int y = MESH_NUM_Y_POINTS-1; y >= 0; y--) { + for (int x = 0; x < MESH_NUM_X_POINTS; x++) { + SERIAL_PROTOCOLPGM(" "); + SERIAL_PROTOCOL_F(mbl.z_values[y][x], 5); + } + SERIAL_PROTOCOLPGM("\n"); + } + */ + if (nMeasPoints == 7 && magnet_elimination) { + mbl_interpolation(nMeasPoints); + } + /* + SERIAL_PROTOCOLPGM("Num X,Y: "); + SERIAL_PROTOCOL(MESH_NUM_X_POINTS); + SERIAL_PROTOCOLPGM(","); + SERIAL_PROTOCOL(MESH_NUM_Y_POINTS); + SERIAL_PROTOCOLPGM("\nZ search height: "); + SERIAL_PROTOCOL(MESH_HOME_Z_SEARCH); + SERIAL_PROTOCOLLNPGM("\nMeasured points:"); + for (int y = MESH_NUM_Y_POINTS-1; y >= 0; y--) { + for (int x = 0; x < MESH_NUM_X_POINTS; x++) { + SERIAL_PROTOCOLPGM(" "); + SERIAL_PROTOCOL_F(mbl.z_values[y][x], 5); + } + SERIAL_PROTOCOLPGM("\n"); + } + */ + // SERIAL_ECHOLNPGM("Upsample finished"); + mbl.active = 1; //activate mesh bed leveling + // SERIAL_ECHOLNPGM("Mesh bed leveling activated"); + go_home_with_z_lift(); + // SERIAL_ECHOLNPGM("Go home finished"); + //unretract (after PINDA preheat retraction) + if (((int)degHotend(active_extruder) > extrude_min_temp) && eeprom_read_byte((unsigned char *)EEPROM_TEMP_CAL_ACTIVE) && calibration_status_pinda() && (target_temperature_bed >= 50)) { + current_position[E_AXIS] += default_retraction; + plan_buffer_line_curposXYZE(400); + } + KEEPALIVE_STATE(NOT_BUSY); + // Restore custom message state + lcd_setstatuspgm(MSG_WELCOME); + custom_message_type = custom_message_type_old; + custom_message_state = custom_message_state_old; + lcd_update(2); + + st_synchronize(); + mesh_bed_leveling_flag = false; } //! @brief Calibrate XYZ @@ -2830,8 +3215,8 @@ bool gcode_M45(bool onlyZ, int8_t verbosity_level) if (!onlyZ) { setTargetBed(0); - setAllTargetHotends(0); - adjust_bed_reset(); //reset bed level correction + setTargetHotend(0); + eeprom_adjust_bed_reset(); //reset bed level correction } // Disable the default update procedure of the display. We will do a modal dialog. @@ -2851,12 +3236,13 @@ bool gcode_M45(bool onlyZ, int8_t verbosity_level) //set_destination_to_current(); int l_feedmultiply = setup_for_endstop_move(); lcd_display_message_fullscreen_P(_T(MSG_AUTO_HOME)); + raise_z_above(MESH_HOME_Z_SEARCH); home_xy(); enable_endstops(false); current_position[X_AXIS] += 5; current_position[Y_AXIS] += 5; - plan_buffer_line_curposXYZE(homing_feedrate[Z_AXIS] / 40, active_extruder); + plan_buffer_line_curposXYZE(homing_feedrate[Z_AXIS] / 40); st_synchronize(); // Let the user move the Z axes up to the end stoppers. @@ -2871,15 +3257,11 @@ bool gcode_M45(bool onlyZ, int8_t verbosity_level) lcd_show_fullscreen_message_and_wait_P(_T(MSG_CONFIRM_NOZZLE_CLEAN)); if(onlyZ){ lcd_display_message_fullscreen_P(_T(MSG_MEASURE_BED_REFERENCE_HEIGHT_LINE1)); - lcd_set_cursor(0, 3); - lcd_print(1); - lcd_puts_P(_T(MSG_MEASURE_BED_REFERENCE_HEIGHT_LINE2)); + lcd_puts_at_P(0,3,_n("1/9")); }else{ //lcd_show_fullscreen_message_and_wait_P(_T(MSG_PAPER)); lcd_display_message_fullscreen_P(_T(MSG_FIND_BED_OFFSET_AND_SKEW_LINE1)); - lcd_set_cursor(0, 2); - lcd_print(1); - lcd_puts_P(_T(MSG_FIND_BED_OFFSET_AND_SKEW_LINE2)); + lcd_puts_at_P(0,3,_n("1/4")); } refresh_cmd_timeout(); @@ -2893,21 +3275,19 @@ bool gcode_M45(bool onlyZ, int8_t verbosity_level) { KEEPALIVE_STATE(PAUSED_FOR_USER); #ifdef STEEL_SHEET - bool result = lcd_show_fullscreen_message_yes_no_and_wait_P(_T(MSG_STEEL_SHEET_CHECK), false, false); - if(result) lcd_show_fullscreen_message_and_wait_P(_T(MSG_REMOVE_STEEL_SHEET)); + uint8_t result = lcd_show_fullscreen_message_yes_no_and_wait_P(_T(MSG_STEEL_SHEET_CHECK), false); + if(result == LCD_LEFT_BUTTON_CHOICE) { + lcd_show_fullscreen_message_and_wait_P(_T(MSG_REMOVE_STEEL_SHEET)); + } #endif //STEEL_SHEET - lcd_show_fullscreen_message_and_wait_P(_T(MSG_PAPER)); + lcd_show_fullscreen_message_and_wait_P(_T(MSG_PAPER)); KEEPALIVE_STATE(IN_HANDLER); lcd_display_message_fullscreen_P(_T(MSG_FIND_BED_OFFSET_AND_SKEW_LINE1)); - lcd_set_cursor(0, 2); - lcd_print(1); - lcd_puts_P(_T(MSG_FIND_BED_OFFSET_AND_SKEW_LINE2)); + lcd_puts_at_P(0,3,_n("1/4")); } bool endstops_enabled = enable_endstops(false); - current_position[Z_AXIS] -= 1; //move 1mm down with disabled endstop - plan_buffer_line_curposXYZE(homing_feedrate[Z_AXIS] / 40, active_extruder); - st_synchronize(); + raise_z(-1); // Move the print head close to the bed. current_position[Z_AXIS] = MESH_HOME_Z_SEARCH; @@ -2917,7 +3297,7 @@ bool gcode_M45(bool onlyZ, int8_t verbosity_level) tmc2130_home_enter(Z_AXIS_MASK); #endif //TMC2130 - plan_buffer_line_curposXYZE(homing_feedrate[Z_AXIS] / 40, active_extruder); + plan_buffer_line_curposXYZE(homing_feedrate[Z_AXIS] / 40); st_synchronize(); #ifdef TMC2130 @@ -2940,25 +3320,23 @@ bool gcode_M45(bool onlyZ, int8_t verbosity_level) bool result = sample_mesh_and_store_reference(); if (result) { - if (calibration_status() == CALIBRATION_STATUS_Z_CALIBRATION) - // Shipped, the nozzle height has been set already. The user can start printing now. - calibration_status_store(CALIBRATION_STATUS_CALIBRATED); - final_result = true; - // babystep_apply(); + calibration_status_set(CALIBRATION_STATUS_Z); + final_result = true; } } else { // Reset the baby step value and the baby step applied flag. - calibration_status_store(CALIBRATION_STATUS_XYZ_CALIBRATION); - eeprom_update_word(reinterpret_cast(&(EEPROM_Sheets_base->s[(eeprom_read_byte(&(EEPROM_Sheets_base->active_sheet)))].z_offset)),0); + calibration_status_clear(CALIBRATION_STATUS_LIVE_ADJUST); + eeprom_update_word(reinterpret_cast(&(EEPROM_Sheets_base->s[(eeprom_read_byte(&(EEPROM_Sheets_base->active_sheet)))].z_offset)),0); + // Complete XYZ calibration. uint8_t point_too_far_mask = 0; BedSkewOffsetDetectionResultType result = find_bed_offset_and_skew(verbosity_level, point_too_far_mask); clean_up_after_endstop_move(l_feedmultiply); // Print head up. current_position[Z_AXIS] = MESH_HOME_Z_SEARCH; - plan_buffer_line_curposXYZE(homing_feedrate[Z_AXIS] / 40, active_extruder); + plan_buffer_line_curposXYZE(homing_feedrate[Z_AXIS] / 40); st_synchronize(); //#ifndef NEW_XYZCAL if (result >= 0) @@ -2978,7 +3356,7 @@ bool gcode_M45(bool onlyZ, int8_t verbosity_level) clean_up_after_endstop_move(l_feedmultiply); // Print head up. current_position[Z_AXIS] = MESH_HOME_Z_SEARCH; - plan_buffer_line_curposXYZE(homing_feedrate[Z_AXIS] / 40, active_extruder); + plan_buffer_line_curposXYZE(homing_feedrate[Z_AXIS] / 40); st_synchronize(); // if (result >= 0) babystep_apply(); #endif //HEATBED_V2 @@ -2991,8 +3369,9 @@ bool gcode_M45(bool onlyZ, int8_t verbosity_level) if (result >= 0) { // Calibration valid, the machine should be able to print. Advise the user to run the V2Calibration.gcode. - calibration_status_store(CALIBRATION_STATUS_LIVE_ADJUST); - if (eeprom_read_byte((uint8_t*)EEPROM_WIZARD_ACTIVE) != 1) lcd_show_fullscreen_message_and_wait_P(_T(MSG_BABYSTEP_Z_NOT_SET)); + calibration_status_set(CALIBRATION_STATUS_XYZ | CALIBRATION_STATUS_Z); + if (!eeprom_read_byte((uint8_t*)EEPROM_WIZARD_ACTIVE)) + lcd_show_fullscreen_message_and_wait_P(_T(MSG_BABYSTEP_Z_NOT_SET)); final_result = true; } } @@ -3038,273 +3417,297 @@ void gcode_M114() SERIAL_PROTOCOLPGM(" Z:"); SERIAL_PROTOCOL(float(st_get_position(Z_AXIS)) / cs.axis_steps_per_unit[Z_AXIS]); SERIAL_PROTOCOLPGM(" E:"); - SERIAL_PROTOCOL(float(st_get_position(E_AXIS)) / cs.axis_steps_per_unit[E_AXIS]); - - SERIAL_PROTOCOLLN(""); + SERIAL_PROTOCOLLN(float(st_get_position(E_AXIS)) / cs.axis_steps_per_unit[E_AXIS]); } -//! extracted code to compute z_shift for M600 in case of filament change operation -//! requested from fsensors. -//! The function ensures, that the printhead lifts to at least 25mm above the heat bed -//! unlike the previous implementation, which was adding 25mm even when the head was -//! printing at e.g. 24mm height. -//! A safety margin of FILAMENTCHANGE_ZADD is added in all cases to avoid touching -//! the printout. -//! This function is templated to enable fast change of computation data type. -//! @return new z_shift value -template -static T gcode_M600_filament_change_z_shift() +#if (defined(FANCHECK) && (((defined(TACH_0) && (TACH_0 >-1)) || (defined(TACH_1) && (TACH_1 > -1))))) +void gcode_M123() { -#ifdef FILAMENTCHANGE_ZADD - static_assert(Z_MAX_POS < (255 - FILAMENTCHANGE_ZADD), "Z-range too high, change the T type from uint8_t to uint16_t"); - // avoid floating point arithmetics when not necessary - results in shorter code - T ztmp = T( current_position[Z_AXIS] ); - T z_shift = 0; - if(ztmp < T(25)){ - z_shift = T(25) - ztmp; // make sure to be at least 25mm above the heat bed - } - return z_shift + T(FILAMENTCHANGE_ZADD); // always move above printout -#else - return T(0); -#endif -} + printf_P(_N("E0:%d RPM PRN1:%d RPM E0@:%u PRN1@:%d\n"), 60*fan_speed[active_extruder], 60*fan_speed[1], newFanSpeed, fanSpeed); +} +#endif //FANCHECK and TACH_0 or TACH_1 -static void gcode_M600(bool automatic, float x_position, float y_position, float z_shift, float e_shift, float /*e_shift_late*/) -{ +static void mmu_M600_wait_and_beep() { + // Beep and wait for user to remove old filament and prepare new filament for load + KEEPALIVE_STATE(PAUSED_FOR_USER); + + lcd_display_message_fullscreen_P(_i("Remove old filament and press the knob to start loading new filament.")); ////MSG_REMOVE_OLD_FILAMENT c=20 r=5 + + while (!lcd_clicked()) { + manage_heater(); + manage_inactivity(true); + sound_wait_for_user(); + } + sound_wait_for_user_reset(); +} + +/** + * @brief Handling of unload when using MMU with M600 + * A fullscreen message showing "Unloading Filament x" + * should be shown on the LCD and LCD updates should be + * are disabled in the meantime. + */ +static void mmu_M600_unload_filament() { + if (MMU2::mmu2.get_current_tool() == (uint8_t)MMU2::FILAMENT_UNKNOWN) return; + + lcd_update_enable(false); + lcd_clear(); + lcd_puts_at_P(0, 1, _T(MSG_UNLOADING_FILAMENT)); + lcd_print(' '); + lcd_print(MMU2::mmu2.get_current_tool() + 1); + + // unload just current filament for multimaterial printers (used also in M702) + MMU2::mmu2.unload(); + lcd_update_enable(true); +} + +/// @brief load filament for mmu v2 +/// @par nozzle_temp nozzle temperature to load filament +static void mmu_M600_load_filament(bool automatic, float nozzle_temp) { + uint8_t slot; + if (automatic) { + slot = SpoolJoin::spooljoin.nextSlot(); + } else { + // Only ask for the slot if automatic/SpoolJoin is off + slot = choose_menu_P(_T(MSG_SELECT_EXTRUDER), _T(MSG_EXTRUDER)); + } + + setTargetHotend(nozzle_temp); + + MMU2::mmu2.load_filament_to_nozzle(slot); + + load_filament_final_feed(); // @@TODO verify + st_synchronize(); +} + +static void gcode_M600(bool automatic, float x_position, float y_position, float z_shift, float e_shift, float /*e_shift_late*/) { st_synchronize(); float lastpos[4]; - if (farm_mode) - { prusa_statistics(22); - } - + //First backup current position and settings int feedmultiplyBckp = feedmultiply; float HotendTempBckp = degTargetHotend(active_extruder); int fanSpeedBckp = fanSpeed; - lastpos[X_AXIS] = current_position[X_AXIS]; - lastpos[Y_AXIS] = current_position[Y_AXIS]; - lastpos[Z_AXIS] = current_position[Z_AXIS]; - lastpos[E_AXIS] = current_position[E_AXIS]; + memcpy(lastpos, current_position, sizeof(lastpos)); - //Retract E + // Turn off the fan + fanSpeed = 0; + + // Retract E current_position[E_AXIS] += e_shift; - plan_buffer_line_curposXYZE(FILAMENTCHANGE_RFEED, active_extruder); + plan_buffer_line_curposXYZE(FILAMENTCHANGE_RFEED); st_synchronize(); - //Lift Z - current_position[Z_AXIS] += z_shift; - plan_buffer_line_curposXYZE(FILAMENTCHANGE_ZFEED, active_extruder); - st_synchronize(); + // Raise the Z axis + raise_z(z_shift); - //Move XY to side + // Move XY to side current_position[X_AXIS] = x_position; current_position[Y_AXIS] = y_position; - plan_buffer_line_curposXYZE(FILAMENTCHANGE_XYFEED, active_extruder); + plan_buffer_line_curposXYZE(FILAMENTCHANGE_XYFEED); st_synchronize(); - //Beep, manage nozzle heater and wait for user to start unload filament - if(!mmu_enabled) M600_wait_for_user(HotendTempBckp); - - lcd_change_fil_state = 0; - // Unload filament - if (mmu_enabled) extr_unload(); //unload just current filament for multimaterial printers (used also in M702) - else unload_filament(); //unload filament for single material (used also in M702) - //finish moves - st_synchronize(); - - if (!mmu_enabled) + if (MMU2::mmu2.Enabled()) { + mmu_M600_unload_filament(); + } else { + // Beep, manage nozzle heater and wait for user to start unload filament + M600_wait_for_user(HotendTempBckp); + unload_filament(FILAMENTCHANGE_FINALRETRACT); + } + st_synchronize(); // finish moves { - KEEPALIVE_STATE(PAUSED_FOR_USER); - lcd_change_fil_state = lcd_show_fullscreen_message_yes_no_and_wait_P(_i("Was filament unload successful?"), - false, true); ////MSG_UNLOAD_SUCCESSFUL c=20 r=2 - if (lcd_change_fil_state == 0) + FSensorBlockRunout fsBlockRunout; + + if (!MMU2::mmu2.Enabled()) { - lcd_clear(); - lcd_set_cursor(0, 2); - lcd_puts_P(_T(MSG_PLEASE_WAIT)); - current_position[X_AXIS] -= 100; - plan_buffer_line_curposXYZE(FILAMENTCHANGE_XYFEED, active_extruder); - st_synchronize(); - lcd_show_fullscreen_message_and_wait_P(_i("Please open idler and remove filament manually."));////MSG_CHECK_IDLER c=20 r=4 - } - } - - if (mmu_enabled) - { - if (!automatic) { - if (saved_printing) mmu_eject_filament(mmu_extruder, false); //if M600 was invoked by filament senzor (FINDA) eject filament so user can easily remove it - mmu_M600_wait_and_beep(); - if (saved_printing) { - + KEEPALIVE_STATE(PAUSED_FOR_USER); + uint8_t choice = + lcd_show_fullscreen_message_yes_no_and_wait_P(_i("Was filament unload successful?"), false, LCD_LEFT_BUTTON_CHOICE); ////MSG_UNLOAD_SUCCESSFUL c=20 r=2 + if (choice == LCD_MIDDLE_BUTTON_CHOICE) { lcd_clear(); - lcd_set_cursor(0, 2); - lcd_puts_P(_T(MSG_PLEASE_WAIT)); - - mmu_command(MmuCmd::R0); - manage_response(false, false); + lcd_puts_at_P(0, 2, _T(MSG_PLEASE_WAIT)); + current_position[X_AXIS] -= 100; + plan_buffer_line_curposXYZE(FILAMENTCHANGE_XYFEED); + st_synchronize(); + lcd_show_fullscreen_message_and_wait_P(_i("Please open idler and remove filament manually.")); ////MSG_CHECK_IDLER c=20 r=5 } + M600_load_filament(); } - mmu_M600_load_filament(automatic, HotendTempBckp); + else // MMU is enabled + { + if (!automatic) { + if (saved_printing){ + // if M600 was invoked by filament senzor (FINDA) eject filament so user can easily remove it + MMU2::mmu2.eject_filament(MMU2::mmu2.get_current_tool(), false); + } + mmu_M600_wait_and_beep(); + if (saved_printing) { + lcd_clear(); + lcd_puts_at_P(0, 2, _T(MSG_PLEASE_WAIT)); +//@@TODO mmu_command(MmuCmd::R0); +// manage_response(false, false); + } + } + mmu_M600_load_filament(automatic, HotendTempBckp); + } + if (!automatic) + M600_check_state(HotendTempBckp); + + lcd_update_enable(true); + + // Not let's go back to print + fanSpeed = fanSpeedBckp; + + // Feed a little of filament to stabilize pressure + if (!automatic) { + current_position[E_AXIS] += FILAMENTCHANGE_RECFEED; + plan_buffer_line_curposXYZE(FILAMENTCHANGE_EXFEED); + } + + // Move XY back + plan_buffer_line(lastpos[X_AXIS], lastpos[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], FILAMENTCHANGE_XYFEED); + st_synchronize(); + + // Move Z back + plan_buffer_line(lastpos[X_AXIS], lastpos[Y_AXIS], lastpos[Z_AXIS], current_position[E_AXIS], FILAMENTCHANGE_ZFEED); + st_synchronize(); + + // Set E position to original + plan_set_e_position(lastpos[E_AXIS]); + + memcpy(current_position, lastpos, sizeof(lastpos)); + set_destination_to_current(); + + // Recover feed rate + feedmultiply = feedmultiplyBckp; + char cmd[9]; + sprintf_P(cmd, PSTR("M220 S%i"), feedmultiplyBckp); + enquecommand(cmd); + } - else - M600_load_filament(); - - if (!automatic) M600_check_state(HotendTempBckp); - - lcd_update_enable(true); - - //Not let's go back to print - fanSpeed = fanSpeedBckp; - - //Feed a little of filament to stabilize pressure - if (!automatic) - { - current_position[E_AXIS] += FILAMENTCHANGE_RECFEED; - plan_buffer_line_curposXYZE(FILAMENTCHANGE_EXFEED, active_extruder); - } - - //Move XY back - plan_buffer_line(lastpos[X_AXIS], lastpos[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], - FILAMENTCHANGE_XYFEED, active_extruder); - st_synchronize(); - //Move Z back - plan_buffer_line(lastpos[X_AXIS], lastpos[Y_AXIS], lastpos[Z_AXIS], current_position[E_AXIS], - FILAMENTCHANGE_ZFEED, active_extruder); - st_synchronize(); - - //Set E position to original - plan_set_e_position(lastpos[E_AXIS]); - - memcpy(current_position, lastpos, sizeof(lastpos)); - memcpy(destination, current_position, sizeof(current_position)); - - //Recover feed rate - feedmultiply = feedmultiplyBckp; - char cmd[9]; - sprintf_P(cmd, PSTR("M220 S%i"), feedmultiplyBckp); - enquecommand(cmd); - -#ifdef IR_SENSOR - //this will set fsensor_watch_autoload to correct value and prevent possible M701 gcode enqueuing when M600 is finished - fsensor_check_autoload(); -#endif //IR_SENSOR - - lcd_setstatuspgm(_T(WELCOME_MSG)); + + lcd_setstatuspgm(MSG_WELCOME); custom_message_type = CustomMsg::Status; } -void gcode_M701() -{ - printf_P(PSTR("gcode_M701 begin\n")); +void gcode_M701(float fastLoadLength, uint8_t mmuSlotIndex){ + FSensorBlockRunout fsBlockRunout; + + prusa_statistics(22); - if (farm_mode) - { - prusa_statistics(22); - } + if (MMU2::mmu2.Enabled() && mmuSlotIndex < MMU_FILAMENT_COUNT) { + MMU2::mmu2.load_filament_to_nozzle(mmuSlotIndex); + } else { + custom_message_type = CustomMsg::FilamentLoading; + lcd_setstatuspgm(_T(MSG_LOADING_FILAMENT)); - if (mmu_enabled) - { - extr_adj(tmp_extruder);//loads current extruder - mmu_extruder = tmp_extruder; - } - else - { - enable_z(); - custom_message_type = CustomMsg::FilamentLoading; + current_position[E_AXIS] += fastLoadLength; + plan_buffer_line_curposXYZE(FILAMENTCHANGE_EFEED_FIRST); //fast sequence -#ifdef FSENSOR_QUALITY - fsensor_oq_meassure_start(40); -#endif //FSENSOR_QUALITY + load_filament_final_feed(); // slow sequence + st_synchronize(); - lcd_setstatuspgm(_T(MSG_LOADING_FILAMENT)); - current_position[E_AXIS] += 40; - plan_buffer_line_curposXYZE(400 / 60, active_extruder); //fast sequence - st_synchronize(); + Sound_MakeCustom(50, 500, false); - raise_z_above(MIN_Z_FOR_LOAD, false); - current_position[E_AXIS] += 30; - plan_buffer_line_curposXYZE(400 / 60, active_extruder); //fast sequence - - load_filament_final_feed(); //slow sequence - st_synchronize(); - - Sound_MakeCustom(50,500,false); - - if (!farm_mode && loading_flag) { - lcd_load_filament_color_check(); - } - lcd_update_enable(true); - lcd_update(2); - lcd_setstatuspgm(_T(WELCOME_MSG)); - disable_z(); - loading_flag = false; - custom_message_type = CustomMsg::Status; - -#ifdef FSENSOR_QUALITY - fsensor_oq_meassure_stop(); - - if (!fsensor_oq_result()) - { - bool disable = lcd_show_fullscreen_message_yes_no_and_wait_P(_i("Fil. sensor response is poor, disable it?"), false, true); - lcd_update_enable(true); - lcd_update(2); - if (disable) - fsensor_disable(); + if (!farm_mode && loading_flag) { + lcd_load_filament_color_check(); } -#endif //FSENSOR_QUALITY - } + lcd_update_enable(true); + lcd_update(2); + lcd_setstatuspgm(MSG_WELCOME); + loading_flag = false; + custom_message_type = CustomMsg::Status; + } + + eFilamentAction = FilamentAction::None; } + +// Common gcode shared by the gcodes. This saves some flash memory +static void gcodes_M704_M705_M706(uint16_t gcode) +{ + uint8_t mmuSlotIndex = 0xffU; + if (MMU2::mmu2.Enabled() && code_seen('P')) + { + mmuSlotIndex = code_value_uint8(); + if (mmuSlotIndex < MMU_FILAMENT_COUNT) { + switch (gcode) + { + case 704: + MMU2::mmu2.load_filament(mmuSlotIndex); + break; + case 705: + MMU2::mmu2.eject_filament(mmuSlotIndex, false); + break; + case 706: +#ifdef MMU_HAS_CUTTER + if (eeprom_read_byte((uint8_t*)EEPROM_MMU_CUTTER_ENABLED) != 0){ + MMU2::mmu2.cut_filament(mmuSlotIndex); + } +#endif // MMU_HAS_CUTTER + break; + default: + break; + } + } + } +} + /** * @brief Get serial number from 32U2 processor * * Typical format of S/N is:CZPX0917X003XC13518 * - * Command operates only in farm mode, if not in farm mode, "Not in farm mode." is written to MYSERIAL. - * * Send command ;S to serial port 0 to retrieve serial number stored in 32U2 processor, - * reply is transmitted to serial port 1 character by character. - * Operation takes typically 23 ms. If the retransmit is not finished until 100 ms, - * it is interrupted, so less, or no characters are retransmitted, only newline character is send - * in any case. + * reply is stored in *SN. + * Operation takes typically 23 ms. If no valid SN can be retrieved within the 250ms window, the function aborts + * and returns a general failure flag. + * The command will fail if the 32U2 processor is unpowered via USB since it is isolated from the rest of the electronics. + * In that case the value that is stored in the EEPROM should be used instead. + * + * @return 0 on success + * @return 1 on general failure */ -static void gcode_PRUSA_SN() +#ifdef PRUSA_SN_SUPPORT +static uint8_t get_PRUSA_SN(char* SN) { - if (farm_mode) { - selectedSerialPort = 0; - putchar(';'); - putchar('S'); - int numbersRead = 0; - ShortTimer timeout; - timeout.start(); + uint8_t selectedSerialPort_bak = selectedSerialPort; + uint8_t rxIndex; + bool SN_valid = false; + ShortTimer timeout; - while (numbersRead < 19) { - while (MSerial.available() > 0) { - uint8_t serial_char = MSerial.read(); - selectedSerialPort = 1; - putchar(serial_char); - numbersRead++; - selectedSerialPort = 0; + selectedSerialPort = 0; + timeout.start(); + + while (!SN_valid) + { + rxIndex = 0; + _delay(50); + MYSERIAL.flush(); //clear RX buffer + SERIAL_ECHOLNRPGM(PSTR(";S")); + while (rxIndex < 19) + { + if (timeout.expired(250u)) + goto exit; + if (MYSERIAL.available() > 0) + { + SN[rxIndex] = MYSERIAL.read(); + rxIndex++; } - if (timeout.expired(100u)) break; } - selectedSerialPort = 1; - putchar('\n'); -#if 0 - for (int b = 0; b < 3; b++) { - _tone(BEEPER, 110); - _delay(50); - _noTone(BEEPER); - _delay(50); - } -#endif - } else { - puts_P(_N("Not in farm mode.")); + SN[rxIndex] = 0; + // printf_P(PSTR("SN:%s\n"), SN); + SN_valid = (strncmp_P(SN, PSTR("CZPX"), 4) == 0); } +exit: + selectedSerialPort = selectedSerialPort_bak; + return !SN_valid; } +#endif //PRUSA_SN_SUPPORT + //! Detection of faulty RAMBo 1.1b boards equipped with bigger capacitors //! at the TACH_1 pin, which causes bad detection of print fan speed. //! Warning: This function is not to be used by ordinary users, it is here only for automated testing purposes, @@ -3393,11 +3796,31 @@ static void gcode_G92() current_position[E_AXIS] = values[E_AXIS]; // Set all at once - plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], - current_position[Z_AXIS], current_position[E_AXIS]); + plan_set_position_curposXYZE(); } } +#ifdef EXTENDED_CAPABILITIES_REPORT + +static void cap_line(const char* name, bool ena = false) { + printf_P(PSTR("Cap:%S:%c\n"), name, (char)ena + '0'); +} + +static void extended_capabilities_report() +{ + // AUTOREPORT_TEMP (M155) + cap_line(PSTR("AUTOREPORT_TEMP"), ENABLED(AUTO_REPORT)); +#if (defined(FANCHECK) && (((defined(TACH_0) && (TACH_0 >-1)) || (defined(TACH_1) && (TACH_1 > -1))))) + // AUTOREPORT_FANS (M123) + cap_line(PSTR("AUTOREPORT_FANS"), ENABLED(AUTO_REPORT)); +#endif //FANCHECK and TACH_0 or TACH_1 + // AUTOREPORT_POSITION (M114) + cap_line(PSTR("AUTOREPORT_POSITION"), ENABLED(AUTO_REPORT)); + // EXTENDED_M20 (support for L and T parameters) + cap_line(PSTR("EXTENDED_M20"), 1); + cap_line(PSTR("PRUSA_MMU2"), 1); //this will soon change to ENABLED(PRUSA_MMU2_SUPPORT) +} +#endif //EXTENDED_CAPABILITIES_REPORT #ifdef BACKLASH_X extern uint8_t st_backlash_x; @@ -3485,12 +3908,14 @@ extern uint8_t st_backlash_y; //!@n M115 - Capabilities string //!@n M117 - display message //!@n M119 - Output Endstop status to serial port +//!@n M123 - Tachometer value //!@n M126 - Solenoid Air Valve Open (BariCUDA support by jmil) //!@n M127 - Solenoid Air Valve Closed (BariCUDA vent to atmospheric pressure by jmil) //!@n M128 - EtoP Open (BariCUDA EtoP = electricity to air pressure transducer by jmil) //!@n M129 - EtoP Closed (BariCUDA EtoP = electricity to air pressure transducer by jmil) //!@n M140 - Set bed target temp //!@n M150 - Set BlinkM Color Output R: Red<0-255> U(!): Green<0-255> B: Blue<0-255> over i2c, G for green does not work. +//!@n M155 - Automatically send temperatures, fan speeds, position //!@n M190 - Sxxx Wait for bed current temp to reach target temp. Waits only when heating //! Rxxx Wait for bed current temp to reach target temp. Waits when heating and cooling //!@n M200 D- set filament diameter and set E axis units to cubic millimeters (use S0 to set back to millimeters). @@ -3503,7 +3928,7 @@ extern uint8_t st_backlash_y; //!@n M207 - set retract length S[positive mm] F[feedrate mm/min] Z[additional zlift/hop], stays in mm regardless of M200 setting //!@n M208 - set recover=unretract length S[positive mm surplus to the M207 S*] F[feedrate mm/sec] //!@n M209 - S<1=true/0=false> enable automatic retract detect if the slicer did not support G10/11: every normal extrude-only move will be classified as retract depending on the direction. -//!@n M218 - set hotend offset (in mm): T X Y +//!@n M214 - Set Arc Parameters (Use M500 to store in eeprom) P S R F //!@n M220 S- set speed factor override percentage //!@n M221 S- set extrude factor override percentage //!@n M226 P S- Wait until the specified pin reaches the state required @@ -3515,6 +3940,7 @@ extern uint8_t st_backlash_y; //!@n M302 - Allow cold extrudes, or set the minimum extrude S. //!@n M303 - PID relay autotune S sets the target temperature. (default target temperature = 150C) //!@n M304 - Set bed PID parameters P I and D +//!@n M310 - Temperature model settings //!@n M400 - Finish all moves //!@n M401 - Lower z-probe if present //!@n M402 - Raise z-probe if present @@ -3528,6 +3954,7 @@ extern uint8_t st_backlash_y; //!@n M503 - print the current settings (from memory not from EEPROM) //!@n M509 - force language selection on next restart //!@n M540 - Use S[0|1] to enable or disable the stop SD card print on endstop hit (requires ABORT_ON_ENDSTOP_HIT_FEATURE_ENABLED) +//!@n M552 - Set IP address //!@n M600 - Pause for filament change X[pos] Y[pos] Z[relative lift] E[initial retract] L[later retract distance for removal] //!@n M605 - Set dual x-carriage movement mode: S [ X R ] //!@n M850 - Set sheet data S[id] Z[offset] L[label] B[bed_temp] P[PINDA_TEMP] @@ -3556,21 +3983,7 @@ There are reasons why some G Codes aren't in numerical order. void process_commands() { -#ifdef FANCHECK - if(fan_check_error){ - if(fan_check_error == EFCE_DETECTED){ - fan_check_error = EFCE_REPORTED; - // SERIAL_PROTOCOLLNRPGM(MSG_OCTOPRINT_PAUSED); - lcd_pause_print(); - } // otherwise it has already been reported, so just ignore further processing - return; //ignore usb stream. It is reenabled by selecting resume from the lcd. - } -#endif - if (!buflen) return; //empty command - #ifdef FILAMENT_RUNOUT_SUPPORT - SET_INPUT(FR_SENS); - #endif #ifdef CMDBUFFER_DEBUG SERIAL_ECHOPGM("Processing a GCODE command: "); @@ -3582,52 +3995,32 @@ void process_commands() #endif /* CMDBUFFER_DEBUG */ unsigned long codenum; //throw away variable - char *starpos = NULL; #ifdef ENABLE_AUTO_BED_LEVELING float x_tmp, y_tmp, z_tmp, real_z; #endif // PRUSA GCODES KEEPALIVE_STATE(IN_HANDLER); - -#ifdef SNMM - float tmp_motor[3] = DEFAULT_PWM_MOTOR_CURRENT; - float tmp_motor_loud[3] = DEFAULT_PWM_MOTOR_CURRENT_LOUD; - int8_t SilentMode; -#endif - /*! - - --------------------------------------------------------------------------------- - ### M117 - Display Message M117: Display Message - This causes the given message to be shown in the status line on an attached LCD. - It is processed early as to allow printing messages that contain G, M, N or T. - - --------------------------------------------------------------------------------- - ### Special internal commands - These are used by internal functions to process certain actions in the right order. Some of these are also usable by the user. - They are processed early as the commands are complex (strings). - These are only available on the MK3(S) as these require TMC2130 drivers: - - CRASH DETECTED - - CRASH RECOVER - - CRASH_CANCEL - - TMC_SET_WAVE - - TMC_SET_STEP - - TMC_SET_CHOP - */ - if (code_seen("M117")) { //moved to highest priority place to be able to to print strings which includes "G", "PRUSA" and "^" - starpos = (strchr(strchr_pointer + 5, '*')); - if (starpos != NULL) - *(starpos) = '\0'; - lcd_setstatus(strchr_pointer + 5); - } - + /*! + ### Special internal commands + These are used by internal functions to process certain actions in the right order. Some of these are also usable by the user. + They are processed early as the commands are complex (strings). + These are only available on the MK3(S) as these require TMC2130 drivers: + - CRASH DETECTED + - CRASH RECOVER + - CRASH_CANCEL + - TMC_SET_WAVE + - TMC_SET_STEP + - TMC_SET_CHOP + */ + if (false) {} // allow chaining of optional next else if blocks #ifdef TMC2130 else if (strncmp_P(CMDBUFFER_CURRENT_STRING, PSTR("CRASH_"), 6) == 0) { // ### CRASH_DETECTED - TMC2130 // --------------------------------- - if(code_seen("CRASH_DETECTED")) + if(code_seen_P(PSTR("CRASH_DETECTED"))) { uint8_t mask = 0; if (code_seen('X')) mask |= X_AXIS_MASK; @@ -3637,12 +4030,12 @@ void process_commands() // ### CRASH_RECOVER - TMC2130 // ---------------------------------- - else if(code_seen("CRASH_RECOVER")) + else if(code_seen_P(PSTR("CRASH_RECOVER"))) crashdet_recover(); // ### CRASH_CANCEL - TMC2130 // ---------------------------------- - else if(code_seen("CRASH_CANCEL")) + else if(code_seen_P(PSTR("CRASH_CANCEL"))) crashdet_cancel(); } else if (strncmp_P(CMDBUFFER_CURRENT_STRING, PSTR("TMC_"), 4) == 0) @@ -3707,7 +4100,7 @@ void process_commands() tmc2130_chopper_config[axis].hend = chop2 & 15; tmc2130_chopper_config[axis].tbl = chop3 & 3; tmc2130_setup_chopper(axis, tmc2130_mres[axis], tmc2130_current_h[axis], tmc2130_current_r[axis]); - //printf_P(_N("TMC_SET_CHOP_%c %hhd %hhd %hhd %hhd\n"), "xyze"[axis], chop0, chop1, chop2, chop3); + //printf_P(_N("TMC_SET_CHOP_%c %d %d %d %d\n"), "xyze"[axis], chop0, chop1, chop2, chop3); } } } @@ -3716,7 +4109,7 @@ void process_commands() { uint8_t bl = (uint8_t)strtol(CMDBUFFER_CURRENT_STRING + 10, NULL, 10); st_backlash_x = bl; - printf_P(_N("st_backlash_x = %hhd\n"), st_backlash_x); + printf_P(_N("st_backlash_x = %d\n"), st_backlash_x); } #endif //BACKLASH_X #ifdef BACKLASH_Y @@ -3724,24 +4117,22 @@ void process_commands() { uint8_t bl = (uint8_t)strtol(CMDBUFFER_CURRENT_STRING + 10, NULL, 10); st_backlash_y = bl; - printf_P(_N("st_backlash_y = %hhd\n"), st_backlash_y); + printf_P(_N("st_backlash_y = %d\n"), st_backlash_y); } #endif //BACKLASH_Y #endif //TMC2130 - else if(code_seen("PRUSA")){ + else if(strncmp_P(CMDBUFFER_CURRENT_STRING, PSTR("PRUSA"), 5) == 0) { /*! --------------------------------------------------------------------------------- ### PRUSA - Internal command set G98: Activate farm mode - Notes Set of internal PRUSA commands #### Usage - PRUSA [ Ping | PRN | FAN | fn | thx | uvlo | MMURES | RESET | fv | M28 | SN | Fir | Rev | Lang | Lz | Beat | FR ] + PRUSA [ PRN | FAN | thx | uvlo | MMURES | RESET | fv | M28 | SN | Fir | Rev | Lang | Lz | FR ] #### Parameters - - `Ping` - `PRN` - Prints revision of the printer - `FAN` - Prints fan details - - `fn` - Prints farm no. - `thx` - `uvlo` - `MMURES` - Reset MMU @@ -3753,133 +4144,78 @@ void process_commands() - `Rev`- Prints filament size, elelectronics, nozzle type - `Lang` - Reset the language - `Lz` - - `Beat` - Kick farm link timer - `FR` - Full factory reset - `nozzle set ` - set nozzle diameter (farm mode only), e.g. `PRUSA nozzle set 0.4` - `nozzle D` - check the nozzle diameter (farm mode only), works like M862.1 P, e.g. `PRUSA nozzle D0.4` - `nozzle` - prints nozzle diameter (farm mode only), works like M862.1 P, e.g. `PRUSA nozzle` */ - - if (code_seen("Ping")) { // PRUSA Ping - if (farm_mode) { - PingTime = _millis(); - //MYSERIAL.print(farm_no); MYSERIAL.println(": OK"); - } - } - else if (code_seen("PRN")) { // PRUSA PRN - printf_P(_N("%d"), status_number); - - } else if( code_seen("FANPINTST") ){ + if (farm_prusa_code_seen()) {} + else if(code_seen_P(PSTR("FANPINTST"))) { gcode_PRUSA_BadRAMBoFanTest(); - }else if (code_seen("FAN")) { // PRUSA FAN - printf_P(_N("E0:%d RPM\nPRN0:%d RPM\n"), 60*fan_speed[0], 60*fan_speed[1]); - }else if (code_seen("fn")) { // PRUSA fn - if (farm_mode) { - printf_P(_N("%d"), farm_no); - } - else { - puts_P(_N("Not in farm mode.")); - } - - } - else if (code_seen("thx")) // PRUSA thx - { - no_response = false; - } - else if (code_seen("uvlo")) // PRUSA uvlo - { - eeprom_update_byte((uint8_t*)EEPROM_UVLO,0); - enquecommand_P(PSTR("M24")); - } - else if (code_seen("MMURES")) // PRUSA MMURES - { - mmu_reset(); - } - else if (code_seen("RESET")) { // PRUSA RESET - // careful! - if (farm_mode) { -#if (defined(WATCHDOG) && (MOTHERBOARD == BOARD_EINSY_1_0a)) - boot_app_magic = BOOT_APP_MAGIC; - boot_app_flags = BOOT_APP_FLG_RUN; - wdt_enable(WDTO_15MS); - cli(); - while(1); -#else //WATCHDOG - asm volatile("jmp 0x3E000"); -#endif //WATCHDOG - } - else { - MYSERIAL.println("Not in farm mode."); - } - }else if (code_seen("fv")) { // PRUSA fv - // get file version - #ifdef SDSUPPORT - card.openFile(strchr_pointer + 3,true); - while (true) { - uint16_t readByte = card.get(); - MYSERIAL.write(readByte); - if (readByte=='\n') { - break; - } } - card.closefile(); + else if (code_seen_P(PSTR("FAN"))) { // PRUSA FAN + printf_P(_N("E0:%d RPM\nPRN0:%d RPM\n"), 60*fan_speed[0], 60*fan_speed[1]); + } + else if (code_seen_P(PSTR("uvlo"))) { // PRUSA uvlo + eeprom_update_byte((uint8_t*)EEPROM_UVLO,0); + enquecommand_P(PSTR("M24")); + } + else if (code_seen_P(PSTR("MMURES"))) // PRUSA MMURES + { + MMU2::mmu2.Reset(MMU2::MMU2::Software); + } + else if (code_seen_P(PSTR("RESET"))) { // PRUSA RESET +#if defined(XFLASH) && defined(BOOTAPP) + boot_app_magic = BOOT_APP_MAGIC; + boot_app_flags = BOOT_APP_FLG_RUN; +#endif //defined(XFLASH) && defined(BOOTAPP) + softReset(); + } + else if (code_seen_P(PSTR("SN"))) { // PRUSA SN + char SN[20]; + eeprom_read_block(SN, (uint8_t*)EEPROM_PRUSA_SN, 20); + if (SN[19]) + puts_P(PSTR("SN invalid")); + else + puts(SN); + } + else if(code_seen_P(PSTR("Fir"))){ // PRUSA Fir - #endif // SDSUPPORT + SERIAL_PROTOCOLLNPGM(FW_VERSION_FULL); - } else if (code_seen("M28")) { // PRUSA M28 - trace(); - prusa_sd_card_upload = true; - card.openFile(strchr_pointer+4,false); + } else if(code_seen_P(PSTR("Rev"))){ // PRUSA Rev - } else if (code_seen("SN")) { // PRUSA SN - gcode_PRUSA_SN(); + SERIAL_PROTOCOLLNPGM(FILAMENT_SIZE "-" ELECTRONICS "-" NOZZLE_TYPE ); - } else if(code_seen("Fir")){ // PRUSA Fir - - SERIAL_PROTOCOLLN(FW_VERSION_FULL); - - } else if(code_seen("Rev")){ // PRUSA Rev - - SERIAL_PROTOCOLLN(FILAMENT_SIZE "-" ELECTRONICS "-" NOZZLE_TYPE ); - - } else if(code_seen("Lang")) { // PRUSA Lang + } else if(code_seen_P(PSTR("Lang"))) { // PRUSA Lang lang_reset(); - } else if(code_seen("Lz")) { // PRUSA Lz + } else if(code_seen_P(PSTR("Lz"))) { // PRUSA Lz eeprom_update_word(reinterpret_cast(&(EEPROM_Sheets_base->s[(eeprom_read_byte(&(EEPROM_Sheets_base->active_sheet)))].z_offset)),0); - } else if(code_seen("Beat")) { // PRUSA Beat - // Kick farm link timer - kicktime = _millis(); - - } else if(code_seen("FR")) { // PRUSA FR + } else if(code_seen_P(PSTR("FR"))) { // PRUSA FR // Factory full reset factory_reset(0); - -//-// -/* - } else if(code_seen("rrr")) { -MYSERIAL.println("=== checking ==="); -MYSERIAL.println(eeprom_read_byte((uint8_t*)EEPROM_CHECK_MODE),DEC); -MYSERIAL.println(eeprom_read_byte((uint8_t*)EEPROM_NOZZLE_DIAMETER),DEC); -MYSERIAL.println(eeprom_read_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM),DEC); -MYSERIAL.println(farm_mode,DEC); -MYSERIAL.println(eCheckMode,DEC); - } else if(code_seen("www")) { -MYSERIAL.println("=== @ FF ==="); -eeprom_update_byte((uint8_t*)EEPROM_CHECK_MODE,0xFF); -eeprom_update_byte((uint8_t*)EEPROM_NOZZLE_DIAMETER,0xFF); -eeprom_update_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM,0xFFFF); -*/ - } else if (code_seen("nozzle")) { // PRUSA nozzle + } else if(code_seen_P(PSTR("MBL"))) { // PRUSA MBL + // Change the MBL status without changing the logical Z position. + if(code_seen('V')) { + bool value = code_value_short(); + st_synchronize(); + if(value != mbl.active) { + mbl.active = value; + // Use plan_set_z_position to reset the physical values + plan_set_z_position(current_position[Z_AXIS]); + } + } + } else if (code_seen_P(PSTR("nozzle"))) { // PRUSA nozzle uint16_t nDiameter; if(code_seen('D')) { nDiameter=(uint16_t)(code_value()*1000.0+0.5); // [,um] nozzle_diameter_check(nDiameter); } - else if(code_seen("set") && farm_mode) + else if(code_seen_P(PSTR("set")) && farm_mode) { strchr_pointer++; // skip 1st char (~ 's') strchr_pointer++; // skip 2nd char (~ 'e') @@ -3888,55 +4224,12 @@ eeprom_update_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM,0xFFFF); eeprom_update_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM,nDiameter); } else SERIAL_PROTOCOLLN((float)eeprom_read_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM)/1000.0); - -//-// !!! SupportMenu -/* -// musi byt PRED "PRUSA model" - } else if (code_seen("smodel")) { //! PRUSA smodel - size_t nOffset; -// ! -> "l" - strchr_pointer+=5*sizeof(*strchr_pointer); // skip 1st - 5th char (~ 'smode') - nOffset=strspn(strchr_pointer+1," \t\n\r\v\f"); - if(*(strchr_pointer+1+nOffset)) - printer_smodel_check(strchr_pointer); - else SERIAL_PROTOCOLLN(PRINTER_NAME); - } else if (code_seen("model")) { //! PRUSA model - uint16_t nPrinterModel; - strchr_pointer+=4*sizeof(*strchr_pointer); // skip 1st - 4th char (~ 'mode') - nPrinterModel=(uint16_t)code_value_long(); - if(nPrinterModel!=0) - printer_model_check(nPrinterModel); - else SERIAL_PROTOCOLLN(PRINTER_TYPE); - } else if (code_seen("version")) { //! PRUSA version - strchr_pointer+=7*sizeof(*strchr_pointer); // skip 1st - 7th char (~ 'version') - while(*strchr_pointer==' ') // skip leading spaces - strchr_pointer++; - if(*strchr_pointer!=0) - fw_version_check(strchr_pointer); - else SERIAL_PROTOCOLLN(FW_VERSION); - } else if (code_seen("gcode")) { //! PRUSA gcode - uint16_t nGcodeLevel; - strchr_pointer+=4*sizeof(*strchr_pointer); // skip 1st - 4th char (~ 'gcod') - nGcodeLevel=(uint16_t)code_value_long(); - if(nGcodeLevel!=0) - gcode_level_check(nGcodeLevel); - else SERIAL_PROTOCOLLN(GCODE_LEVEL); -*/ - } - //else if (code_seen('Cal')) { - // lcd_calibration(); - // } - - } - // This prevents reading files with "^" in their names. - // Since it is unclear, if there is some usage of this construct, - // it will be deprecated in 3.9 alpha a possibly completely removed in the future: - // else if (code_seen('^')) { - // // nothing, this is a version line - // } - else if(code_seen('G')) + } + } + else if(*CMDBUFFER_CURRENT_STRING == 'G') { - gcode_in_progress = (int)code_value(); + strchr_pointer = CMDBUFFER_CURRENT_STRING; + gcode_in_progress = code_value_short(); // printf_P(_N("BEGIN G-CODE=%u\n"), gcode_in_progress); switch (gcode_in_progress) { @@ -3961,209 +4254,30 @@ eeprom_update_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM,0xFFFF); */ case 0: // G0 -> G1 case 1: // G1 - if(Stopped == false) { - - #ifdef FILAMENT_RUNOUT_SUPPORT - - if(READ(FR_SENS)){ - - int feedmultiplyBckp=feedmultiply; - float target[4]; - float lastpos[4]; - target[X_AXIS]=current_position[X_AXIS]; - target[Y_AXIS]=current_position[Y_AXIS]; - target[Z_AXIS]=current_position[Z_AXIS]; - target[E_AXIS]=current_position[E_AXIS]; - lastpos[X_AXIS]=current_position[X_AXIS]; - lastpos[Y_AXIS]=current_position[Y_AXIS]; - lastpos[Z_AXIS]=current_position[Z_AXIS]; - lastpos[E_AXIS]=current_position[E_AXIS]; - //retract by E - - target[E_AXIS]+= FILAMENTCHANGE_FIRSTRETRACT ; - - plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], 400, active_extruder); - - - target[Z_AXIS]+= FILAMENTCHANGE_ZADD ; - - plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], 300, active_extruder); - - target[X_AXIS]= FILAMENTCHANGE_XPOS ; - - target[Y_AXIS]= FILAMENTCHANGE_YPOS ; - - - plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], 70, active_extruder); - - target[E_AXIS]+= FILAMENTCHANGE_FINALRETRACT ; - - - plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], 20, active_extruder); - - //finish moves - st_synchronize(); - //disable extruder steppers so filament can be removed - disable_e0(); - disable_e1(); - disable_e2(); - _delay(100); - - //LCD_ALERTMESSAGEPGM(_T(MSG_FILAMENTCHANGE)); - uint8_t cnt=0; - int counterBeep = 0; - lcd_wait_interact(); - while(!lcd_clicked()){ - cnt++; - manage_heater(); - manage_inactivity(true); - //lcd_update(0); - if(cnt==0) - { - #if BEEPER > 0 - - if (counterBeep== 500){ - counterBeep = 0; - - } - - - SET_OUTPUT(BEEPER); - if (counterBeep== 0){ -if(eSoundMode!=e_SOUND_MODE_SILENT) - WRITE(BEEPER,HIGH); - } - - if (counterBeep== 20){ - WRITE(BEEPER,LOW); - } - - - - - counterBeep++; - #else - #endif - } - } - - WRITE(BEEPER,LOW); - - target[E_AXIS]+= FILAMENTCHANGE_FIRSTFEED ; - plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], 20, active_extruder); - - - target[E_AXIS]+= FILAMENTCHANGE_FINALFEED ; - plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], 2, active_extruder); - - - - - - lcd_change_fil_state = 0; - lcd_loading_filament(); - while ((lcd_change_fil_state == 0)||(lcd_change_fil_state != 1)){ - - lcd_change_fil_state = 0; - lcd_alright(); - switch(lcd_change_fil_state){ - - case 2: - target[E_AXIS]+= FILAMENTCHANGE_FIRSTFEED ; - plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], 20, active_extruder); - - - target[E_AXIS]+= FILAMENTCHANGE_FINALFEED ; - plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], 2, active_extruder); - - - lcd_loading_filament(); - break; - case 3: - target[E_AXIS]+= FILAMENTCHANGE_FINALFEED ; - plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], 2, active_extruder); - lcd_loading_color(); - break; - - default: - lcd_change_success(); - break; - } - - } - - - - target[E_AXIS]+= 5; - plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], 2, active_extruder); - - target[E_AXIS]+= FILAMENTCHANGE_FIRSTRETRACT; - plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], 400, active_extruder); - - - //current_position[E_AXIS]=target[E_AXIS]; //the long retract of L is compensated by manual filament feeding - //plan_set_e_position(current_position[E_AXIS]); - plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], 70, active_extruder); //should do nothing - plan_buffer_line(lastpos[X_AXIS], lastpos[Y_AXIS], target[Z_AXIS], target[E_AXIS], 70, active_extruder); //move xy back - plan_buffer_line(lastpos[X_AXIS], lastpos[Y_AXIS], lastpos[Z_AXIS], target[E_AXIS], 200, active_extruder); //move z back - - - target[E_AXIS]= target[E_AXIS] - FILAMENTCHANGE_FIRSTRETRACT; - - - - plan_buffer_line(lastpos[X_AXIS], lastpos[Y_AXIS], lastpos[Z_AXIS], target[E_AXIS], 5, active_extruder); //final untretract - - - plan_set_e_position(lastpos[E_AXIS]); - - feedmultiply=feedmultiplyBckp; - - - - char cmd[9]; - - sprintf_P(cmd, PSTR("M220 S%i"), feedmultiplyBckp); - enquecommand(cmd); - - } - - - - #endif - - get_coordinates(); // For X Y Z E F - - // When recovering from a previous print move, restore the originally - // calculated target position on the first USB/SD command. This accounts - // properly for relative moves - if ((saved_target[0] != SAVED_TARGET_UNSET) && - ((CMDBUFFER_CURRENT_TYPE == CMDBUFFER_CURRENT_TYPE_SDCARD) || - (CMDBUFFER_CURRENT_TYPE == CMDBUFFER_CURRENT_TYPE_USB_WITH_LINENR))) - { - memcpy(destination, saved_target, sizeof(destination)); - saved_target[0] = SAVED_TARGET_UNSET; - } + { + uint16_t start_segment_idx = restore_interrupted_gcode(); + get_coordinates(); // For X Y Z E F if (total_filament_used > ((current_position[E_AXIS] - destination[E_AXIS]) * 100)) { //protection against total_filament_used overflow total_filament_used = total_filament_used + ((destination[E_AXIS] - current_position[E_AXIS]) * 100); } - #ifdef FWRETRACT - if(cs.autoretract_enabled) + +#ifdef FWRETRACT + if(cs.autoretract_enabled) { if( !(code_seen('X') || code_seen('Y') || code_seen('Z')) && code_seen('E')) { - float echange=destination[E_AXIS]-current_position[E_AXIS]; - - if((echange<-MIN_RETRACT && !retracted[active_extruder]) || (echange>MIN_RETRACT && retracted[active_extruder])) { //move appears to be an attempt to retract or recover - current_position[E_AXIS] = destination[E_AXIS]; //hide the slicer-generated retract/recover from calculations - plan_set_e_position(current_position[E_AXIS]); //AND from the planner - retract(!retracted[active_extruder]); - return; - } - - + float echange=destination[E_AXIS]-current_position[E_AXIS]; + if((echange<-MIN_RETRACT && !retracted[active_extruder]) || (echange>MIN_RETRACT && retracted[active_extruder])) { //move appears to be an attempt to retract or recover + st_synchronize(); + current_position[E_AXIS] = destination[E_AXIS]; //hide the slicer-generated retract/recover from calculations + plan_set_e_position(current_position[E_AXIS]); //AND from the planner + retract(!retracted[active_extruder]); + return; + } } - #endif //FWRETRACT - prepare_move(); + } +#endif //FWRETRACT + + prepare_move(start_segment_idx); //ClearToSend(); } break; @@ -4181,27 +4295,31 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) #### Parameters - `X` - The position to move to on the X axis - `Y` - The position to move to on the Y axis + - 'Z' - The position to move to on the Z axis - `I` - The point in X space from the current X position to maintain a constant distance from - `J` - The point in Y space from the current Y position to maintain a constant distance from - `E` - The amount to extrude between the starting point and ending point - `F` - The feedrate per minute of the move between the starting point and ending point (if supplied) */ - case 2: - if(Stopped == false) { - get_arc_coordinates(); - prepare_arc_move(true); - } - break; - - // ------------------------------- - case 3: - if(Stopped == false) { - get_arc_coordinates(); - prepare_arc_move(false); - } - break; + case 2: + case 3: + { + uint16_t start_segment_idx = restore_interrupted_gcode(); +#ifdef SF_ARC_FIX + bool relative_mode_backup = relative_mode; + relative_mode = true; +#endif + get_coordinates(); // For X Y Z E F +#ifdef SF_ARC_FIX + relative_mode=relative_mode_backup; +#endif + offset[0] = code_seen('I') ? code_value() : 0.f; + offset[1] = code_seen('J') ? code_value() : 0.f; + + prepare_arc_move((gcode_in_progress == 2), start_segment_idx); + } break; /*! ### G4 - Dwell G4: Dwell @@ -4220,19 +4338,25 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) codenum = 0; if(code_seen('P')) codenum = code_value(); // milliseconds to wait if(code_seen('S')) codenum = code_value() * 1000; // seconds to wait - if(codenum != 0) LCD_MESSAGERPGM(_n("Sleep..."));////MSG_DWELL + if(codenum != 0) + { + if(custom_message_type != CustomMsg::M117) + { + LCD_MESSAGERPGM(_n("Sleep..."));////MSG_DWELL + } + } st_synchronize(); codenum += _millis(); // keep track of when we started waiting - previous_millis_cmd = _millis(); + previous_millis_cmd.start(); while(_millis() < codenum) { manage_heater(); manage_inactivity(); lcd_update(0); } break; - #ifdef FWRETRACT - + +#ifdef FWRETRACT /*! ### G10 - Retract G10: Retract Retracts filament according to settings of `M207` @@ -4245,7 +4369,7 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) retract(true); #endif break; - + /*! ### G11 - Retract recover G11: Unretract @@ -4256,9 +4380,17 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) retract(false,retracted_swap[active_extruder]); #else retract(false); - #endif + #endif break; - #endif //FWRETRACT +#endif //FWRETRACT + + + /*! + ### G21 - Sets Units to Millimters G21: Set Units to Millimeters + Units are in millimeters. Prusa doesn't support inches. + */ + case 21: + break; //Doing nothing. This is just to prevent serial UNKOWN warnings. /*! @@ -4282,11 +4414,11 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) long home_z_value = 0; // Which axes should be homed? bool home_x = code_seen(axis_codes[X_AXIS]); - home_x_value = code_value_long(); + if (home_x) home_x_value = code_value_long(); bool home_y = code_seen(axis_codes[Y_AXIS]); - home_y_value = code_value_long(); + if (home_y) home_y_value = code_value_long(); bool home_z = code_seen(axis_codes[Z_AXIS]); - home_z_value = code_value_long(); + if (home_z) home_z_value = code_value_long(); bool without_mbl = code_seen('W'); // calibrate? #ifdef TMC2130 @@ -4296,9 +4428,7 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) gcode_G28(home_x, home_x_value, home_y, home_y_value, home_z, home_z_value, without_mbl); #endif //TMC2130 if ((home_x || home_y || without_mbl || home_z) == false) { - // Push the commands to the front of the message queue in the reverse order! - // There shall be always enough space reserved for these commands. - goto case_G80; + gcode_G80(); } break; } @@ -4337,7 +4467,7 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) current_position[X_AXIS] = uncorrected_position.x; current_position[Y_AXIS] = uncorrected_position.y; current_position[Z_AXIS] = uncorrected_position.z; - plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); + plan_set_position_curposXYZE(); int l_feedmultiply = setup_for_endstop_move(); feedrate = homing_feedrate[Z_AXIS]; @@ -4451,7 +4581,7 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) apply_rotation_xyz(plan_bed_level_matrix, x_tmp, y_tmp, z_tmp); //Apply the correction sending the probe offset current_position[Z_AXIS] = z_tmp - real_z + current_position[Z_AXIS]; //The difference is added to current position and sent to planner. - plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); + plan_set_position_curposXYZE(); } break; #ifndef Z_PROBE_SLED @@ -4512,6 +4642,8 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) case 30: { st_synchronize(); + homing_flag = true; + // TODO: make sure the bed_level_rotation_matrix is identity or the planner will get set incorectly int l_feedmultiply = setup_for_endstop_move(); @@ -4522,6 +4654,7 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) printf_P(_N("%S X: %.5f Y: %.5f Z: %.5f\n"), _T(MSG_BED), _x, _y, _z); clean_up_after_endstop_move(l_feedmultiply); + homing_flag = false; } break; @@ -4531,7 +4664,7 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) */ case 75: { - for (int i = 40; i <= 110; i++) + for (uint8_t i = 40; i <= 110; i++) printf_P(_N("%d %.2f"), i, temp_comp_interpolation(i)); } break; @@ -4539,11 +4672,15 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) /*! ### G76 - PINDA probe temperature calibration G76: PINDA probe temperature calibration This G-code is used to calibrate the temperature drift of the PINDA (inductive Sensor). - + The PINDAv2 sensor has a built-in thermistor which has the advantage that the calibration can be done once for all materials. The Original i3 Prusa MK2/s uses PINDAv1 and this calibration improves the temperature drift, but not as good as the PINDAv2. + superPINDA sensor has internal temperature compensation and no thermistor output. There is no point of doing temperature calibration in such case. + If PINDA_THERMISTOR and SUPERPINDA_SUPPORT is defined during compilation, calibration is skipped with serial message "No PINDA thermistor". + This can be caused also if PINDA thermistor connection is broken or PINDA temperature is lower than PINDA_MINTEMP. + #### Example ``` @@ -4558,154 +4695,159 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) case 76: { #ifdef PINDA_THERMISTOR - if (true) - { + if (!has_temperature_compensation()) + { + SERIAL_ECHOLNPGM("No PINDA thermistor"); + break; + } - if (calibration_status() >= CALIBRATION_STATUS_XYZ_CALIBRATION) { - //we need to know accurate position of first calibration point - //if xyz calibration was not performed yet, interrupt temperature calibration and inform user that xyz cal. is needed - lcd_show_fullscreen_message_and_wait_P(_i("Please run XYZ calibration first.")); - break; - } - - if (!(axis_known_position[X_AXIS] && axis_known_position[Y_AXIS] && axis_known_position[Z_AXIS])) - { - // We don't know where we are! HOME! - // Push the commands to the front of the message queue in the reverse order! - // There shall be always enough space reserved for these commands. - repeatcommand_front(); // repeat G76 with all its parameters - enquecommand_front_P((PSTR("G28 W0"))); - break; - } - lcd_show_fullscreen_message_and_wait_P(_i("Stable ambient temperature 21-26C is needed a rigid stand is required."));////MSG_TEMP_CAL_WARNING c=20 r=4 - bool result = lcd_show_fullscreen_message_yes_no_and_wait_P(_T(MSG_STEEL_SHEET_CHECK), false, false); - - if (result) - { - current_position[Z_AXIS] = MESH_HOME_Z_SEARCH; - plan_buffer_line_curposXYZE(3000 / 60, active_extruder); - current_position[Z_AXIS] = 50; - current_position[Y_AXIS] = 180; - plan_buffer_line_curposXYZE(3000 / 60, active_extruder); - st_synchronize(); - lcd_show_fullscreen_message_and_wait_P(_T(MSG_REMOVE_STEEL_SHEET)); - current_position[Y_AXIS] = pgm_read_float(bed_ref_points_4 + 1); - current_position[X_AXIS] = pgm_read_float(bed_ref_points_4); - plan_buffer_line_curposXYZE(3000 / 60, active_extruder); - st_synchronize(); - gcode_G28(false, false, true); + if (!calibration_status_get(CALIBRATION_STATUS_XYZ)) { + //we need to know accurate position of first calibration point + //if xyz calibration was not performed yet, interrupt temperature calibration and inform user that xyz cal. is needed + lcd_show_fullscreen_message_and_wait_P(_i("Please run XYZ calibration first.")); ////MSG_RUN_XYZ c=20 r=4 + break; + } - } - if ((current_temperature_pinda > 35) && (farm_mode == false)) { - //waiting for PIDNA probe to cool down in case that we are not in farm mode - current_position[Z_AXIS] = 100; - plan_buffer_line_curposXYZE(3000 / 60, active_extruder); - if (lcd_wait_for_pinda(35) == false) { //waiting for PINDA probe to cool, if this takes more then time expected, temp. cal. fails - lcd_temp_cal_show_result(false); - break; - } - } - lcd_update_enable(true); - KEEPALIVE_STATE(NOT_BUSY); //no need to print busy messages as we print current temperatures periodicaly - SERIAL_ECHOLNPGM("PINDA probe calibration start"); + if (!(axis_known_position[X_AXIS] && axis_known_position[Y_AXIS] && axis_known_position[Z_AXIS])) + { + // We don't know where we are! HOME! + // Push the commands to the front of the message queue in the reverse order! + // There shall be always enough space reserved for these commands. + repeatcommand_front(); // repeat G76 with all its parameters + enquecommand_front_P(G28W0); + break; + } + lcd_show_fullscreen_message_and_wait_P(_i("Stable ambient temperature 21-26C is needed a rigid stand is required."));////MSG_TEMP_CAL_WARNING c=20 r=4 + uint8_t result = lcd_show_fullscreen_message_yes_no_and_wait_P(_T(MSG_STEEL_SHEET_CHECK), false); - float zero_z; - int z_shift = 0; //unit: steps - float start_temp = 5 * (int)(current_temperature_pinda / 5); - if (start_temp < 35) start_temp = 35; - if (start_temp < current_temperature_pinda) start_temp += 5; - printf_P(_N("start temperature: %.1f\n"), start_temp); + if (result == LCD_LEFT_BUTTON_CHOICE) + { + current_position[Z_AXIS] = MESH_HOME_Z_SEARCH; + plan_buffer_line_curposXYZE(3000 / 60); + current_position[Z_AXIS] = 50; + current_position[Y_AXIS] = 180; + plan_buffer_line_curposXYZE(3000 / 60); + st_synchronize(); + lcd_show_fullscreen_message_and_wait_P(_T(MSG_REMOVE_STEEL_SHEET)); + current_position[Y_AXIS] = pgm_read_float(bed_ref_points_4 + 1); + current_position[X_AXIS] = pgm_read_float(bed_ref_points_4); + plan_buffer_line_curposXYZE(3000 / 60); + st_synchronize(); + gcode_G28(false, false, true); -// setTargetHotend(200, 0); - setTargetBed(70 + (start_temp - 30)); + } + if ((current_temperature_pinda > 35) && (farm_mode == false)) { + //waiting for PIDNA probe to cool down in case that we are not in farm mode + current_position[Z_AXIS] = 100; + plan_buffer_line_curposXYZE(3000 / 60); + if (lcd_wait_for_pinda(35) == false) { //waiting for PINDA probe to cool, if this takes more then time expected, temp. cal. fails + lcd_temp_cal_show_result(false); + break; + } + } - custom_message_type = CustomMsg::TempCal; - custom_message_state = 1; - lcd_setstatuspgm(_T(MSG_TEMP_CALIBRATION)); - current_position[Z_AXIS] = MESH_HOME_Z_SEARCH; - plan_buffer_line_curposXYZE(3000 / 60, active_extruder); - current_position[X_AXIS] = PINDA_PREHEAT_X; - current_position[Y_AXIS] = PINDA_PREHEAT_Y; - plan_buffer_line_curposXYZE(3000 / 60, active_extruder); - current_position[Z_AXIS] = PINDA_PREHEAT_Z; - plan_buffer_line_curposXYZE(3000 / 60, active_extruder); - st_synchronize(); + st_synchronize(); + homing_flag = true; // keep homing on to avoid babystepping while the LCD is enabled - while (current_temperature_pinda < start_temp) - { - delay_keep_alive(1000); - serialecho_temperatures(); - } + lcd_update_enable(true); + SERIAL_ECHOLNPGM("PINDA probe calibration start"); - eeprom_update_byte((uint8_t*)EEPROM_CALIBRATION_STATUS_PINDA, 0); //invalidate temp. calibration in case that in will be aborted during the calibration process + float zero_z; + int z_shift = 0; //unit: steps + float start_temp = 5 * (int)(current_temperature_pinda / 5); + if (start_temp < 35) start_temp = 35; + if (start_temp < current_temperature_pinda) start_temp += 5; + printf_P(_N("start temperature: %.1f\n"), start_temp); - current_position[Z_AXIS] = MESH_HOME_Z_SEARCH; - plan_buffer_line_curposXYZE(3000 / 60, active_extruder); - current_position[X_AXIS] = pgm_read_float(bed_ref_points_4); - current_position[Y_AXIS] = pgm_read_float(bed_ref_points_4 + 1); - plan_buffer_line_curposXYZE(3000 / 60, active_extruder); - st_synchronize(); + setTargetBed(70 + (start_temp - 30)); - bool find_z_result = find_bed_induction_sensor_point_z(-1.f); - if (find_z_result == false) { - lcd_temp_cal_show_result(find_z_result); - break; - } - zero_z = current_position[Z_AXIS]; + custom_message_type = CustomMsg::TempCal; + custom_message_state = 1; + lcd_setstatuspgm(_T(MSG_PINDA_CALIBRATION)); + current_position[Z_AXIS] = MESH_HOME_Z_SEARCH; + plan_buffer_line_curposXYZE(3000 / 60); + current_position[X_AXIS] = PINDA_PREHEAT_X; + current_position[Y_AXIS] = PINDA_PREHEAT_Y; + plan_buffer_line_curposXYZE(3000 / 60); + current_position[Z_AXIS] = PINDA_PREHEAT_Z; + plan_buffer_line_curposXYZE(3000 / 60); + st_synchronize(); - printf_P(_N("\nZERO: %.3f\n"), current_position[Z_AXIS]); + while (current_temperature_pinda < start_temp) + { + delay_keep_alive(1000); + serialecho_temperatures(); + } - int i = -1; for (; i < 5; i++) - { - float temp = (40 + i * 5); - printf_P(_N("\nStep: %d/6 (skipped)\nPINDA temperature: %d Z shift (mm):0\n"), i + 2, (40 + i*5)); - if (i >= 0) EEPROM_save_B(EEPROM_PROBE_TEMP_SHIFT + i * 2, &z_shift); - if (start_temp <= temp) break; - } + eeprom_update_byte((uint8_t*)EEPROM_CALIBRATION_STATUS_PINDA, 0); //invalidate temp. calibration in case that in will be aborted during the calibration process - for (i++; i < 5; i++) - { - float temp = (40 + i * 5); - printf_P(_N("\nStep: %d/6\n"), i + 2); - custom_message_state = i + 2; - setTargetBed(50 + 10 * (temp - 30) / 5); -// setTargetHotend(255, 0); - current_position[Z_AXIS] = MESH_HOME_Z_SEARCH; - plan_buffer_line_curposXYZE(3000 / 60, active_extruder); - current_position[X_AXIS] = PINDA_PREHEAT_X; - current_position[Y_AXIS] = PINDA_PREHEAT_Y; - plan_buffer_line_curposXYZE(3000 / 60, active_extruder); - current_position[Z_AXIS] = PINDA_PREHEAT_Z; - plan_buffer_line_curposXYZE(3000 / 60, active_extruder); - st_synchronize(); - while (current_temperature_pinda < temp) - { - delay_keep_alive(1000); - serialecho_temperatures(); - } - current_position[Z_AXIS] = MESH_HOME_Z_SEARCH; - plan_buffer_line_curposXYZE(3000 / 60, active_extruder); - current_position[X_AXIS] = pgm_read_float(bed_ref_points_4); - current_position[Y_AXIS] = pgm_read_float(bed_ref_points_4 + 1); - plan_buffer_line_curposXYZE(3000 / 60, active_extruder); - st_synchronize(); - find_z_result = find_bed_induction_sensor_point_z(-1.f); - if (find_z_result == false) { - lcd_temp_cal_show_result(find_z_result); - break; - } - z_shift = (int)((current_position[Z_AXIS] - zero_z)*cs.axis_steps_per_unit[Z_AXIS]); + current_position[Z_AXIS] = MESH_HOME_Z_SEARCH; + plan_buffer_line_curposXYZE(3000 / 60); + current_position[X_AXIS] = pgm_read_float(bed_ref_points_4); + current_position[Y_AXIS] = pgm_read_float(bed_ref_points_4 + 1); + plan_buffer_line_curposXYZE(3000 / 60); + st_synchronize(); - printf_P(_N("\nPINDA temperature: %.1f Z shift (mm): %.3f"), current_temperature_pinda, current_position[Z_AXIS] - zero_z); + bool find_z_result = find_bed_induction_sensor_point_z(-1.f); + if (find_z_result == false) { + lcd_temp_cal_show_result(find_z_result); + homing_flag = false; + break; + } + zero_z = current_position[Z_AXIS]; - EEPROM_save_B(EEPROM_PROBE_TEMP_SHIFT + i * 2, &z_shift); + printf_P(_N("\nZERO: %.3f\n"), current_position[Z_AXIS]); - } - lcd_temp_cal_show_result(true); + int i = -1; for (; i < 5; i++) + { + float temp = (40 + i * 5); + printf_P(_N("\nStep: %d/6 (skipped)\nPINDA temperature: %d Z shift (mm):0\n"), i + 2, (40 + i*5)); + if (i >= 0) { + eeprom_update_word((uint16_t*)EEPROM_PROBE_TEMP_SHIFT + i, z_shift); + } + if (start_temp <= temp) break; + } - break; - } -#endif //PINDA_THERMISTOR + for (i++; i < 5; i++) + { + float temp = (40 + i * 5); + printf_P(_N("\nStep: %d/6\n"), i + 2); + custom_message_state = i + 2; + setTargetBed(50 + 10 * (temp - 30) / 5); + current_position[Z_AXIS] = MESH_HOME_Z_SEARCH; + plan_buffer_line_curposXYZE(3000 / 60); + current_position[X_AXIS] = PINDA_PREHEAT_X; + current_position[Y_AXIS] = PINDA_PREHEAT_Y; + plan_buffer_line_curposXYZE(3000 / 60); + current_position[Z_AXIS] = PINDA_PREHEAT_Z; + plan_buffer_line_curposXYZE(3000 / 60); + st_synchronize(); + while (current_temperature_pinda < temp) + { + delay_keep_alive(1000); + serialecho_temperatures(); + } + current_position[Z_AXIS] = MESH_HOME_Z_SEARCH; + plan_buffer_line_curposXYZE(3000 / 60); + current_position[X_AXIS] = pgm_read_float(bed_ref_points_4); + current_position[Y_AXIS] = pgm_read_float(bed_ref_points_4 + 1); + plan_buffer_line_curposXYZE(3000 / 60); + st_synchronize(); + find_z_result = find_bed_induction_sensor_point_z(-1.f); + if (find_z_result == false) { + lcd_temp_cal_show_result(find_z_result); + break; + } + z_shift = (int)((current_position[Z_AXIS] - zero_z)*cs.axis_steps_per_unit[Z_AXIS]); + + printf_P(_N("\nPINDA temperature: %.1f Z shift (mm): %.3f"), current_temperature_pinda, current_position[Z_AXIS] - zero_z); + + eeprom_update_word((uint16_t*)EEPROM_PROBE_TEMP_SHIFT + i, z_shift); + } + lcd_temp_cal_show_result(true); + homing_flag = false; + +#else //PINDA_THERMISTOR setTargetBed(PINDA_MIN_T); float zero_z; @@ -4717,20 +4859,20 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) // Push the commands to the front of the message queue in the reverse order! // There shall be always enough space reserved for these commands. repeatcommand_front(); // repeat G76 with all its parameters - enquecommand_front_P((PSTR("G28 W0"))); + enquecommand_front_P(G28W0); break; } puts_P(_N("PINDA probe calibration start")); custom_message_type = CustomMsg::TempCal; custom_message_state = 1; - lcd_setstatuspgm(_T(MSG_TEMP_CALIBRATION)); + lcd_setstatuspgm(_T(MSG_PINDA_CALIBRATION)); current_position[X_AXIS] = PINDA_PREHEAT_X; current_position[Y_AXIS] = PINDA_PREHEAT_Y; current_position[Z_AXIS] = PINDA_PREHEAT_Z; - plan_buffer_line_curposXYZE(3000 / 60, active_extruder); + plan_buffer_line_curposXYZE(3000 / 60); st_synchronize(); - while (abs(degBed() - PINDA_MIN_T) > 1) { + while (fabs(degBed() - PINDA_MIN_T) > 1) { delay_keep_alive(1000); serialecho_temperatures(); } @@ -4743,11 +4885,11 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) eeprom_update_byte((uint8_t*)EEPROM_CALIBRATION_STATUS_PINDA, 0); //invalidate temp. calibration in case that in will be aborted during the calibration process current_position[Z_AXIS] = 5; - plan_buffer_line_curposXYZE(3000 / 60, active_extruder); + plan_buffer_line_curposXYZE(3000 / 60); current_position[X_AXIS] = BED_X0; current_position[Y_AXIS] = BED_Y0; - plan_buffer_line_curposXYZE(3000 / 60, active_extruder); + plan_buffer_line_curposXYZE(3000 / 60); st_synchronize(); find_bed_induction_sensor_point_z(-1.f); @@ -4764,7 +4906,7 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) current_position[X_AXIS] = PINDA_PREHEAT_X; current_position[Y_AXIS] = PINDA_PREHEAT_Y; current_position[Z_AXIS] = PINDA_PREHEAT_Z; - plan_buffer_line_curposXYZE(3000 / 60, active_extruder); + plan_buffer_line_curposXYZE(3000 / 60); st_synchronize(); while (degBed() < t_c) { delay_keep_alive(1000); @@ -4775,17 +4917,17 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) serialecho_temperatures(); } current_position[Z_AXIS] = 5; - plan_buffer_line_curposXYZE(3000 / 60, active_extruder); + plan_buffer_line_curposXYZE(3000 / 60); current_position[X_AXIS] = BED_X0; current_position[Y_AXIS] = BED_Y0; - plan_buffer_line_curposXYZE(3000 / 60, active_extruder); + plan_buffer_line_curposXYZE(3000 / 60); st_synchronize(); find_bed_induction_sensor_point_z(-1.f); z_shift = (int)((current_position[Z_AXIS] - zero_z)*cs.axis_steps_per_unit[Z_AXIS]); printf_P(_N("\nTemperature: %d Z shift (mm): %.3f\n"), t_c, current_position[Z_AXIS] - zero_z); - EEPROM_save_B(EEPROM_PROBE_TEMP_SHIFT + i*2, &z_shift); + eeprom_update_word((uint16_t*)EEPROM_PROBE_TEMP_SHIFT + i, z_shift); } @@ -4800,14 +4942,13 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) disable_e1(); disable_e2(); setTargetBed(0); //set bed target temperature back to 0 - lcd_show_fullscreen_message_and_wait_P(_T(MSG_TEMP_CALIBRATION_DONE)); - temp_cal_active = true; + lcd_show_fullscreen_message_and_wait_P(_T(MSG_PINDA_CALIBRATION_DONE)); eeprom_update_byte((unsigned char *)EEPROM_TEMP_CAL_ACTIVE, 1); lcd_update_enable(true); lcd_update(2); - +#endif //PINDA_THERMISTOR } break; @@ -4841,411 +4982,8 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) * v Y-axis */ - case 80: - -#ifdef MK1BP - break; -#endif //MK1BP - case_G80: - { - mesh_bed_leveling_flag = true; -#ifndef LA_NOCOMPAT - // When printing via USB there's no clear boundary between prints. Abuse MBL to indicate - // the beginning of a new print, allowing a new autodetected setting just after G80. - la10c_reset(); -#endif -#ifndef PINDA_THERMISTOR - static bool run = false; // thermistor-less PINDA temperature compensation is running -#endif // ndef PINDA_THERMISTOR - -#ifdef SUPPORT_VERBOSITY - int8_t verbosity_level = 0; - if (code_seen('V')) { - // Just 'V' without a number counts as V1. - char c = strchr_pointer[1]; - verbosity_level = (c == ' ' || c == '\t' || c == 0) ? 1 : code_value_short(); - } -#endif //SUPPORT_VERBOSITY - // Firstly check if we know where we are - if (!(axis_known_position[X_AXIS] && axis_known_position[Y_AXIS] && axis_known_position[Z_AXIS])) { - // We don't know where we are! HOME! - // Push the commands to the front of the message queue in the reverse order! - // There shall be always enough space reserved for these commands. - repeatcommand_front(); // repeat G80 with all its parameters - enquecommand_front_P((PSTR("G28 W0"))); - break; - } - - uint8_t nMeasPoints = MESH_MEAS_NUM_X_POINTS; - if (code_seen('N')) { - nMeasPoints = code_value_uint8(); - if (nMeasPoints != 7) { - nMeasPoints = 3; - } - } - else { - nMeasPoints = eeprom_read_byte((uint8_t*)EEPROM_MBL_POINTS_NR); - } - - uint8_t nProbeRetry = 3; - if (code_seen('R')) { - nProbeRetry = code_value_uint8(); - if (nProbeRetry > 10) { - nProbeRetry = 10; - } - } - else { - nProbeRetry = eeprom_read_byte((uint8_t*)EEPROM_MBL_PROBE_NR); - } - bool magnet_elimination = (eeprom_read_byte((uint8_t*)EEPROM_MBL_MAGNET_ELIMINATION) > 0); - -#ifndef PINDA_THERMISTOR - if (run == false && temp_cal_active == true && calibration_status_pinda() == true && target_temperature_bed >= 50) - { - temp_compensation_start(); - run = true; - repeatcommand_front(); // repeat G80 with all its parameters - enquecommand_front_P((PSTR("G28 W0"))); - break; - } - run = false; -#endif //PINDA_THERMISTOR - // Save custom message state, set a new custom message state to display: Calibrating point 9. - CustomMsg custom_message_type_old = custom_message_type; - unsigned int custom_message_state_old = custom_message_state; - custom_message_type = CustomMsg::MeshBedLeveling; - custom_message_state = (nMeasPoints * nMeasPoints) + 10; - lcd_update(1); - - mbl.reset(); //reset mesh bed leveling - - // Reset baby stepping to zero, if the babystepping has already been loaded before. The babystepsTodo value will be - // consumed during the first movements following this statement. - babystep_undo(); - - // Cycle through all points and probe them - // First move up. During this first movement, the babystepping will be reverted. - current_position[Z_AXIS] = MESH_HOME_Z_SEARCH; - plan_buffer_line_curposXYZE(homing_feedrate[Z_AXIS] / 60, active_extruder); - // The move to the first calibration point. - current_position[X_AXIS] = BED_X0; - current_position[Y_AXIS] = BED_Y0; - - #ifdef SUPPORT_VERBOSITY - if (verbosity_level >= 1) - { - bool clamped = world2machine_clamp(current_position[X_AXIS], current_position[Y_AXIS]); - clamped ? SERIAL_PROTOCOLPGM("First calibration point clamped.\n") : SERIAL_PROTOCOLPGM("No clamping for first calibration point.\n"); - } - #else //SUPPORT_VERBOSITY - world2machine_clamp(current_position[X_AXIS], current_position[Y_AXIS]); - #endif //SUPPORT_VERBOSITY - - plan_buffer_line_curposXYZE(homing_feedrate[X_AXIS] / 30, active_extruder); - // Wait until the move is finished. - st_synchronize(); - - uint8_t mesh_point = 0; //index number of calibration point - - int XY_AXIS_FEEDRATE = homing_feedrate[X_AXIS] / 20; - int Z_LIFT_FEEDRATE = homing_feedrate[Z_AXIS] / 40; - bool has_z = is_bed_z_jitter_data_valid(); //checks if we have data from Z calibration (offsets of the Z heiths of the 8 calibration points from the first point) - #ifdef SUPPORT_VERBOSITY - if (verbosity_level >= 1) { - has_z ? SERIAL_PROTOCOLPGM("Z jitter data from Z cal. valid.\n") : SERIAL_PROTOCOLPGM("Z jitter data from Z cal. not valid.\n"); - } - #endif // SUPPORT_VERBOSITY - int l_feedmultiply = setup_for_endstop_move(false); //save feedrate and feedmultiply, sets feedmultiply to 100 - const char *kill_message = NULL; - while (mesh_point != nMeasPoints * nMeasPoints) { - // Get coords of a measuring point. - uint8_t ix = mesh_point % nMeasPoints; // from 0 to MESH_NUM_X_POINTS - 1 - uint8_t iy = mesh_point / nMeasPoints; - /*if (!mbl_point_measurement_valid(ix, iy, nMeasPoints, true)) { - printf_P(PSTR("Skipping point [%d;%d] \n"), ix, iy); - custom_message_state--; - mesh_point++; - continue; //skip - }*/ - if (iy & 1) ix = (nMeasPoints - 1) - ix; // Zig zag - if (nMeasPoints == 7) //if we have 7x7 mesh, compare with Z-calibration for points which are in 3x3 mesh - { - has_z = ((ix % 3 == 0) && (iy % 3 == 0)) && is_bed_z_jitter_data_valid(); - } - float z0 = 0.f; - if (has_z && (mesh_point > 0)) { - uint16_t z_offset_u = 0; - if (nMeasPoints == 7) { - z_offset_u = eeprom_read_word((uint16_t*)(EEPROM_BED_CALIBRATION_Z_JITTER + 2 * ((ix/3) + iy - 1))); - } - else { - z_offset_u = eeprom_read_word((uint16_t*)(EEPROM_BED_CALIBRATION_Z_JITTER + 2 * (ix + iy * 3 - 1))); - } - z0 = mbl.z_values[0][0] + *reinterpret_cast(&z_offset_u) * 0.01; - #ifdef SUPPORT_VERBOSITY - if (verbosity_level >= 1) { - printf_P(PSTR("Bed leveling, point: %d, calibration Z stored in eeprom: %d, calibration z: %f \n"), mesh_point, z_offset_u, z0); - } - #endif // SUPPORT_VERBOSITY - } - - // Move Z up to MESH_HOME_Z_SEARCH. - if((ix == 0) && (iy == 0)) current_position[Z_AXIS] = MESH_HOME_Z_SEARCH; - else current_position[Z_AXIS] += 2.f / nMeasPoints; //use relative movement from Z coordinate where PINDa triggered on previous point. This makes calibration faster. - float init_z_bckp = current_position[Z_AXIS]; - plan_buffer_line_curposXYZE(Z_LIFT_FEEDRATE, active_extruder); - st_synchronize(); - - // Move to XY position of the sensor point. - current_position[X_AXIS] = BED_X(ix, nMeasPoints); - current_position[Y_AXIS] = BED_Y(iy, nMeasPoints); - - //printf_P(PSTR("[%f;%f]\n"), current_position[X_AXIS], current_position[Y_AXIS]); - - - #ifdef SUPPORT_VERBOSITY - if (verbosity_level >= 1) { - clamped = world2machine_clamp(current_position[X_AXIS], current_position[Y_AXIS]); - SERIAL_PROTOCOL(mesh_point); - clamped ? SERIAL_PROTOCOLPGM(": xy clamped.\n") : SERIAL_PROTOCOLPGM(": no xy clamping\n"); - } - #else //SUPPORT_VERBOSITY - world2machine_clamp(current_position[X_AXIS], current_position[Y_AXIS]); - #endif // SUPPORT_VERBOSITY - - //printf_P(PSTR("after clamping: [%f;%f]\n"), current_position[X_AXIS], current_position[Y_AXIS]); - plan_buffer_line_curposXYZE(XY_AXIS_FEEDRATE, active_extruder); - st_synchronize(); - - // Go down until endstop is hit - const float Z_CALIBRATION_THRESHOLD = 1.f; - if (!find_bed_induction_sensor_point_z((has_z && mesh_point > 0) ? z0 - Z_CALIBRATION_THRESHOLD : -10.f, nProbeRetry)) { //if we have data from z calibration max allowed difference is 1mm for each point, if we dont have data max difference is 10mm from initial point - printf_P(_T(MSG_BED_LEVELING_FAILED_POINT_LOW)); - break; - } - if (init_z_bckp - current_position[Z_AXIS] < 0.1f) { //broken cable or initial Z coordinate too low. Go to MESH_HOME_Z_SEARCH and repeat last step (z-probe) again to distinguish between these two cases. - //printf_P(PSTR("Another attempt! Current Z position: %f\n"), current_position[Z_AXIS]); - current_position[Z_AXIS] = MESH_HOME_Z_SEARCH; - plan_buffer_line_curposXYZE(Z_LIFT_FEEDRATE, active_extruder); - st_synchronize(); - - if (!find_bed_induction_sensor_point_z((has_z && mesh_point > 0) ? z0 - Z_CALIBRATION_THRESHOLD : -10.f, nProbeRetry)) { //if we have data from z calibration max allowed difference is 1mm for each point, if we dont have data max difference is 10mm from initial point - printf_P(_T(MSG_BED_LEVELING_FAILED_POINT_LOW)); - break; - } - if (MESH_HOME_Z_SEARCH - current_position[Z_AXIS] < 0.1f) { - printf_P(PSTR("Bed leveling failed. Sensor disconnected or cable broken.\n")); - break; - } - } - if (has_z && fabs(z0 - current_position[Z_AXIS]) > Z_CALIBRATION_THRESHOLD) { //if we have data from z calibration, max. allowed difference is 1mm for each point - printf_P(PSTR("Bed leveling failed. Sensor triggered too high.\n")); - break; - } - #ifdef SUPPORT_VERBOSITY - if (verbosity_level >= 10) { - SERIAL_ECHOPGM("X: "); - MYSERIAL.print(current_position[X_AXIS], 5); - SERIAL_ECHOLNPGM(""); - SERIAL_ECHOPGM("Y: "); - MYSERIAL.print(current_position[Y_AXIS], 5); - SERIAL_PROTOCOLPGM("\n"); - } - #endif // SUPPORT_VERBOSITY - float offset_z = 0; - -#ifdef PINDA_THERMISTOR - offset_z = temp_compensation_pinda_thermistor_offset(current_temperature_pinda); -#endif //PINDA_THERMISTOR -// #ifdef SUPPORT_VERBOSITY -/* if (verbosity_level >= 1) - { - SERIAL_ECHOPGM("mesh bed leveling: "); - MYSERIAL.print(current_position[Z_AXIS], 5); - SERIAL_ECHOPGM(" offset: "); - MYSERIAL.print(offset_z, 5); - SERIAL_ECHOLNPGM(""); - }*/ -// #endif // SUPPORT_VERBOSITY - mbl.set_z(ix, iy, current_position[Z_AXIS] - offset_z); //store measured z values z_values[iy][ix] = z - offset_z; - - custom_message_state--; - mesh_point++; - lcd_update(1); - } - current_position[Z_AXIS] = MESH_HOME_Z_SEARCH; - #ifdef SUPPORT_VERBOSITY - if (verbosity_level >= 20) { - SERIAL_ECHOLNPGM("Mesh bed leveling while loop finished."); - SERIAL_ECHOLNPGM("MESH_HOME_Z_SEARCH: "); - MYSERIAL.print(current_position[Z_AXIS], 5); - } - #endif // SUPPORT_VERBOSITY - plan_buffer_line_curposXYZE(Z_LIFT_FEEDRATE, active_extruder); - st_synchronize(); - if (mesh_point != nMeasPoints * nMeasPoints) { - Sound_MakeSound(e_SOUND_TYPE_StandardAlert); - bool bState; - do { // repeat until Z-leveling o.k. - lcd_display_message_fullscreen_P(_i("Some problem encountered, Z-leveling enforced ...")); -#ifdef TMC2130 - lcd_wait_for_click_delay(MSG_BED_LEVELING_FAILED_TIMEOUT); - calibrate_z_auto(); // Z-leveling (X-assembly stay up!!!) -#else // TMC2130 - lcd_wait_for_click_delay(0); // ~ no timeout - lcd_calibrate_z_end_stop_manual(true); // Z-leveling (X-assembly stay up!!!) -#endif // TMC2130 - // ~ Z-homing (can not be used "G28", because X & Y-homing would have been done before (Z-homing)) - bState=enable_z_endstop(false); - current_position[Z_AXIS] -= 1; - plan_buffer_line_curposXYZE(homing_feedrate[Z_AXIS] / 40, active_extruder); - st_synchronize(); - enable_z_endstop(true); -#ifdef TMC2130 - tmc2130_home_enter(Z_AXIS_MASK); -#endif // TMC2130 - current_position[Z_AXIS] = MESH_HOME_Z_SEARCH; - plan_buffer_line_curposXYZE(homing_feedrate[Z_AXIS] / 40, active_extruder); - st_synchronize(); -#ifdef TMC2130 - tmc2130_home_exit(); -#endif // TMC2130 - enable_z_endstop(bState); - } while (st_get_position_mm(Z_AXIS) > MESH_HOME_Z_SEARCH); // i.e. Z-leveling not o.k. -// plan_set_z_position(MESH_HOME_Z_SEARCH); // is not necessary ('do-while' loop always ends at the expected Z-position) - custom_message_type=CustomMsg::Status; // display / status-line recovery - lcd_update_enable(true); // display / status-line recovery - gcode_G28(true, true, true); // X & Y & Z-homing (must be after individual Z-homing (problem with spool-holder)!) - repeatcommand_front(); // re-run (i.e. of "G80") - break; - } - clean_up_after_endstop_move(l_feedmultiply); -// SERIAL_ECHOLNPGM("clean up finished "); - -#ifndef PINDA_THERMISTOR - if(temp_cal_active == true && calibration_status_pinda() == true) temp_compensation_apply(); //apply PINDA temperature compensation -#endif - babystep_apply(); // Apply Z height correction aka baby stepping before mesh bed leveing gets activated. -// SERIAL_ECHOLNPGM("babystep applied"); - bool eeprom_bed_correction_valid = eeprom_read_byte((unsigned char*)EEPROM_BED_CORRECTION_VALID) == 1; - #ifdef SUPPORT_VERBOSITY - if (verbosity_level >= 1) { - eeprom_bed_correction_valid ? SERIAL_PROTOCOLPGM("Bed correction data valid\n") : SERIAL_PROTOCOLPGM("Bed correction data not valid\n"); - } - #endif // SUPPORT_VERBOSITY - - for (uint8_t i = 0; i < 4; ++i) { - unsigned char codes[4] = { 'L', 'R', 'F', 'B' }; - long correction = 0; - if (code_seen(codes[i])) - correction = code_value_long(); - else if (eeprom_bed_correction_valid) { - unsigned char *addr = (i < 2) ? - ((i == 0) ? (unsigned char*)EEPROM_BED_CORRECTION_LEFT : (unsigned char*)EEPROM_BED_CORRECTION_RIGHT) : - ((i == 2) ? (unsigned char*)EEPROM_BED_CORRECTION_FRONT : (unsigned char*)EEPROM_BED_CORRECTION_REAR); - correction = eeprom_read_int8(addr); - } - if (correction == 0) - continue; - - if (labs(correction) > BED_ADJUSTMENT_UM_MAX) { - SERIAL_ERROR_START; - SERIAL_ECHOPGM("Excessive bed leveling correction: "); - SERIAL_ECHO(correction); - SERIAL_ECHOLNPGM(" microns"); - } - else { - float offset = float(correction) * 0.001f; - switch (i) { - case 0: - for (uint8_t row = 0; row < nMeasPoints; ++row) { - for (uint8_t col = 0; col < nMeasPoints - 1; ++col) { - mbl.z_values[row][col] += offset * (nMeasPoints - 1 - col) / (nMeasPoints - 1); - } - } - break; - case 1: - for (uint8_t row = 0; row < nMeasPoints; ++row) { - for (uint8_t col = 1; col < nMeasPoints; ++col) { - mbl.z_values[row][col] += offset * col / (nMeasPoints - 1); - } - } - break; - case 2: - for (uint8_t col = 0; col < nMeasPoints; ++col) { - for (uint8_t row = 0; row < nMeasPoints; ++row) { - mbl.z_values[row][col] += offset * (nMeasPoints - 1 - row) / (nMeasPoints - 1); - } - } - break; - case 3: - for (uint8_t col = 0; col < nMeasPoints; ++col) { - for (uint8_t row = 1; row < nMeasPoints; ++row) { - mbl.z_values[row][col] += offset * row / (nMeasPoints - 1); - } - } - break; - } - } - } -// SERIAL_ECHOLNPGM("Bed leveling correction finished"); - if (nMeasPoints == 3) { - mbl.upsample_3x3(); //interpolation from 3x3 to 7x7 points using largrangian polynomials while using the same array z_values[iy][ix] for storing (just coppying measured data to new destination and interpolating between them) - } -/* - SERIAL_PROTOCOLPGM("Num X,Y: "); - SERIAL_PROTOCOL(MESH_NUM_X_POINTS); - SERIAL_PROTOCOLPGM(","); - SERIAL_PROTOCOL(MESH_NUM_Y_POINTS); - SERIAL_PROTOCOLPGM("\nZ search height: "); - SERIAL_PROTOCOL(MESH_HOME_Z_SEARCH); - SERIAL_PROTOCOLLNPGM("\nMeasured points:"); - for (int y = MESH_NUM_Y_POINTS-1; y >= 0; y--) { - for (int x = 0; x < MESH_NUM_X_POINTS; x++) { - SERIAL_PROTOCOLPGM(" "); - SERIAL_PROTOCOL_F(mbl.z_values[y][x], 5); - } - SERIAL_PROTOCOLPGM("\n"); - } -*/ - if (nMeasPoints == 7 && magnet_elimination) { - mbl_interpolation(nMeasPoints); - } -/* - SERIAL_PROTOCOLPGM("Num X,Y: "); - SERIAL_PROTOCOL(MESH_NUM_X_POINTS); - SERIAL_PROTOCOLPGM(","); - SERIAL_PROTOCOL(MESH_NUM_Y_POINTS); - SERIAL_PROTOCOLPGM("\nZ search height: "); - SERIAL_PROTOCOL(MESH_HOME_Z_SEARCH); - SERIAL_PROTOCOLLNPGM("\nMeasured points:"); - for (int y = MESH_NUM_Y_POINTS-1; y >= 0; y--) { - for (int x = 0; x < MESH_NUM_X_POINTS; x++) { - SERIAL_PROTOCOLPGM(" "); - SERIAL_PROTOCOL_F(mbl.z_values[y][x], 5); - } - SERIAL_PROTOCOLPGM("\n"); - } -*/ -// SERIAL_ECHOLNPGM("Upsample finished"); - mbl.active = 1; //activate mesh bed leveling -// SERIAL_ECHOLNPGM("Mesh bed leveling activated"); - go_home_with_z_lift(); -// SERIAL_ECHOLNPGM("Go home finished"); - //unretract (after PINDA preheat retraction) - if (degHotend(active_extruder) > EXTRUDE_MINTEMP && temp_cal_active == true && calibration_status_pinda() == true && target_temperature_bed >= 50) { - current_position[E_AXIS] += default_retraction; - plan_buffer_line_curposXYZE(400, active_extruder); - } - KEEPALIVE_STATE(NOT_BUSY); - // Restore custom message state - lcd_setstatuspgm(_T(WELCOME_MSG)); - custom_message_type = custom_message_type_old; - custom_message_state = custom_message_state_old; - mesh_bed_leveling_flag = false; - mesh_bed_run_from_menu = false; - lcd_update(2); - + case 80: { + gcode_G80(); } break; @@ -5257,17 +4995,17 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) if (mbl.active) { SERIAL_PROTOCOLPGM("Num X,Y: "); SERIAL_PROTOCOL(MESH_NUM_X_POINTS); - SERIAL_PROTOCOLPGM(","); + SERIAL_PROTOCOL(','); SERIAL_PROTOCOL(MESH_NUM_Y_POINTS); SERIAL_PROTOCOLPGM("\nZ search height: "); SERIAL_PROTOCOL(MESH_HOME_Z_SEARCH); SERIAL_PROTOCOLLNPGM("\nMeasured points:"); - for (int y = MESH_NUM_Y_POINTS-1; y >= 0; y--) { - for (int x = 0; x < MESH_NUM_X_POINTS; x++) { + for (uint8_t y = MESH_NUM_Y_POINTS; y-- > 0;) { + for (uint8_t x = 0; x < MESH_NUM_X_POINTS; x++) { SERIAL_PROTOCOLPGM(" "); SERIAL_PROTOCOL_F(mbl.z_values[y][x], 5); } - SERIAL_PROTOCOLPGM("\n"); + SERIAL_PROTOCOLLN(); } } else @@ -5308,7 +5046,7 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) }else{ // Save it to the eeprom babystepLoadZ = babystepz; - EEPROM_save_B(EEPROM_BABYSTEP_Z0+(BabyPosition*2),&babystepLoadZ); + eeprom_update_word((uint16_t*)EEPROM_BABYSTEP_Z0 + BabyPosition, babystepLoadZ); // adjust the Z babystepsTodoZadd(babystepLoadZ); } @@ -5342,7 +5080,7 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) (Prusa3D specific) */ case 86: - calibration_status_store(CALIBRATION_STATUS_LIVE_ADJUST); + calibration_status_clear(CALIBRATION_STATUS_LIVE_ADJUST); break; @@ -5353,7 +5091,7 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) (Prusa3D specific) */ case 87: - calibration_status_store(CALIBRATION_STATUS_CALIBRATED); + calibration_status_set(CALIBRATION_STATUS_LIVE_ADJUST); break; /*! @@ -5373,21 +5111,19 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) /*! ### G90 - Switch off relative mode G90: Set to Absolute Positioning - All coordinates from now on are absolute relative to the origin of the machine. E axis is also switched to absolute mode. + All coordinates from now on are absolute relative to the origin of the machine. E axis is left intact. */ case 90: { - for(uint8_t i = 0; i != NUM_AXIS; ++i) - axis_relative_modes[i] = false; + axis_relative_modes &= ~(X_AXIS_MASK | Y_AXIS_MASK | Z_AXIS_MASK); } break; /*! ### G91 - Switch on relative mode G91: Set to Relative Positioning - All coordinates from now on are relative to the last position. E axis is also switched to relative mode. + All coordinates from now on are relative to the last position. E axis is left intact. */ case 91: { - for(uint8_t i = 0; i != NUM_AXIS; ++i) - axis_relative_modes[i] = true; + axis_relative_modes |= X_AXIS_MASK | Y_AXIS_MASK | Z_AXIS_MASK; } break; @@ -5415,33 +5151,25 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) } break; +#ifdef PRUSA_FARM /*! ### G98 - Activate farm mode G98: Activate farm mode - Enable Prusa-specific Farm functions and g-code. + Enable Prusa-specific Farm functions and g-code. See Internal Prusa commands. */ - case 98: - farm_mode = 1; - PingTime = _millis(); - eeprom_update_byte((unsigned char *)EEPROM_FARM_MODE, farm_mode); - EEPROM_save_B(EEPROM_FARM_NUMBER, &farm_no); - SilentModeMenu = SILENT_MODE_OFF; - eeprom_update_byte((unsigned char *)EEPROM_SILENT, SilentModeMenu); - fCheckModeInit(); // alternatively invoke printer reset - break; + case 98: + farm_gcode_g98(); + break; /*! ### G99 - Deactivate farm mode G99: Deactivate farm mode - Disables Prusa-specific Farm functions and g-code. - */ - case 99: - farm_mode = 0; - lcd_printer_connected(); - eeprom_update_byte((unsigned char *)EEPROM_FARM_MODE, farm_mode); - lcd_update(2); - fCheckModeInit(); // alternatively invoke printer reset - break; + Disables Prusa-specific Farm functions and g-code. + */ + case 99: + farm_gcode_g99(); + break; +#endif //PRUSA_FARM default: - printf_P(PSTR("Unknown G code: %s \n"), cmdbuffer + bufindr + CMDHDRSIZE); + printf_P(MSG_UNKNOWN_CODE, 'G', cmdbuffer + bufindr + CMDHDRSIZE); } // printf_P(_N("END G-CODE=%u\n"), gcode_in_progress); gcode_in_progress = 0; @@ -5456,80 +5184,93 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) */ - else if(code_seen('M')) + else if(*CMDBUFFER_CURRENT_STRING == 'M') { + strchr_pointer = CMDBUFFER_CURRENT_STRING; int index; for (index = 1; *(strchr_pointer + index) == ' ' || *(strchr_pointer + index) == '\t'; index++); /*for (++strchr_pointer; *strchr_pointer == ' ' || *strchr_pointer == '\t'; ++strchr_pointer);*/ if (*(strchr_pointer+index) < '0' || *(strchr_pointer+index) > '9') { - printf_P(PSTR("Invalid M code: %s \n"), cmdbuffer + bufindr + CMDHDRSIZE); + printf_P(PSTR("Invalid M code: %s\n"), cmdbuffer + bufindr + CMDHDRSIZE); } else { - mcode_in_progress = (int)code_value(); + mcode_in_progress = code_value_short(); // printf_P(_N("BEGIN M-CODE=%u\n"), mcode_in_progress); switch(mcode_in_progress) { /*! - ### M0, M1 - Stop the printer M0: Stop or Unconditional stop + ### M0, M1 - Stop the printer M0: Stop or Unconditional stop + #### Usage + + M0 [P] [string] + M1 [P] [S] [string] + + #### Parameters + + - `P` - Expire time, in milliseconds + - `S` - Expire time, in seconds + - `string` - Must for M1 and optional for M0 message to display on the LCD */ - case 0: // M0 - Unconditional stop - Wait for user button press on LCD - case 1: // M1 - Conditional stop - Wait for user button press on LCD - { - char *src = strchr_pointer + 2; - codenum = 0; - - bool hasP = false, hasS = false; - if (code_seen('P')) { - codenum = code_value(); // milliseconds to wait - hasP = codenum > 0; - } - if (code_seen('S')) { - codenum = code_value() * 1000; // seconds to wait - hasS = codenum > 0; - } - starpos = strchr(src, '*'); - if (starpos != NULL) *(starpos) = '\0'; - while (*src == ' ') ++src; - if (!hasP && !hasS && *src != '\0') { - lcd_setstatus(src); - } else { - LCD_MESSAGERPGM(_i("Wait for user..."));////MSG_USERWAIT - } - - lcd_ignore_click(); //call lcd_ignore_click aslo for else ??? - st_synchronize(); - previous_millis_cmd = _millis(); - if (codenum > 0){ - codenum += _millis(); // keep track of when we started waiting - KEEPALIVE_STATE(PAUSED_FOR_USER); - while(_millis() < codenum && !lcd_clicked()){ - manage_heater(); - manage_inactivity(true); - lcd_update(0); + case 0: + case 1: { + const char *src = strchr_pointer + 2; + codenum = 0; + bool hasP = false, hasS = false; + if (code_seen('P')) { + codenum = code_value_long(); // milliseconds to wait + hasP = codenum > 0; } - KEEPALIVE_STATE(IN_HANDLER); - lcd_ignore_click(false); - }else{ - marlin_wait_for_click(); - } - if (IS_SD_PRINTING) - LCD_MESSAGERPGM(_T(MSG_RESUMING_PRINT)); - else - LCD_MESSAGERPGM(_T(WELCOME_MSG)); + if (code_seen('S')) { + codenum = code_value_long() * 1000; // seconds to wait + hasS = codenum > 0; + } + while (*src == ' ') ++src; + custom_message_type = CustomMsg::M0Wait; + if (!hasP && !hasS && *src != '\0') { + lcd_setstatus(src); + } else { + // farmers want to abuse a bug from the previous firmware releases + // - they need to see the filename on the status screen instead of "Wait for user..." + // So we won't update the message in farm mode... + if( ! farm_mode){ + LCD_MESSAGERPGM(_i("Wait for user..."));////MSG_USERWAIT c=20 + } else { + custom_message_type = CustomMsg::Status; // let the lcd display the name of the printed G-code file in farm mode + } + } + lcd_ignore_click(); //call lcd_ignore_click also for else ??? + st_synchronize(); + previous_millis_cmd.start(); + if (codenum > 0 ) { + codenum += _millis(); // keep track of when we started waiting + KEEPALIVE_STATE(PAUSED_FOR_USER); + while(_millis() < codenum && !lcd_clicked()) { + delay_keep_alive(0); + } + KEEPALIVE_STATE(IN_HANDLER); + lcd_ignore_click(false); + } else { + marlin_wait_for_click(); + } + if (IS_SD_PRINTING) + custom_message_type = CustomMsg::Status; + else + LCD_MESSAGERPGM(MSG_WELCOME); } break; /*! ### M17 - Enable all axes M17: Enable/Power all stepper motors */ + case 17: - LCD_MESSAGERPGM(_i("No move."));////MSG_NO_MOVE + LCD_MESSAGERPGM(_i("No move."));////MSG_NO_MOVE c=20 enable_x(); enable_y(); enable_z(); @@ -5542,12 +5283,19 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) /*! ### M20 - SD Card file list M20: List SD card + #### Usage + + M20 [ L | T ] + #### Parameters + - `T` - Report timestamps as well. The value is one uint32_t encoded as hex. Requires host software parsing (Cap:EXTENDED_M20). + - `L` - Reports long filenames instead of just short filenames. Requires host software parsing (Cap:EXTENDED_M20). */ case 20: + KEEPALIVE_STATE(NOT_BUSY); // do not send busy messages during listing. Inhibits the output of manage_heater() SERIAL_PROTOCOLLNRPGM(_N("Begin file list"));////MSG_BEGIN_FILE_LIST - card.ls(); + card.ls(CardReader::ls_param(code_seen('L'), code_seen('T'))); SERIAL_PROTOCOLLNRPGM(_N("End file list"));////MSG_END_FILE_LIST - break; + break; /*! ### M21 - Init SD card M21: Initialize SD card @@ -5571,28 +5319,39 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) */ case 23: - starpos = (strchr(strchr_pointer + 4,'*')); - if(starpos!=NULL) - *(starpos)='\0'; - card.openFile(strchr_pointer + 4,true); + card.openFileReadFilteredGcode(strchr_pointer + 4, true); break; /*! ### M24 - Start SD print M24: Start/resume SD print */ case 24: - if (isPrintPaused) - lcd_resume_print(); - else + if (isPrintPaused) + lcd_resume_print(); + else + { + if (!card.get_sdpos()) { - failstats_reset_print(); + // A new print has started from scratch, reset stats + failstats_reset_print(); + sdpos_atomic = 0; #ifndef LA_NOCOMPAT - la10c_reset(); + la10c_reset(); #endif - card.startFileprint(); - starttime=_millis(); } - break; + + card.startFileprint(); + starttime=_millis(); + if (MMU2::mmu2.Enabled()) + { + if (MMU2::mmu2.FindaDetectsFilament() && !fsensor.getFilamentPresent()) + { // Filament only half way into the PTFE. Unload the filament. + MMU2::mmu2.unload(); + // Tx and Tc gcodes take care of loading the filament to the nozzle. + } + } + } + break; /*! ### M26 - Set SD index M26: Set SD position @@ -5618,22 +5377,22 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) /*! ### M27 - Get SD status M27: Report SD print status + #### Usage + + M27 [ P ] + + #### Parameters + - `P` - Show full SFN path instead of LFN only. */ case 27: - card.getStatus(); + card.getStatus(code_seen('P')); break; /*! ### M28 - Start SD write M28: Begin write to SD card */ case 28: - starpos = (strchr(strchr_pointer + 4,'*')); - if(starpos != NULL){ - char* npos = strchr(CMDBUFFER_CURRENT_STRING, 'N'); - strchr_pointer = strchr(npos,' ') + 1; - *(starpos) = '\0'; - } - card.openFile(strchr_pointer+4,false); + card.openFileWrite(strchr_pointer+4); break; /*! ### M29 - Stop SD write M29: Stop writing to SD card @@ -5654,12 +5413,6 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) case 30: if (card.cardOK){ card.closefile(); - starpos = (strchr(strchr_pointer + 4,'*')); - if(starpos != NULL){ - char* npos = strchr(CMDBUFFER_CURRENT_STRING, 'N'); - strchr_pointer = strchr(npos,' ') + 1; - *(starpos) = '\0'; - } card.removeFile(strchr_pointer + 4); } break; @@ -5674,9 +5427,8 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) st_synchronize(); } - starpos = (strchr(strchr_pointer + 4,'*')); - char* namestartpos = (strchr(strchr_pointer + 4,'!')); //find ! to indicate filename string start. + const char* namestartpos = (strchr(strchr_pointer + 4,'!')); //find ! to indicate filename string start. if(namestartpos==NULL) { namestartpos=strchr_pointer + 4; //default name position, 4 letters after the M @@ -5684,9 +5436,6 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) else namestartpos++; //to skip the '!' - if(starpos!=NULL) - *(starpos)='\0'; - bool call_procedure=(code_seen('P')); if(strchr_pointer>namestartpos) @@ -5694,16 +5443,24 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) if( card.cardOK ) { - card.openFile(namestartpos,true,!call_procedure); + card.openFileReadFilteredGcode(namestartpos,!call_procedure); if(code_seen('S')) if(strchr_pointer= 0 && pin_status <= 255) - pin_number = code_value(); - for(int8_t i = 0; i < (int8_t)(sizeof(sensitive_pins)/sizeof(int)); i++) + uint8_t pin_status = code_value_uint8(); + int8_t pin_number = LED_PIN; + if (code_seen('P')) + pin_number = code_value_uint8(); + for(int8_t i = 0; i < (int8_t)(sizeof(sensitive_pins)/sizeof(sensitive_pins[0])); i++) { - if (sensitive_pins[i] == pin_number) + if ((int8_t)pgm_read_byte(&sensitive_pins[i]) == pin_number) { pin_number = -1; break; @@ -5790,12 +5541,14 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) */ case 44: // M44: Prusa3D: Reset the bed skew and offset calibration. - // Reset the baby step value and the baby step applied flag. - calibration_status_store(CALIBRATION_STATUS_ASSEMBLED); - eeprom_update_word(reinterpret_cast(&(EEPROM_Sheets_base->s[(eeprom_read_byte(&(EEPROM_Sheets_base->active_sheet)))].z_offset)),0); + // Reset the baby step value and the baby step applied flag. + calibration_status_clear(CALIBRATION_STATUS_LIVE_ADJUST); + eeprom_update_word(reinterpret_cast(&(EEPROM_Sheets_base->s[(eeprom_read_byte(&(EEPROM_Sheets_base->active_sheet)))].z_offset)),0); // Reset the skew and offset in both RAM and EEPROM. + calibration_status_clear(CALIBRATION_STATUS_XYZ); reset_bed_offset_and_skew(); + // Reset world2machine_rotation_and_skew and world2machine_shift, therefore // the planner will not perform any adjustments in the XY plane. // Wait for the motors to stop and update the current position with the absolute values. @@ -5830,39 +5583,39 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) /*! ### M46 - Show the assigned IP address M46: Show the assigned IP address. */ - /* - case 46: + case 46: { // M46: Prusa3D: Show the assigned IP address. - uint8_t ip[4]; - bool hasIP = card.ToshibaFlashAir_GetIP(ip); - if (hasIP) { - SERIAL_ECHOPGM("Toshiba FlashAir current IP: "); - SERIAL_ECHO(int(ip[0])); - SERIAL_ECHOPGM("."); - SERIAL_ECHO(int(ip[1])); - SERIAL_ECHOPGM("."); - SERIAL_ECHO(int(ip[2])); - SERIAL_ECHOPGM("."); - SERIAL_ECHO(int(ip[3])); - SERIAL_ECHOLNPGM(""); + if (card.ToshibaFlashAir_isEnabled()) { + uint8_t ip[4]; + if (card.ToshibaFlashAir_GetIP(ip)) { + // SERIAL_PROTOCOLPGM("Toshiba FlashAir current IP: "); + SERIAL_PROTOCOL(uint8_t(ip[0])); + SERIAL_PROTOCOL('.'); + SERIAL_PROTOCOL(uint8_t(ip[1])); + SERIAL_PROTOCOL('.'); + SERIAL_PROTOCOL(uint8_t(ip[2])); + SERIAL_PROTOCOL('.'); + SERIAL_PROTOCOLLN(uint8_t(ip[3])); + } else { + SERIAL_PROTOCOLPGM("?Toshiba FlashAir GetIP failed\n"); + } } else { - SERIAL_ECHOLNPGM("Toshiba FlashAir GetIP failed"); + SERIAL_PROTOCOLLNPGM("n/a"); } break; } - */ /*! ### M47 - Show end stops dialog on the display M47: Show end stops dialog on the display */ +#ifndef TMC2130 case 47: - KEEPALIVE_STATE(PAUSED_FOR_USER); lcd_diag_show_end_stops(); KEEPALIVE_STATE(IN_HANDLER); break; - +#endif //!TMC2130 #if 0 case 48: // M48: scan the bed induction sensor points, print the sensor trigger coordinates to the serial line for visualization on the PC. { @@ -5876,7 +5629,7 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) world2machine_revert_to_uncorrected(); // Move the print head close to the bed. current_position[Z_AXIS] = MESH_HOME_Z_SEARCH; - plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS],current_position[Z_AXIS] , current_position[E_AXIS], homing_feedrate[Z_AXIS]/40, active_extruder); + plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS],current_position[Z_AXIS] , current_position[E_AXIS], homing_feedrate[Z_AXIS]/40); st_synchronize(); // Home in the XY plane. set_destination_to_current(); @@ -5892,7 +5645,7 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) clean_up_after_endstop_move(l_feedmultiply); // Print head up. current_position[Z_AXIS] = MESH_HOME_Z_SEARCH; - plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS],current_position[Z_AXIS] , current_position[E_AXIS], homing_feedrate[Z_AXIS]/40, active_extruder); + plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS],current_position[Z_AXIS] , current_position[E_AXIS], homing_feedrate[Z_AXIS]/40); st_synchronize(); lcd_update_enable(true); break; @@ -5997,8 +5750,7 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) plan_bed_level_matrix.set_to_identity(); plan_buffer_line( X_current, Y_current, Z_start_location, ext_position, - homing_feedrate[Z_AXIS]/60, - active_extruder); + homing_feedrate[Z_AXIS]/60); st_synchronize(); // @@ -6011,8 +5763,7 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) plan_buffer_line( X_probe_location, Y_probe_location, Z_start_location, ext_position, - homing_feedrate[X_AXIS]/60, - active_extruder); + homing_feedrate[X_AXIS]/60); st_synchronize(); current_position[X_AXIS] = X_current = st_get_position_mm(X_AXIS); @@ -6033,8 +5784,7 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) plan_buffer_line( X_probe_location, Y_probe_location, Z_start_location, ext_position, - homing_feedrate[X_AXIS]/60, - active_extruder); + homing_feedrate[X_AXIS]/60); st_synchronize(); current_position[Z_AXIS] = Z_current = st_get_position_mm(Z_AXIS); @@ -6133,7 +5883,7 @@ if(eSoundMode!=e_SOUND_MODE_SILENT) SERIAL_PROTOCOLPGM("\n"); plan_buffer_line( X_probe_location, Y_probe_location, Z_start_location, - current_position[E_AXIS], homing_feedrate[Z_AXIS]/60, active_extruder); + current_position[E_AXIS], homing_feedrate[Z_AXIS]/60); st_synchronize(); } @@ -6160,49 +5910,55 @@ Sigma_Exit: #endif // Z_PROBE_REPEATABILITY_TEST #endif // ENABLE_AUTO_BED_LEVELING - /*! - ### M73 - Set/get print progress M73: Set/Get build percentage - #### Usage - - M73 [ P | R | Q | S ] - - #### Parameters - - `P` - Percent in normal mode - - `R` - Time remaining in normal mode - - `Q` - Percent in silent mode - - `S` - Time in silent mode - */ - case 73: //M73 show percent done and time remaining - if(code_seen('P')) print_percent_done_normal = code_value(); - if(code_seen('R')) print_time_remaining_normal = code_value(); - if(code_seen('Q')) print_percent_done_silent = code_value(); - if(code_seen('S')) print_time_remaining_silent = code_value(); - - { - const char* _msg_mode_done_remain = _N("%S MODE: Percent done: %d; print time remaining in mins: %d\n"); - printf_P(_msg_mode_done_remain, _N("NORMAL"), int(print_percent_done_normal), print_time_remaining_normal); - printf_P(_msg_mode_done_remain, _N("SILENT"), int(print_percent_done_silent), print_time_remaining_silent); - } - break; - /*! - ### M104 - Set hotend temperature M104: Set Extruder Temperature - #### Usage + ### M73 - Set/get print progress M73: Set/Get build percentage + #### Usage - M104 [ S ] + M73 [ P | R | Q | S | C | D ] + + #### Parameters + - `P` - Percent in normal mode + - `R` - Time remaining in normal mode + - `Q` - Percent in silent mode + - `S` - Time in silent mode + - `C` - Time to change/pause/user interaction in normal mode + - `D` - Time to change/pause/user interaction in silent mode + */ + case 73: //M73 show percent done, time remaining and time to change/pause + { + if(code_seen('P')) print_percent_done_normal = code_value_uint8(); + if(code_seen('R')) print_time_remaining_normal = code_value(); + if(code_seen('Q')) print_percent_done_silent = code_value_uint8(); + if(code_seen('S')) print_time_remaining_silent = code_value(); + if(code_seen('C')){ + float print_time_to_change_normal_f = code_value(); + print_time_to_change_normal = ( print_time_to_change_normal_f <= 0 ) ? PRINT_TIME_REMAINING_INIT : print_time_to_change_normal_f; + } + if(code_seen('D')){ + float print_time_to_change_silent_f = code_value(); + print_time_to_change_silent = ( print_time_to_change_silent_f <= 0 ) ? PRINT_TIME_REMAINING_INIT : print_time_to_change_silent_f; + } + { + const char* _msg_mode_done_remain = _N("%S MODE: Percent done: %hhd; print time remaining in mins: %d; Change in mins: %d\n"); + printf_P(_msg_mode_done_remain, _N("NORMAL"), int8_t(print_percent_done_normal), print_time_remaining_normal, print_time_to_change_normal); + printf_P(_msg_mode_done_remain, _N("SILENT"), int8_t(print_percent_done_silent), print_time_remaining_silent, print_time_to_change_silent); + } + break; + } + /*! + ### M104 - Set hotend temperature M104: Set Extruder Temperature + #### Usage - #### Parameters + M104 [ S ] + + #### Parameters - `S` - Target temperature */ case 104: // M104 { - uint8_t extruder; - if(setTargetedHotend(104,extruder)){ - break; - } if (code_seen('S')) { - setTargetHotendSafe(code_value(), extruder); + setTargetHotend(code_value()); } break; } @@ -6229,117 +5985,66 @@ Sigma_Exit: break; /*! - ### M105 - Report temperatures M105: Get Extruder Temperature - Prints temperatures: - - - `T:` - Hotend (actual / target) - - `B:` - Bed (actual / target) - - `Tx:` - x Tool (actual / target) - - `@:` - Hotend power - - `B@:` - Bed power - - `P:` - PINDAv2 actual (only MK2.5/s and MK3/s) - - `A:` - Ambient actual (only MK3/s) - - _Example:_ - - ok T:20.2 /0.0 B:19.1 /0.0 T0:20.2 /0.0 @:0 B@:0 P:19.8 A:26.4 - + ### M105 - Report temperatures M105: Get Extruder Temperature + Prints temperatures: + + - `T:` - Hotend (actual / target) + - `B:` - Bed (actual / target) + - `Tx:` - x Tool (actual / target) + - `@:` - Hotend power + - `B@:` - Bed power + - `P:` - PINDAv2 actual (only MK2.5/s and MK3/s) + - `A:` - Ambient actual (only MK3/s) + + _Example:_ + + ok T:20.2 /0.0 B:19.1 /0.0 T0:20.2 /0.0 @:0 B@:0 P:19.8 A:26.4 + */ case 105: { - uint8_t extruder; - if(setTargetedHotend(105, extruder)){ - break; - } - #if defined(TEMP_0_PIN) && TEMP_0_PIN > -1 - SERIAL_PROTOCOLPGM("ok T:"); - SERIAL_PROTOCOL_F(degHotend(extruder),1); - SERIAL_PROTOCOLPGM(" /"); - SERIAL_PROTOCOL_F(degTargetHotend(extruder),1); - #if defined(TEMP_BED_PIN) && TEMP_BED_PIN > -1 - SERIAL_PROTOCOLPGM(" B:"); - SERIAL_PROTOCOL_F(degBed(),1); - SERIAL_PROTOCOLPGM(" /"); - SERIAL_PROTOCOL_F(degTargetBed(),1); - #endif //TEMP_BED_PIN - for (int8_t cur_extruder = 0; cur_extruder < EXTRUDERS; ++cur_extruder) { - SERIAL_PROTOCOLPGM(" T"); - SERIAL_PROTOCOL(cur_extruder); - SERIAL_PROTOCOLPGM(":"); - SERIAL_PROTOCOL_F(degHotend(cur_extruder),1); - SERIAL_PROTOCOLPGM(" /"); - SERIAL_PROTOCOL_F(degTargetHotend(cur_extruder),1); - } - #else - SERIAL_ERROR_START; - SERIAL_ERRORLNRPGM(_i("No thermistors - no temperature"));////MSG_ERR_NO_THERMISTORS - #endif - - SERIAL_PROTOCOLPGM(" @:"); - #ifdef EXTRUDER_WATTS - SERIAL_PROTOCOL((EXTRUDER_WATTS * getHeaterPower(tmp_extruder))/127); - SERIAL_PROTOCOLPGM("W"); - #else - SERIAL_PROTOCOL(getHeaterPower(extruder)); - #endif - - SERIAL_PROTOCOLPGM(" B@:"); - #ifdef BED_WATTS - SERIAL_PROTOCOL((BED_WATTS * getHeaterPower(-1))/127); - SERIAL_PROTOCOLPGM("W"); - #else - SERIAL_PROTOCOL(getHeaterPower(-1)); - #endif - -#ifdef PINDA_THERMISTOR - SERIAL_PROTOCOLPGM(" P:"); - SERIAL_PROTOCOL_F(current_temperature_pinda,1); -#endif //PINDA_THERMISTOR - -#ifdef AMBIENT_THERMISTOR - SERIAL_PROTOCOLPGM(" A:"); - SERIAL_PROTOCOL_F(current_temperature_ambient,1); -#endif //AMBIENT_THERMISTOR - - - #ifdef SHOW_TEMP_ADC_VALUES - {float raw = 0.0; - - #if defined(TEMP_BED_PIN) && TEMP_BED_PIN > -1 - SERIAL_PROTOCOLPGM(" ADC B:"); - SERIAL_PROTOCOL_F(degBed(),1); - SERIAL_PROTOCOLPGM("C->"); - raw = rawBedTemp(); - SERIAL_PROTOCOL_F(raw/OVERSAMPLENR,5); - SERIAL_PROTOCOLPGM(" Rb->"); - SERIAL_PROTOCOL_F(100 * (1 + (PtA * (raw/OVERSAMPLENR)) + (PtB * sq((raw/OVERSAMPLENR)))), 5); - SERIAL_PROTOCOLPGM(" Rxb->"); - SERIAL_PROTOCOL_F(raw, 5); - #endif - for (int8_t cur_extruder = 0; cur_extruder < EXTRUDERS; ++cur_extruder) { - SERIAL_PROTOCOLPGM(" T"); - SERIAL_PROTOCOL(cur_extruder); - SERIAL_PROTOCOLPGM(":"); - SERIAL_PROTOCOL_F(degHotend(cur_extruder),1); - SERIAL_PROTOCOLPGM("C->"); - raw = rawHotendTemp(cur_extruder); - SERIAL_PROTOCOL_F(raw/OVERSAMPLENR,5); - SERIAL_PROTOCOLPGM(" Rt"); - SERIAL_PROTOCOL(cur_extruder); - SERIAL_PROTOCOLPGM("->"); - SERIAL_PROTOCOL_F(100 * (1 + (PtA * (raw/OVERSAMPLENR)) + (PtB * sq((raw/OVERSAMPLENR)))), 5); - SERIAL_PROTOCOLPGM(" Rx"); - SERIAL_PROTOCOL(cur_extruder); - SERIAL_PROTOCOLPGM("->"); - SERIAL_PROTOCOL_F(raw, 5); - }} - #endif - SERIAL_PROTOCOLLN(""); - KEEPALIVE_STATE(NOT_BUSY); - return; + SERIAL_PROTOCOLPGM("ok "); + gcode_M105(); + cmdqueue_pop_front(); //prevent an ok after the command since this command uses an ok at the beginning. + cmdbuffer_front_already_processed = true; break; } +#if defined(AUTO_REPORT) + /*! + ### M155 - Automatically send status M155: Automatically send temperatures + #### Usage + + M155 [ S ] [ C ] + + #### Parameters + + - `S` - Set autoreporting interval in seconds. 0 to disable. Maximum: 255 + - `C` - Activate auto-report function (bit mask). Default is temperature. + + bit 0 = Auto-report temperatures + bit 1 = Auto-report fans + bit 2 = Auto-report position + bit 3 = free + bit 4 = free + bit 5 = free + bit 6 = free + bit 7 = free + */ + case 155: + { + if (code_seen('S')){ + autoReportFeatures.SetPeriod( code_value_uint8() ); + } + if (code_seen('C')){ + autoReportFeatures.SetMask(code_value_uint8()); + } else{ + autoReportFeatures.SetMask(1); //Backwards compability to host systems like Octoprint to send only temp if paramerter `C`isn't used. + } + } + break; +#endif //AUTO_REPORT + /*! ### M109 - Wait for extruder temperature M109: Set Extruder Temperature and Wait #### Usage @@ -6358,21 +6063,17 @@ Sigma_Exit: */ case 109: { - uint8_t extruder; - if(setTargetedHotend(109, extruder)){ - break; - } LCD_MESSAGERPGM(_T(MSG_HEATING)); - heating_status = 1; - if (farm_mode) { prusa_statistics(1); }; + heating_status = HeatingStatus::EXTRUDER_HEATING; + prusa_statistics(1); #ifdef AUTOTEMP autotemp_enabled=false; #endif if (code_seen('S')) { - setTargetHotendSafe(code_value(), extruder); + setTargetHotend(code_value()); } else if (code_seen('R')) { - setTargetHotendSafe(code_value(), extruder); + setTargetHotend(code_value()); } #ifdef AUTOTEMP if (code_seen('S')) autotemp_min=code_value(); @@ -6387,21 +6088,18 @@ Sigma_Exit: codenum = _millis(); /* See if we are heating up or cooling down */ - target_direction = isHeatingHotend(extruder); // true if heating, false if cooling - - KEEPALIVE_STATE(NOT_BUSY); + target_direction = isHeatingHotend(active_extruder); // true if heating, false if cooling cancel_heatup = false; - wait_for_heater(codenum, extruder); //loops until target temperature is reached + wait_for_heater(codenum, active_extruder); //loops until target temperature is reached LCD_MESSAGERPGM(_T(MSG_HEATING_COMPLETE)); - KEEPALIVE_STATE(IN_HANDLER); - heating_status = 2; - if (farm_mode) { prusa_statistics(2); }; + heating_status = HeatingStatus::EXTRUDER_HEATING_COMPLETE; + prusa_statistics(2); //starttime=_millis(); - previous_millis_cmd = _millis(); + previous_millis_cmd.start(); } break; @@ -6423,8 +6121,8 @@ Sigma_Exit: { bool CooldownNoWait = false; LCD_MESSAGERPGM(_T(MSG_BED_HEATING)); - heating_status = 3; - if (farm_mode) { prusa_statistics(1); }; + heating_status = HeatingStatus::BED_HEATING; + prusa_statistics(1); if (code_seen('S')) { setTargetBed(code_value()); @@ -6439,20 +6137,12 @@ Sigma_Exit: cancel_heatup = false; target_direction = isHeatingBed(); // true if heating, false if cooling - KEEPALIVE_STATE(NOT_BUSY); - while ( (target_direction)&&(!cancel_heatup) ? (isHeatingBed()) : (isCoolingBed()&&(CooldownNoWait==false)) ) + while ( (!cancel_heatup) && (target_direction ? (isHeatingBed()) : (isCoolingBed()&&(CooldownNoWait==false))) ) { if(( _millis() - codenum) > 1000 ) //Print Temp Reading every 1 second while heating up. { if (!farm_mode) { - float tt = degHotend(active_extruder); - SERIAL_PROTOCOLPGM("T:"); - SERIAL_PROTOCOL(tt); - SERIAL_PROTOCOLPGM(" E:"); - SERIAL_PROTOCOL((int)active_extruder); - SERIAL_PROTOCOLPGM(" B:"); - SERIAL_PROTOCOL_F(degBed(), 1); - SERIAL_PROTOCOLLN(""); + serialecho_temperatures(); } codenum = _millis(); @@ -6462,10 +6152,9 @@ Sigma_Exit: lcd_update(0); } LCD_MESSAGERPGM(_T(MSG_BED_DONE)); - KEEPALIVE_STATE(IN_HANDLER); - heating_status = 4; + heating_status = HeatingStatus::BED_HEATING_COMPLETE; - previous_millis_cmd = _millis(); + previous_millis_cmd.start(); } #endif break; @@ -6483,10 +6172,10 @@ Sigma_Exit: */ case 106: // M106 Sxxx Fan On S 0 .. 255 if (code_seen('S')){ - fanSpeed=constrain(code_value(),0,255); + fanSpeed = code_value_uint8(); } else { - fanSpeed=255; + fanSpeed = 255; } break; @@ -6517,7 +6206,7 @@ Sigma_Exit: #endif powersupply = true; - LCD_MESSAGERPGM(_T(WELCOME_MSG)); + LCD_MESSAGERPGM(MSG_WELCOME); lcd_update(0); break; @@ -6552,7 +6241,7 @@ Sigma_Exit: Makes the extruder interpret extrusion as absolute positions. */ case 82: - axis_relative_modes[E_AXIS] = false; + axis_relative_modes &= ~E_AXIS_MASK; break; /*! @@ -6560,7 +6249,7 @@ Sigma_Exit: Makes the extruder interpret extrusion values as relative positions. */ case 83: - axis_relative_modes[E_AXIS] = true; + axis_relative_modes |= E_AXIS_MASK; break; /*! @@ -6576,7 +6265,7 @@ Sigma_Exit: - `X` - X axis - `Y` - Y axis - `Z` - Z axis - - `E` - Exruder + - `E` - Extruder ### M18 - Disable steppers M18: Disable all stepper motors Equal to M84 (compatibility) @@ -6612,9 +6301,6 @@ Sigma_Exit: #endif } } - //in the end of print set estimated time to end of print and extruders used during print to default values for next print - print_time_remaining_init(); - snmm_filaments_used = 0; break; /*! @@ -6669,7 +6355,7 @@ Sigma_Exit: { if(code_seen(axis_codes[i])) { - if(i == 3) { // E + if(i == E_AXIS) { // E float value = code_value(); if(value < 20.0) { float factor = cs.axis_steps_per_unit[i] / value; // increase e constants if M92 E14 is given for netfab. @@ -6678,12 +6364,16 @@ Sigma_Exit: axis_steps_per_sqr_second[i] *= factor; } cs.axis_steps_per_unit[i] = value; +#if defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125) + fsensor.init(); +#endif //defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125) } else { cs.axis_steps_per_unit[i] = code_value(); } } } + reset_acceleration_rates(); break; /*! @@ -6713,13 +6403,13 @@ Sigma_Exit: */ case 113: if (code_seen('S')) { - host_keepalive_interval = (uint8_t)code_value_short(); + host_keepalive_interval = code_value_uint8(); // NOMORE(host_keepalive_interval, 60); } else { SERIAL_ECHO_START; SERIAL_ECHOPAIR("M113 S", (unsigned long)host_keepalive_interval); - SERIAL_PROTOCOLLN(""); + SERIAL_PROTOCOLLN(); } break; @@ -6769,6 +6459,9 @@ Sigma_Exit: SERIAL_ECHOPGM(STRINGIFY(EXTRUDERS)); SERIAL_ECHOPGM(" UUID:"); SERIAL_ECHOLNPGM(MACHINE_UUID); +#ifdef EXTENDED_CAPABILITIES_REPORT + extended_capabilities_report(); +#endif //EXTENDED_CAPABILITIES_REPORT } break; @@ -6779,30 +6472,31 @@ Sigma_Exit: gcode_M114(); break; - - /* - M117 moved up to get the high priority - - case 117: // M117 display message - starpos = (strchr(strchr_pointer + 5,'*')); - if(starpos!=NULL) - *(starpos)='\0'; - lcd_setstatus(strchr_pointer + 5); - break;*/ - /*! - ### M120 - Enable endstops M120: Enable endstop detection + ### M117 - Display Message M117: Display Message + */ + case 117: { + const char *src = strchr_pointer + 4; // "M117" + lcd_setstatus(*src == ' '? src + 1: src); + custom_message_type = CustomMsg::M117; + } + break; + +#ifdef M120_M121_ENABLED + /*! + ### M120 - Enable endstops M120: Enable endstop detection */ case 120: - enable_endstops(false) ; + enable_endstops(true) ; break; /*! - ### M121 - Disable endstops M121: Disable endstop detection + ### M121 - Disable endstops M121: Disable endstop detection */ case 121: - enable_endstops(true) ; + enable_endstops(false) ; break; +#endif //M120_M121_ENABLED /*! ### M119 - Get endstop states M119: Get Endstop Status @@ -6810,7 +6504,7 @@ Sigma_Exit: */ case 119: SERIAL_PROTOCOLRPGM(_N("Reporting endstop status"));////MSG_M119_REPORT - SERIAL_PROTOCOLLN(""); + SERIAL_PROTOCOLLN(); #if defined(X_MIN_PIN) && X_MIN_PIN > -1 SERIAL_PROTOCOLRPGM(_n("x_min: "));////MSG_X_MIN if(READ(X_MIN_PIN)^X_MIN_ENDSTOP_INVERTING){ @@ -6818,7 +6512,7 @@ Sigma_Exit: }else{ SERIAL_PROTOCOLRPGM(MSG_ENDSTOP_OPEN); } - SERIAL_PROTOCOLLN(""); + SERIAL_PROTOCOLLN(); #endif #if defined(X_MAX_PIN) && X_MAX_PIN > -1 SERIAL_PROTOCOLRPGM(_n("x_max: "));////MSG_X_MAX @@ -6827,7 +6521,7 @@ Sigma_Exit: }else{ SERIAL_PROTOCOLRPGM(MSG_ENDSTOP_OPEN); } - SERIAL_PROTOCOLLN(""); + SERIAL_PROTOCOLLN(); #endif #if defined(Y_MIN_PIN) && Y_MIN_PIN > -1 SERIAL_PROTOCOLRPGM(_n("y_min: "));////MSG_Y_MIN @@ -6836,7 +6530,7 @@ Sigma_Exit: }else{ SERIAL_PROTOCOLRPGM(MSG_ENDSTOP_OPEN); } - SERIAL_PROTOCOLLN(""); + SERIAL_PROTOCOLLN(); #endif #if defined(Y_MAX_PIN) && Y_MAX_PIN > -1 SERIAL_PROTOCOLRPGM(_n("y_max: "));////MSG_Y_MAX @@ -6845,7 +6539,7 @@ Sigma_Exit: }else{ SERIAL_PROTOCOLRPGM(MSG_ENDSTOP_OPEN); } - SERIAL_PROTOCOLLN(""); + SERIAL_PROTOCOLLN(); #endif #if defined(Z_MIN_PIN) && Z_MIN_PIN > -1 SERIAL_PROTOCOLRPGM(MSG_Z_MIN); @@ -6854,7 +6548,7 @@ Sigma_Exit: }else{ SERIAL_PROTOCOLRPGM(MSG_ENDSTOP_OPEN); } - SERIAL_PROTOCOLLN(""); + SERIAL_PROTOCOLLN(); #endif #if defined(Z_MAX_PIN) && Z_MAX_PIN > -1 SERIAL_PROTOCOLRPGM(MSG_Z_MAX); @@ -6863,11 +6557,33 @@ Sigma_Exit: }else{ SERIAL_PROTOCOLRPGM(MSG_ENDSTOP_OPEN); } - SERIAL_PROTOCOLLN(""); + SERIAL_PROTOCOLLN(); #endif break; //!@todo update for all axes, use for loop + +#if (defined(FANCHECK) && (((defined(TACH_0) && (TACH_0 >-1)) || (defined(TACH_1) && (TACH_1 > -1))))) + /*! + ### M123 - Tachometer value M123: Tachometer value + This command is used to report fan speeds and fan pwm values. + #### Usage + M123 + + - E0: - Hotend fan speed in RPM + - PRN1: - Part cooling fans speed in RPM + - E0@: - Hotend fan PWM value + - PRN1@: -Part cooling fan PWM value + + _Example:_ + + E0:3240 RPM PRN1:4560 RPM E0@:255 PRN1@:255 + + */ + case 123: + gcode_M123(); + break; +#endif //FANCHECK and TACH_0 and TACH_1 #ifdef BLINKM /*! @@ -6912,7 +6628,7 @@ Sigma_Exit: uint8_t extruder = active_extruder; if(code_seen('T')) { - extruder = code_value(); + extruder = code_value_uint8(); if(extruder >= EXTRUDERS) { SERIAL_ECHO_START; SERIAL_ECHO(_n("M200 Invalid extruder "));////MSG_M200_INVALID_EXTRUDER @@ -6920,14 +6636,14 @@ Sigma_Exit: } } if(code_seen('D')) { - float diameter = (float)code_value(); + float diameter = code_value(); if (diameter == 0.0) { // setting any extruder filament size disables volumetric on the assumption that // slicers either generate in extruder values as cubic mm or as as filament feeds // for all extruders cs.volumetric_enabled = false; } else { - cs.filament_size[extruder] = (float)code_value(); + cs.filament_size[extruder] = code_value(); // make sure all extruders have some sane value for the filament size cs.filament_size[0] = (cs.filament_size[0] == 0.0 ? DEFAULT_NOMINAL_FILAMENT_DIA : cs.filament_size[0]); #if EXTRUDERS > 1 @@ -6947,8 +6663,17 @@ Sigma_Exit: break; /*! - ### M201 - Set Print Max Acceleration M201: Set max printing acceleration + ### M201 - Set Print Max Acceleration M201: Set max printing acceleration For each axis individually. + ##### Usage + + M201 [ X | Y | Z | E ] + + ##### Parameters + - `X` - Acceleration for X axis in units/s^2 + - `Y` - Acceleration for Y axis in units/s^2 + - `Z` - Acceleration for Z axis in units/s^2 + - `E` - Acceleration for the active or specified extruder in units/s^2 */ case 201: for (int8_t i = 0; i < NUM_AXIS; i++) @@ -6984,11 +6709,20 @@ Sigma_Exit: #endif /*! - ### M203 - Set Max Feedrate M203: Set maximum feedrate + ### M203 - Set Max Feedrate M203: Set maximum feedrate For each axis individually. + ##### Usage + + M203 [ X | Y | Z | E ] + + ##### Parameters + - `X` - Maximum feedrate for X axis + - `Y` - Maximum feedrate for Y axis + - `Z` - Maximum feedrate for Z axis + - `E` - Maximum feedrate for extruder drives */ case 203: // M203 max feedrate mm/sec - for (int8_t i = 0; i < NUM_AXIS; i++) + for (uint8_t i = 0; i < NUM_AXIS; i++) { if (code_seen(axis_codes[i])) { @@ -7039,7 +6773,7 @@ Sigma_Exit: // Legacy acceleration format. This format is used by the legacy Marlin, MK2 or MK3 firmware, // and it is also generated by Slic3r to control acceleration per extrusion type // (there is a separate acceleration settings in Slicer for perimeter, first layer etc). - cs.acceleration = code_value(); + cs.acceleration = cs.travel_acceleration = code_value(); // Interpret the T value as retract acceleration in the old Marlin format. if(code_seen('T')) cs.retract_acceleration = code_value(); @@ -7049,13 +6783,8 @@ Sigma_Exit: cs.acceleration = code_value(); if(code_seen('R')) cs.retract_acceleration = code_value(); - if(code_seen('T')) { - // Interpret the T value as the travel acceleration in the new Marlin format. - /*! - @todo Prusa3D firmware currently does not support travel acceleration value independent from the extruding acceleration value. - */ - // travel_acceleration = code_value(); - } + if(code_seen('T')) + cs.travel_acceleration = code_value(); } } break; @@ -7084,9 +6813,14 @@ Sigma_Exit: if(code_seen('X')) cs.max_jerk[X_AXIS] = cs.max_jerk[Y_AXIS] = code_value(); if(code_seen('Y')) cs.max_jerk[Y_AXIS] = code_value(); if(code_seen('Z')) cs.max_jerk[Z_AXIS] = code_value(); - if(code_seen('E')) cs.max_jerk[E_AXIS] = code_value(); - if (cs.max_jerk[X_AXIS] > DEFAULT_XJERK) cs.max_jerk[X_AXIS] = DEFAULT_XJERK; - if (cs.max_jerk[Y_AXIS] > DEFAULT_YJERK) cs.max_jerk[Y_AXIS] = DEFAULT_YJERK; + if(code_seen('E')) + { + float e = code_value(); +#ifndef LA_NOCOMPAT + e = la10c_jerk(e); +#endif + cs.max_jerk[E_AXIS] = e; + } } break; @@ -7102,13 +6836,14 @@ Sigma_Exit: - `Z` - Z axis offset */ case 206: - for(int8_t i=0; i < 3; i++) + for(uint8_t i=0; i < 3; i++) { if(code_seen(axis_codes[i])) cs.add_homing[i] = code_value(); } break; - #ifdef FWRETRACT + +#ifdef FWRETRACT /*! ### M207 - Set firmware retraction M207: Set retract length #### Usage @@ -7172,8 +6907,7 @@ Sigma_Exit: { if(code_seen('S')) { - int t= code_value() ; - switch(t) + switch(code_value_uint8()) { case 0: { @@ -7207,45 +6941,45 @@ Sigma_Exit: }break; #endif // FWRETRACT - #if EXTRUDERS > 1 - /*! - ### M218 - Set hotend offset M218: Set Hotend Offset - In Prusa Firmware this G-code is only active if `EXTRUDERS` is higher then 1 in the source code. On Original i3 Prusa MK2/s MK2.5/s MK3/s it is not active. + ### M214 - Set Arc configuration values (Use M500 to store in eeprom) M214: Set Arc configuration values + #### Usage - - M218 [ X | Y ] - + + M214 [P] [S] [N] [R] [F] + #### Parameters - - `X` - X offset - - `Y` - Y offset + - `P` - A float representing the max and default millimeters per arc segment. Must be greater than 0. + - `S` - A float representing the minimum allowable millimeters per arc segment. Set to 0 to disable + - `N` - An int representing the number of arcs to draw before correcting the small angle approximation. Set to 0 to disable. + - `R` - An int representing the minimum number of segments per arcs of any radius, + except when the results in segment lengths greater than or less than the minimum + and maximum segment length. Set to 0 to disable. + - `F` - An int representing the number of segments per second, unless this results in segment lengths + greater than or less than the minimum and maximum segment length. Set to 0 to disable. */ - case 218: // M218 - set hotend offset (in mm), T X Y + case 214: //!@n M214 - Set Arc Parameters (Use M500 to store in eeprom) P S R F { - uint8_t extruder; - if(setTargetedHotend(218, extruder)){ - break; - } - if(code_seen('X')) - { - extruder_offset[X_AXIS][extruder] = code_value(); - } - if(code_seen('Y')) - { - extruder_offset[Y_AXIS][extruder] = code_value(); - } - SERIAL_ECHO_START; - SERIAL_ECHORPGM(MSG_HOTEND_OFFSET); - for(extruder = 0; extruder < EXTRUDERS; extruder++) - { - SERIAL_ECHO(" "); - SERIAL_ECHO(extruder_offset[X_AXIS][extruder]); - SERIAL_ECHO(","); - SERIAL_ECHO(extruder_offset[Y_AXIS][extruder]); - } - SERIAL_ECHOLN(""); + // Extract all possible parameters if they appear + float p = code_seen('P') ? code_value() : cs.mm_per_arc_segment; + float s = code_seen('S') ? code_value() : cs.min_mm_per_arc_segment; + unsigned char n = code_seen('N') ? code_value() : cs.n_arc_correction; + unsigned short r = code_seen('R') ? code_value() : cs.min_arc_segments; + unsigned short f = code_seen('F') ? code_value() : cs.arc_segments_per_sec; + + // Ensure mm_per_arc_segment is greater than 0, and that min_mm_per_arc_segment is sero or greater than or equal to mm_per_arc_segment + if (p <=0 || s < 0 || p < s) + { + // Should we display some error here? + break; + } + + cs.mm_per_arc_segment = p; + cs.min_mm_per_arc_segment = s; + cs.n_arc_correction = n; + cs.min_arc_segments = r; + cs.arc_segments_per_sec = f; }break; - #endif /*! ### M220 Set feedrate percentage M220: Set speed factor override percentage @@ -7260,17 +6994,26 @@ Sigma_Exit: */ case 220: // M220 S- set speed factor override percentage { - if (code_seen('B')) //backup current speed factor - { - saved_feedmultiply_mm = feedmultiply; - } - if(code_seen('S')) - { - feedmultiply = code_value() ; - } - if (code_seen('R')) { //restore previous feedmultiply - feedmultiply = saved_feedmultiply_mm; - } + bool codesWereSeen = false; + if (code_seen('B')) //backup current speed factor + { + saved_feedmultiply_mm = feedmultiply; + codesWereSeen = true; + } + if (code_seen('S')) + { + feedmultiply = code_value_short(); + codesWereSeen = true; + } + if (code_seen('R')) //restore previous feedmultiply + { + feedmultiply = saved_feedmultiply_mm; + codesWereSeen = true; + } + if (!codesWereSeen) + { + printf_P(PSTR("%i%%\n"), feedmultiply); + } } break; @@ -7278,31 +7021,22 @@ Sigma_Exit: ### M221 - Set extrude factor override percentage M221: Set extrude factor override percentage #### Usage - M221 [ S | T ] + M221 [ S ] #### Parameters - `S` - Extrude factor override percentage (0..100 or higher), default 100% - - `T` - Extruder drive number (Prusa Firmware only), default 0 if not set. */ case 221: // M221 S- set extrude factor override percentage { - if(code_seen('S')) - { - int tmp_code = code_value(); - if (code_seen('T')) + if (code_seen('S')) { - uint8_t extruder; - if(setTargetedHotend(221, extruder)){ - break; - } - extruder_multiply[extruder] = tmp_code; + extrudemultiply = code_value_short(); + calculate_extruder_multipliers(); } else { - extrudemultiply = tmp_code ; + printf_P(PSTR("%i%%\n"), extrudemultiply); } - } - calculate_extruder_multipliers(); } break; @@ -7320,16 +7054,16 @@ Sigma_Exit: case 226: // M226 P S- Wait until the specified pin reaches the state required { if(code_seen('P')){ - int pin_number = code_value(); // pin number + int pin_number = code_value_short(); // pin number int pin_state = -1; // required pin state - default is inverted - if(code_seen('S')) pin_state = code_value(); // required pin state + if(code_seen('S')) pin_state = code_value_short(); // required pin state if(pin_state >= -1 && pin_state <= 1){ - for(int8_t i = 0; i < (int8_t)(sizeof(sensitive_pins)/sizeof(int)); i++) + for(int8_t i = 0; i < (int8_t)(sizeof(sensitive_pins)/sizeof(sensitive_pins[0])); i++) { - if (sensitive_pins[i] == pin_number) + if (((int8_t)pgm_read_byte(&sensitive_pins[i]) == pin_number)) { pin_number = -1; break; @@ -7412,15 +7146,13 @@ Sigma_Exit: SERIAL_PROTOCOL(" Servo "); SERIAL_PROTOCOL(servo_index); SERIAL_PROTOCOL(": "); - SERIAL_PROTOCOL(servos[servo_index].read()); - SERIAL_PROTOCOLLN(""); + SERIAL_PROTOCOLLN(servos[servo_index].read()); } } break; #endif // NUM_SERVOS > 0 - #if (LARGE_FLASH == true && ( BEEPER > 0 || defined(ULTRALCD) || defined(LCD_USE_I2C_BUZZER))) - + #if (LARGE_FLASH == true && BEEPER > 0 ) /*! ### M300 - Play tone M300: Play beep sound In Prusa Firmware the defaults are `100Hz` and `1000ms`, so that `M300` without parameters will beep for a second. @@ -7434,18 +7166,19 @@ Sigma_Exit: */ case 300: // M300 { - int beepS = code_seen('S') ? code_value() : 110; - int beepP = code_seen('P') ? code_value() : 1000; - if (beepS > 0) - { - #if BEEPER > 0 - Sound_MakeCustom(beepP,beepS,false); - #endif - } - else - { - _delay(beepP); + uint16_t beepP = code_seen('P') ? code_value() : 1000; + uint16_t beepS; + if (!code_seen('S')) + beepS = 0; + else { + beepS = code_value(); + if (!beepS) { + // handle S0 as a pause + _delay(beepP); + break; + } } + Sound_MakeCustom(beepP, beepS, false); } break; #endif // M300 @@ -7458,13 +7191,12 @@ Sigma_Exit: See also PID Tuning. #### Usage - M301 [ P | I | D | C ] + M301 [ P | I | D ] #### Parameters - `P` - proportional (Kp) - `I` - integral (Ki) - `D` - derivative (Kd) - - `C` - heating power=Kc*(e_speed0) */ case 301: { @@ -7472,24 +7204,15 @@ Sigma_Exit: if(code_seen('I')) cs.Ki = scalePID_i(code_value()); if(code_seen('D')) cs.Kd = scalePID_d(code_value()); - #ifdef PID_ADD_EXTRUSION_RATE - if(code_seen('C')) Kc = code_value(); - #endif - updatePID(); SERIAL_PROTOCOLRPGM(MSG_OK); - SERIAL_PROTOCOL(" p:"); + SERIAL_PROTOCOLPGM(" p:"); SERIAL_PROTOCOL(cs.Kp); - SERIAL_PROTOCOL(" i:"); + SERIAL_PROTOCOLPGM(" i:"); SERIAL_PROTOCOL(unscalePID_i(cs.Ki)); - SERIAL_PROTOCOL(" d:"); + SERIAL_PROTOCOLPGM(" d:"); SERIAL_PROTOCOL(unscalePID_d(cs.Kd)); - #ifdef PID_ADD_EXTRUSION_RATE - SERIAL_PROTOCOL(" c:"); - //Kc does not have scaling applied above, or in resetting defaults - SERIAL_PROTOCOL(Kc); - #endif - SERIAL_PROTOCOLLN(""); + SERIAL_PROTOCOLLN(); } break; #endif //PIDTEMP @@ -7516,13 +7239,12 @@ Sigma_Exit: updatePID(); SERIAL_PROTOCOLRPGM(MSG_OK); - SERIAL_PROTOCOL(" p:"); + SERIAL_PROTOCOLPGM(" p:"); SERIAL_PROTOCOL(cs.bedKp); - SERIAL_PROTOCOL(" i:"); + SERIAL_PROTOCOLPGM(" i:"); SERIAL_PROTOCOL(unscalePID_i(cs.bedKi)); - SERIAL_PROTOCOL(" d:"); - SERIAL_PROTOCOL(unscalePID_d(cs.bedKd)); - SERIAL_PROTOCOLLN(""); + SERIAL_PROTOCOLPGM(" d:"); + SERIAL_PROTOCOLLN(unscalePID_d(cs.bedKd)); } break; #endif //PIDTEMP @@ -7579,8 +7301,8 @@ Sigma_Exit: */ case 302: { - float temp = .0; - if (code_seen('S')) temp=code_value(); + int temp = 0; + if (code_seen('S')) temp=code_value_short(); set_extrude_min_temp(temp); } break; @@ -7601,16 +7323,80 @@ Sigma_Exit: case 303: { float temp = 150.0; - int e=0; - int c=5; - if (code_seen('E')) e=code_value(); - if (e<0) - temp=70; - if (code_seen('S')) temp=code_value(); - if (code_seen('C')) c=code_value(); + int e = 0; + int c = 5; + if (code_seen('E')) e = code_value_short(); + if (e < 0) + temp = 70; + if (code_seen('S')) temp = code_value(); + if (code_seen('C')) c = code_value_short(); PID_autotune(temp, e, c); } break; + +#ifdef TEMP_MODEL + /*! + ### M310 - Temperature model settings M310: Temperature model settings + #### Usage + + M310 ; report values + M310 [ A ] [ F ] ; autotune + M310 [ S ] ; set 0=disable 1=enable + M310 [ I ] [ R ] ; set resistance at index + M310 [ P | C ] ; set power, capacitance + M310 [ B | E | W ] ; set beeper, warning and error threshold + M310 [ T ] ; set ambient temperature correction + + #### Parameters + - `I` - resistance index position (0-15) + - `R` - resistance value at index (K/W; requires `I`) + - `P` - power (W) + - `C` - capacitance (J/K) + - `S` - set 0=disable 1=enable + - `B` - beep and warn when reaching warning threshold 0=disable 1=enable (default: 1) + - `E` - error threshold (K/s; default in variant) + - `W` - warning threshold (K/s; default in variant) + - `T` - ambient temperature correction (K; default in variant) + - `A` - autotune C+R values + - `F` - force model self-test state (0=off 1=on) during autotune using current values + */ + case 310: + { + // parse all parameters + float P = NAN, C = NAN, R = NAN, E = NAN, W = NAN, T = NAN; + int8_t I = -1, S = -1, B = -1, F = -1; + int16_t A = -1; + if(code_seen('C')) C = code_value(); + if(code_seen('P')) P = code_value(); + if(code_seen('I')) I = code_value_short(); + if(code_seen('R')) R = code_value(); + if(code_seen('S')) S = code_value_short(); + if(code_seen('B')) B = code_value_short(); + if(code_seen('E')) E = code_value(); + if(code_seen('W')) W = code_value(); + if(code_seen('T')) T = code_value(); + if(code_seen('A')) A = code_value_short(); + if(code_seen('F')) F = code_value_short(); + + // report values if nothing has been requested + if(isnan(C) && isnan(P) && isnan(R) && isnan(E) && isnan(W) && isnan(T) && I < 0 && S < 0 && B < 0 && A < 0) { + temp_model_report_settings(); + break; + } + + // update all parameters + if(B >= 0) temp_model_set_warn_beep(B); + if(!isnan(C) || !isnan(P) || !isnan(T) || !isnan(W) || !isnan(E)) temp_model_set_params(C, P, T, W, E); + if(I >= 0 && !isnan(R)) temp_model_set_resistance(I, R); + + // enable the model last, if requested + if(S >= 0) temp_model_set_enabled(S); + + // run autotune + if(A >= 0) temp_model_autotune(A, F > 0); + } + break; +#endif /*! ### M400 - Wait for all moves to finish M400: Wait for current moves to finish @@ -7639,14 +7425,13 @@ Sigma_Exit: { // currently three different materials are needed (default, flex and PVA) // add storing this information for different load/unload profiles etc. in the future - // firmware does not wait for "ok" from mmu - if (mmu_enabled) + if (MMU2::mmu2.Enabled()) { uint8_t extruder = 255; uint8_t filament = FILAMENT_UNDEFINED; - if(code_seen('E')) extruder = code_value(); - if(code_seen('F')) filament = code_value(); - mmu_set_filament_type(extruder, filament); + if(code_seen('E')) extruder = code_value_uint8(); + if(code_seen('F')) filament = code_value_uint8(); + MMU2::mmu2.set_filament_type(extruder, filament); } } break; @@ -7722,6 +7507,7 @@ Sigma_Exit: break; #endif +#ifdef ENABLE_AUTO_BED_LEVELING /*! ### M851 - Set Z-Probe Offset M851: Set Z-Probe Offset" Sets the Z-probe Z offset. This offset is used to determine the actual Z position of the nozzle when using a probe to home Z with G28. This value may also be used by G81 (Prusa) / G29 (Marlin) to apply correction to the Z position. @@ -7745,7 +7531,7 @@ Sigma_Exit: cs.zprobe_zoffset = -value; // compare w/ line 278 of ConfigurationStore.cpp SERIAL_ECHO_START; SERIAL_ECHOLNRPGM(CAT4(MSG_ZPROBE_ZOFFSET, " ", MSG_OK,PSTR(""))); - SERIAL_PROTOCOLLN(""); + SERIAL_PROTOCOLLN(); } else { @@ -7755,7 +7541,7 @@ Sigma_Exit: SERIAL_ECHO(Z_PROBE_OFFSET_RANGE_MIN); SERIAL_ECHORPGM(MSG_Z_MAX); SERIAL_ECHO(Z_PROBE_OFFSET_RANGE_MAX); - SERIAL_PROTOCOLLN(""); + SERIAL_PROTOCOLLN(); } } else @@ -7763,11 +7549,42 @@ Sigma_Exit: SERIAL_ECHO_START; SERIAL_ECHOLNRPGM(CAT2(MSG_ZPROBE_ZOFFSET, PSTR(" : "))); SERIAL_ECHO(-cs.zprobe_zoffset); - SERIAL_PROTOCOLLN(""); + SERIAL_PROTOCOLLN(); } break; } #endif // CUSTOM_M_CODE_SET_Z_PROBE_OFFSET +#endif // ENABLE_AUTO_BED_LEVELING + + /*! + ### M552 - Set IP address M552: Set IP address, enable/disable network interface" + Sets the printer IP address that is shown in the support menu. Designed to be used with the help of host software. + If P is not specified nothing happens. + If the structure of the IP address is invalid, 0.0.0.0 is assumed and nothing is shown on the screen in the Support menu. + #### Usage + + M552 [ P ] + + #### Parameters + - `P` - The IP address in xxx.xxx.xxx.xxx format. Eg: P192.168.1.14 + */ + case 552: + { + if (code_seen('P')) + { + uint8_t valCnt = 0; + IP_address = 0; + do + { + *strchr_pointer = '*'; + ((uint8_t*)&IP_address)[valCnt] = code_value_short(); + valCnt++; + } while ((valCnt < 4) && code_seen('.')); + + if (valCnt != 4) + IP_address = 0; + } + } break; #ifdef FILAMENTCHANGEENABLE @@ -7781,7 +7598,7 @@ Sigma_Exit: - `X` - X position, default 211 - `Y` - Y position, default 0 - - `Z` - relative lift Z, default 2. + - `Z` - relative lift Z, default MIN_Z_FOR_SWAP. - `E` - initial retract, default -2 - `L` - later retract distance for removal, default -80 - `AUTO` - Automatically (only with MMU) @@ -7792,7 +7609,7 @@ Sigma_Exit: float x_position = current_position[X_AXIS]; float y_position = current_position[Y_AXIS]; - float z_shift = 0; // is it necessary to be a float? + float z_shift = MIN_Z_FOR_SWAP; float e_shift_init = 0; float e_shift_late = 0; bool automatic = false; @@ -7821,16 +7638,10 @@ Sigma_Exit: #endif } - //Lift Z - if(code_seen('Z')) - { - z_shift = code_value(); - } - else - { - z_shift = gcode_M600_filament_change_z_shift(); - } - //Move XY to side + // Z lift. For safety only allow positive values + if (code_seen('Z')) z_shift = fabs(code_value()); + + //Move XY to side if(code_seen('X')) { x_position = code_value(); @@ -7852,7 +7663,7 @@ Sigma_Exit: #endif } - if (mmu_enabled && code_seen("AUTO")) + if (MMU2::mmu2.Enabled() && code_seen_P(PSTR("AUTO"))) automatic = true; gcode_M600(automatic, x_position, y_position, z_shift, e_shift_init, e_shift_late); @@ -7870,34 +7681,36 @@ Sigma_Exit: /*! ### M25 - Pause SD print M25: Pause SD print */ - case 25: - case 601: - { - if (!isPrintPaused) - { + case 25: + case 601: + { + if (!isPrintPaused) { st_synchronize(); + ClearToSend(); //send OK even before the command finishes executing because we want to make sure it is not skipped because of cmdqueue_pop_front(); cmdqueue_pop_front(); //trick because we want skip this command (M601) after restore lcd_pause_print(); } - } - break; + } + break; /*! - ### M602 - Resume print M602: Resume print + ### M602 - Resume print M602: Resume print */ - case 602: { - if (isPrintPaused) - lcd_resume_print(); - } - break; + case 602: + { + if (isPrintPaused) lcd_resume_print(); + } + break; /*! ### M603 - Stop print M603: Stop print */ - case 603: { - lcd_print_stop(); - } - break; + + case 603: { + print_stop(); + } + break; + case 850: { //! ### M850 - set sheet parameters //! //!@n M850 - Set sheet data S[id] Z[offset] L[label] B[bed_temp] P[PINDA_TEMP] @@ -8028,7 +7841,7 @@ Sigma_Exit: int set_target_pinda = 0; if (code_seen('S')) { - set_target_pinda = code_value(); + set_target_pinda = code_value_short(); } else { break; @@ -8037,8 +7850,7 @@ Sigma_Exit: LCD_MESSAGERPGM(_T(MSG_PLEASE_WAIT)); SERIAL_PROTOCOLPGM("Wait for PINDA target temperature:"); - SERIAL_PROTOCOL(set_target_pinda); - SERIAL_PROTOCOLLN(""); + SERIAL_PROTOCOLLN(set_target_pinda); codenum = _millis(); cancel_heatup = false; @@ -8053,9 +7865,8 @@ Sigma_Exit: { SERIAL_PROTOCOLPGM("P:"); SERIAL_PROTOCOL_F(current_temperature_pinda, 1); - SERIAL_PROTOCOLPGM("/"); - SERIAL_PROTOCOL(set_target_pinda); - SERIAL_PROTOCOLLN(""); + SERIAL_PROTOCOL('/'); + SERIAL_PROTOCOLLN(set_target_pinda); codenum = _millis(); } manage_heater(); @@ -8081,15 +7892,18 @@ Sigma_Exit: - `S` - Microsteps - `I` - Table index */ - case 861: + case 861: { + const char * const _header = PSTR("index, temp, ustep, um"); if (code_seen('?')) { // ? - Print out current EEPROM offset values - uint8_t cal_status = calibration_status_pinda(); int16_t usteps = 0; - cal_status ? SERIAL_PROTOCOLLN("PINDA cal status: 1") : SERIAL_PROTOCOLLN("PINDA cal status: 0"); - SERIAL_PROTOCOLLN("index, temp, ustep, um"); + SERIAL_PROTOCOLPGM("PINDA cal status: "); + SERIAL_PROTOCOLLN(calibration_status_pinda()); + SERIAL_PROTOCOLLNRPGM(_header); for (uint8_t i = 0; i < 6; i++) { - if(i>0) EEPROM_read_B(EEPROM_PROBE_TEMP_SHIFT + (i-1) * 2, &usteps); + if(i > 0) { + usteps = eeprom_read_word((uint16_t*) EEPROM_PROBE_TEMP_SHIFT + (i - 1)); + } float mm = ((float)usteps) / cs.axis_steps_per_unit[Z_AXIS]; i == 0 ? SERIAL_PROTOCOLPGM("n/a") : SERIAL_PROTOCOL(i - 1); SERIAL_PROTOCOLPGM(", "); @@ -8097,42 +7911,45 @@ Sigma_Exit: SERIAL_PROTOCOLPGM(", "); SERIAL_PROTOCOL(usteps); SERIAL_PROTOCOLPGM(", "); - SERIAL_PROTOCOL(mm * 1000); - SERIAL_PROTOCOLLN(""); + SERIAL_PROTOCOLLN(mm * 1000); } } else if (code_seen('!')) { // ! - Set factory default values eeprom_write_byte((uint8_t*)EEPROM_CALIBRATION_STATUS_PINDA, 1); int16_t z_shift = 8; //40C - 20um - 8usteps - EEPROM_save_B(EEPROM_PROBE_TEMP_SHIFT, &z_shift); + eeprom_update_word((uint16_t*)EEPROM_PROBE_TEMP_SHIFT, z_shift); z_shift = 24; //45C - 60um - 24usteps - EEPROM_save_B(EEPROM_PROBE_TEMP_SHIFT + 2, &z_shift); + eeprom_update_word((uint16_t*)EEPROM_PROBE_TEMP_SHIFT + 1, z_shift); z_shift = 48; //50C - 120um - 48usteps - EEPROM_save_B(EEPROM_PROBE_TEMP_SHIFT + 4, &z_shift); + eeprom_update_word((uint16_t*)EEPROM_PROBE_TEMP_SHIFT + 2, z_shift); z_shift = 80; //55C - 200um - 80usteps - EEPROM_save_B(EEPROM_PROBE_TEMP_SHIFT + 6, &z_shift); + eeprom_update_word((uint16_t*)EEPROM_PROBE_TEMP_SHIFT + 3, z_shift); z_shift = 120; //60C - 300um - 120usteps - EEPROM_save_B(EEPROM_PROBE_TEMP_SHIFT + 8, &z_shift); - SERIAL_PROTOCOLLN("factory restored"); + eeprom_update_word((uint16_t*)EEPROM_PROBE_TEMP_SHIFT + 4, z_shift); + SERIAL_PROTOCOLLNPGM("factory restored"); } else if (code_seen('Z')) { // Z - Set all values to 0 (effectively disabling PINDA temperature compensation) eeprom_write_byte((uint8_t*)EEPROM_CALIBRATION_STATUS_PINDA, 1); int16_t z_shift = 0; - for (uint8_t i = 0; i < 5; i++) EEPROM_save_B(EEPROM_PROBE_TEMP_SHIFT + i * 2, &z_shift); - SERIAL_PROTOCOLLN("zerorized"); + for (uint8_t i = 0; i < 5; i++) { + eeprom_update_word((uint16_t*)EEPROM_PROBE_TEMP_SHIFT + i, z_shift); + } + SERIAL_PROTOCOLLNPGM("zerorized"); } else if (code_seen('S')) { // Sxxx Iyyy - Set compensation ustep value S for compensation table index I - int16_t usteps = code_value(); + int16_t usteps = code_value_short(); if (code_seen('I')) { - uint8_t index = code_value(); + uint8_t index = code_value_uint8(); if (index < 5) { - EEPROM_save_B(EEPROM_PROBE_TEMP_SHIFT + index * 2, &usteps); - SERIAL_PROTOCOLLN("OK"); - SERIAL_PROTOCOLLN("index, temp, ustep, um"); + eeprom_update_word((uint16_t*)EEPROM_PROBE_TEMP_SHIFT + index, usteps); + SERIAL_PROTOCOLLNRPGM(MSG_OK); + SERIAL_PROTOCOLLNRPGM(_header); for (uint8_t i = 0; i < 6; i++) { usteps = 0; - if (i>0) EEPROM_read_B(EEPROM_PROBE_TEMP_SHIFT + (i - 1) * 2, &usteps); + if (i > 0) { + usteps = eeprom_read_word((uint16_t*)EEPROM_PROBE_TEMP_SHIFT + (i - 1)); + } float mm = ((float)usteps) / cs.axis_steps_per_unit[Z_AXIS]; i == 0 ? SERIAL_PROTOCOLPGM("n/a") : SERIAL_PROTOCOL(i - 1); SERIAL_PROTOCOLPGM(", "); @@ -8140,16 +7957,15 @@ Sigma_Exit: SERIAL_PROTOCOLPGM(", "); SERIAL_PROTOCOL(usteps); SERIAL_PROTOCOLPGM(", "); - SERIAL_PROTOCOL(mm * 1000); - SERIAL_PROTOCOLLN(""); + SERIAL_PROTOCOLLN(mm * 1000); } } } } else { - SERIAL_PROTOCOLPGM("no valid command"); + SERIAL_PROTOCOLLNPGM("no valid command"); } - break; + } break; #endif //PINDA_THERMISTOR @@ -8191,7 +8007,7 @@ Sigma_Exit: case 862: // M862: print checking float nDummy; uint8_t nCommand; - nCommand=(uint8_t)(modff(code_value_float(),&nDummy)*10.0+0.5); + nCommand=(uint8_t)(modff(code_value(),&nDummy)*10.0+0.5); switch((ClPrintChecking)nCommand) { case ClPrintChecking::_Nozzle: // ~ .1 @@ -8201,38 +8017,35 @@ Sigma_Exit: nDiameter=(uint16_t)(code_value()*1000.0+0.5); // [,um] nozzle_diameter_check(nDiameter); } -/* - else if(code_seen('S')&&farm_mode) - { - nDiameter=(uint16_t)(code_value()*1000.0+0.5); // [,um] - eeprom_update_byte((uint8_t*)EEPROM_NOZZLE_DIAMETER,(uint8_t)ClNozzleDiameter::_Diameter_Undef); // for correct synchronization after farm-mode exiting - eeprom_update_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM,nDiameter); - } -*/ else if(code_seen('Q')) SERIAL_PROTOCOLLN((float)eeprom_read_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM)/1000.0); break; - case ClPrintChecking::_Model: // ~ .2 + case ClPrintChecking::_Model: { // ~ .2 + uint16_t type = nPrinterType(MMU2::mmu2.Enabled()); if(code_seen('P')) { uint16_t nPrinterModel; nPrinterModel=(uint16_t)code_value_long(); - printer_model_check(nPrinterModel); + // based on current state of MMU (active/stopped/connecting) perform a runtime update of the printer type + printer_model_check(nPrinterModel, type); } else if(code_seen('Q')) - SERIAL_PROTOCOLLN(nPrinterType); - break; - case ClPrintChecking::_Smodel: // ~ .3 + SERIAL_PROTOCOLLN(type); + } break; + case ClPrintChecking::_Smodel: { // ~ .3 + const char *type = sPrinterType(MMU2::mmu2.Enabled()); if(code_seen('P')) - printer_smodel_check(strchr_pointer); + { + printer_smodel_check(strchr_pointer, type); + } else if(code_seen('Q')) - SERIAL_PROTOCOLLNRPGM(sPrinterName); - break; + SERIAL_PROTOCOLLNRPGM(type); + } break; case ClPrintChecking::_Version: // ~ .4 if(code_seen('P')) fw_version_check(++strchr_pointer); else if(code_seen('Q')) - SERIAL_PROTOCOLLN(FW_VERSION); + SERIAL_PROTOCOLLNRPGM(FW_VERSION_STR_P()); break; case ClPrintChecking::_Gcode: // ~ .5 if(code_seen('P')) @@ -8270,6 +8083,7 @@ Sigma_Exit: /*! ### M907 - Set digital trimpot motor current in mA using axis codes M907: Set digital trimpot motor Set digital trimpot motor current using axis codes (X, Y, Z, E, B, S). + M907 has no effect when the experimental Extruder motor current scaling mode is active (that applies to farm printing as well) #### Usage M907 [ X | Y | Z | E | B | S ] @@ -8286,16 +8100,20 @@ Sigma_Exit: { #ifdef TMC2130 // See tmc2130_cur2val() for translation to 0 .. 63 range - for (int i = 0; i < NUM_AXIS; i++) - if(code_seen(axis_codes[i])) - { - long cur_mA = code_value_long(); - uint8_t val = tmc2130_cur2val(cur_mA); - tmc2130_set_current_h(i, val); - tmc2130_set_current_r(i, val); - //if (i == E_AXIS) printf_P(PSTR("E-axis current=%ldmA\n"), cur_mA); - } - + for (uint_least8_t i = 0; i < NUM_AXIS; i++){ + if(code_seen(axis_codes[i])){ + if( i == E_AXIS && FarmOrUserECool() ){ + SERIAL_ECHORPGM(eMotorCurrentScalingEnabled); + SERIAL_ECHOLNPGM(", M907 E ignored"); + continue; + } + long cur_mA = code_value_long(); + uint8_t val = tmc2130_cur2val(cur_mA); + tmc2130_set_current_h(i, val); + tmc2130_set_current_r(i, val); + //if (i == E_AXIS) printf_P(PSTR("E-axis current=%ldmA\n"), cur_mA); + } + } #else //TMC2130 #if defined(DIGIPOTSS_PIN) && DIGIPOTSS_PIN > -1 for(int i=0;i 0 && res_new <= 256 && !(res_new & (res_new - 1)); // must be a power of two +#else bool res_valid = (res_new == 8) || (res_new == 16) || (res_new == 32); // resolutions valid for all axis res_valid |= (i != E_AXIS) && ((res_new == 1) || (res_new == 2) || (res_new == 4)); // resolutions valid for X Y Z only res_valid |= (i == E_AXIS) && ((res_new == 64) || (res_new == 128)); // resolutions valid for E only +#endif if (res_valid) { - st_synchronize(); uint16_t res = tmc2130_get_res(i); tmc2130_set_res(i, res_new); @@ -8547,9 +8368,14 @@ Sigma_Exit: cs.axis_steps_per_unit[i] /= fac; position[i] /= fac; } +#if defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125) + if (i == E_AXIS) + fsensor.init(); +#endif //defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125) } } } + reset_acceleration_rates(); #else //TMC2130 #if defined(X_MS1_PIN) && X_MS1_PIN > -1 if(code_seen('S')) for(int i=0;i<=4;i++) microstep_mode(i,code_value()); @@ -8595,66 +8421,220 @@ Sigma_Exit: } break; - /*! - ### M701 - Load filament M701: Load filament - - */ - case 701: - { - if (mmu_enabled && code_seen('E')) - tmp_extruder = code_value(); - gcode_M701(); - } - break; + /*! + ### M701 - Load filament to extruder M701: Load filament + Load filament into the active extruder. + #### Usage + + M701 [ P | T | L | Z ] + + #### Parameters + - `P` - n index of MMU slot (zero based, so 0-4 like T0 and T4) + - `T` - Alias of `P`. Used for compatibility with Marlin + - `L` - Extrude distance for insertion (positive value)(manual reload) + - `Z` - Move the Z axis by this distance. Default value MIN_Z_FOR_LOAD + */ + case 701: + { + uint8_t mmuSlotIndex = 0xffU; + float fastLoadLength = FILAMENTCHANGE_FIRSTFEED; // Only used without MMU + float z_target = MIN_Z_FOR_LOAD; + if( MMU2::mmu2.Enabled() ) + { + if( code_seen('P') || code_seen('T') ) { + mmuSlotIndex = code_value_uint8(); + } + } + + if (code_seen('L')) fastLoadLength = code_value(); + + // Z lift. For safety only allow positive values + if (code_seen('Z')) z_target = fabs(code_value()); + + // Raise the Z axis + float delta = raise_z(z_target); + + // Load filament + gcode_M701(fastLoadLength, mmuSlotIndex); + + // Restore Z axis + raise_z(-delta); + } + break; /*! ### M702 - Unload filament G32: Undock Z Probe sled #### Usage - M702 [ U | C ] + M702 [ U | Z ] #### Parameters - - `U` - Unload all filaments used in current print - - `C` - Unload just current filament - - without any parameters unload all filaments + - `U` - Retract distance for removal (manual reload). Default value is 0. + - `Z` - Move the Z axis by this distance. Default value MIN_Z_FOR_UNLOAD. */ - case 702: - { -#ifdef SNMM - if (code_seen('U')) - extr_unload_used(); //! if "U" unload all filaments which were used in current print - else if (code_seen('C')) - extr_unload(); //! if "C" unload just current filament - else - extr_unload_all(); //! otherwise unload all filaments -#else - if (code_seen('C')) { - if(mmu_enabled) extr_unload(); //! if "C" unload current filament; if mmu is not present no action is performed - } - else { - if(mmu_enabled) extr_unload(); //! unload current filament - else unload_filament(); - } + case 702: + { + float z_target = MIN_Z_FOR_UNLOAD; + float unloadLength = FILAMENTCHANGE_FINALRETRACT; + if (code_seen('U')) unloadLength = code_value(); -#endif //SNMM - } - break; + // For safety only allow positive values + if (code_seen('Z')) z_target = fabs(code_value()); + + // Raise the Z axis + float delta = raise_z(z_target); + + // Unload filament + if (MMU2::mmu2.Enabled()) MMU2::mmu2.unload(); + else unload_filament(unloadLength); + + // Restore Z axis + raise_z(-delta); + } + break; /*! - ### M999 - Restart after being stopped M999: Restart after being stopped by error - @todo Usually doesn't work. Should be fixed or removed. Most of the time, if `Stopped` it set, the print fails and is unrecoverable. + ### M704 - Load to MMU M704: Load to MMU + #### Usage + + M704 [ P ] + + #### Parameters + - `P` - n index of slot (zero based, so 0-4 like T0 and T4) */ - case 999: - Stopped = false; - lcd_reset_alert_level(); - gcode_LastN = Stopped_gcode_LastN; - FlushSerialRequestResend(); + case 704: + { + gcodes_M704_M705_M706(704); + } break; + + /*! + ### M705 - Eject filament M705: Eject filament + #### Usage + + M705 [ P ] + + #### Parameters + - `P` - n index of slot (zero based, so 0-4 like T0 and T4) + */ + case 705: + { + gcodes_M704_M705_M706(705); + } + break; + + + /*! + ### M706 - Cut filament M706: Cut filament + #### Usage + + M706 [ P ] + + #### Parameters + - `P` - n index of slot (zero based, so 0-4 like T0 and T4) + */ + case 706: + { + gcodes_M704_M705_M706(706); + } + break; + + /*! + ### M707 - Read from MMU register M707: Read from MMU register + #### Usage + + M707 [ A ] + + #### Parameters + - `A` - Address of register in hexidecimal. + + #### Example + + M707 A0x1b - Read a 8bit integer from register 0x1b and prints the result onto the serial line. + + Does nothing if the A parameter is not present or if MMU is not enabled. + + */ + case 707: { + if ( MMU2::mmu2.Enabled() ) { + if( code_seen('A') ) { + MMU2::mmu2.ReadRegister(uint8_t(strtol(strchr_pointer+1, NULL, 16))); + } + } + } break; + + /*! + ### M708 - Write to MMU register M707: Write to MMU register + #### Usage + + M708 [ A | X ] + + #### Parameters + - `A` - Address of register in hexidecimal. + - `X` - Data to write (16-bit integer). Default value 0. + + #### Example + M708 A0x1b X05 - Write to register 0x1b the value 05. + + Does nothing if A parameter is missing or if MMU is not enabled. + */ + case 708: { + if ( MMU2::mmu2.Enabled() ){ + uint8_t addr = 0; + if( code_seen('A') ) { + addr = uint8_t(strtol(strchr_pointer+1, NULL, 16)); + } + uint16_t data = 0; + if( code_seen('X') ) { + data = code_value_short(); + } + if(addr){ + MMU2::mmu2.WriteRegister(addr, data); + } + } + } break; + + /*! + ### M709 - MMU reset M709: MMU reset + The MK3S cannot not power off the MMU, for that reason the functionality is not supported. + #### Usage + + M709 [ X ] + + #### Parameters + - `X` - Reset MMU (0:soft reset | 1:hardware reset) + + #### Example + + M709 X0 - issue an X0 command via communication into the MMU (soft reset) + + M709 X1 - toggle the MMU's reset pin (hardware reset) + + */ + case 709: + { + if (MMU2::mmu2.Enabled() && code_seen('X')) + { + switch (code_value_uint8()) + { + case 0: + MMU2::mmu2.Reset(MMU2::MMU2::Software); + break; + case 1: + MMU2::mmu2.Reset(MMU2::MMU2::ResetPin); + break; + default: + break; + } + } + } + break; + /*! #### End of M-Commands */ default: - printf_P(PSTR("Unknown M code: %s \n"), cmdbuffer + bufindr + CMDHDRSIZE); + printf_P(MSG_UNKNOWN_CODE, 'M', cmdbuffer + bufindr + CMDHDRSIZE); } // printf_P(_N("END M-CODE=%u\n"), mcode_in_progress); mcode_in_progress = 0; @@ -8671,181 +8651,11 @@ Sigma_Exit: @n Tx Same as T?, except nozzle doesn't have to be preheated. Tc must be placed after extruder nozzle is preheated to finish filament load. @n Tc Load to nozzle after filament was prepared by Tc and extruder nozzle is already heated. */ - else if(code_seen('T')) - { - int index; - bool load_to_nozzle = false; - for (index = 1; *(strchr_pointer + index) == ' ' || *(strchr_pointer + index) == '\t'; index++); - - *(strchr_pointer + index) = tolower(*(strchr_pointer + index)); - - if ((*(strchr_pointer + index) < '0' || *(strchr_pointer + index) > '4') && *(strchr_pointer + index) != '?' && *(strchr_pointer + index) != 'x' && *(strchr_pointer + index) != 'c') { - SERIAL_ECHOLNPGM("Invalid T code."); - } - else if (*(strchr_pointer + index) == 'x'){ //load to bondtech gears; if mmu is not present do nothing - if (mmu_enabled) - { - tmp_extruder = choose_menu_P(_T(MSG_CHOOSE_FILAMENT), _T(MSG_FILAMENT)); - if ((tmp_extruder == mmu_extruder) && mmu_fil_loaded) //dont execute the same T-code twice in a row - { - printf_P(PSTR("Duplicate T-code ignored.\n")); - } - else - { - st_synchronize(); - mmu_command(MmuCmd::T0 + tmp_extruder); - manage_response(true, true, MMU_TCODE_MOVE); - } - } - } - else if (*(strchr_pointer + index) == 'c') { //load to from bondtech gears to nozzle (nozzle should be preheated) - if (mmu_enabled) - { - st_synchronize(); - mmu_continue_loading(is_usb_printing || (lcd_commands_type == LcdCommands::Layer1Cal)); - mmu_extruder = tmp_extruder; //filament change is finished - mmu_load_to_nozzle(); - } - } - else { - if (*(strchr_pointer + index) == '?') - { - if(mmu_enabled) - { - tmp_extruder = choose_menu_P(_T(MSG_CHOOSE_FILAMENT), _T(MSG_FILAMENT)); - load_to_nozzle = true; - } else - { - tmp_extruder = choose_menu_P(_T(MSG_CHOOSE_EXTRUDER), _T(MSG_EXTRUDER)); - } - } - else { - tmp_extruder = code_value(); - if (mmu_enabled && lcd_autoDepleteEnabled()) - { - tmp_extruder = ad_getAlternative(tmp_extruder); - } - } - st_synchronize(); - snmm_filaments_used |= (1 << tmp_extruder); //for stop print - - if (mmu_enabled) - { - if ((tmp_extruder == mmu_extruder) && mmu_fil_loaded) //dont execute the same T-code twice in a row - { - printf_P(PSTR("Duplicate T-code ignored.\n")); - } - else - { -#if defined(MMU_HAS_CUTTER) && defined(MMU_ALWAYS_CUT) - if (EEPROM_MMU_CUTTER_ENABLED_always == eeprom_read_byte((uint8_t*)EEPROM_MMU_CUTTER_ENABLED)) - { - mmu_command(MmuCmd::K0 + tmp_extruder); - manage_response(true, true, MMU_UNLOAD_MOVE); - } -#endif //defined(MMU_HAS_CUTTER) && defined(MMU_ALWAYS_CUT) - mmu_command(MmuCmd::T0 + tmp_extruder); - manage_response(true, true, MMU_TCODE_MOVE); - mmu_continue_loading(is_usb_printing || (lcd_commands_type == LcdCommands::Layer1Cal)); - - mmu_extruder = tmp_extruder; //filament change is finished - - if (load_to_nozzle)// for single material usage with mmu - { - mmu_load_to_nozzle(); - } - } - } - else - { -#ifdef SNMM - mmu_extruder = tmp_extruder; - - _delay(100); - - disable_e0(); - disable_e1(); - disable_e2(); - - pinMode(E_MUX0_PIN, OUTPUT); - pinMode(E_MUX1_PIN, OUTPUT); - - _delay(100); - SERIAL_ECHO_START; - SERIAL_ECHO("T:"); - SERIAL_ECHOLN((int)tmp_extruder); - switch (tmp_extruder) { - case 1: - WRITE(E_MUX0_PIN, HIGH); - WRITE(E_MUX1_PIN, LOW); - - break; - case 2: - WRITE(E_MUX0_PIN, LOW); - WRITE(E_MUX1_PIN, HIGH); - - break; - case 3: - WRITE(E_MUX0_PIN, HIGH); - WRITE(E_MUX1_PIN, HIGH); - - break; - default: - WRITE(E_MUX0_PIN, LOW); - WRITE(E_MUX1_PIN, LOW); - - break; - } - _delay(100); - -#else //SNMM - if (tmp_extruder >= EXTRUDERS) { - SERIAL_ECHO_START; - SERIAL_ECHOPGM("T"); - SERIAL_PROTOCOLLN((int)tmp_extruder); - SERIAL_ECHOLNRPGM(_n("Invalid extruder"));////MSG_INVALID_EXTRUDER - } - else { -#if EXTRUDERS > 1 - boolean make_move = false; -#endif - if (code_seen('F')) { -#if EXTRUDERS > 1 - make_move = true; -#endif - next_feedrate = code_value(); - if (next_feedrate > 0.0) { - feedrate = next_feedrate; - } - } -#if EXTRUDERS > 1 - if (tmp_extruder != active_extruder) { - // Save current position to return to after applying extruder offset - memcpy(destination, current_position, sizeof(destination)); - // Offset extruder (only by XY) - int i; - for (i = 0; i < 2; i++) { - current_position[i] = current_position[i] - - extruder_offset[i][active_extruder] + - extruder_offset[i][tmp_extruder]; - } - // Set the new active extruder and position - active_extruder = tmp_extruder; - plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); - // Move to the old position if 'F' was in the parameters - if (make_move && Stopped == false) { - prepare_move(); - } - } -#endif - SERIAL_ECHO_START; - SERIAL_ECHORPGM(_n("Active Extruder: "));////MSG_ACTIVE_EXTRUDER - SERIAL_PROTOCOLLN((int)active_extruder); - } - -#endif //SNMM - } - } + else if(*CMDBUFFER_CURRENT_STRING == 'T') { + strchr_pointer = CMDBUFFER_CURRENT_STRING; + processing_tcode = true; + TCodes(strchr_pointer, code_value_uint8()); + processing_tcode = false; } // end if(code_seen('T')) (end of T codes) /*! #### End of T-Codes @@ -8855,9 +8665,10 @@ Sigma_Exit: *--------------------------------------------------------------------------------- *# D codes */ - else if (code_seen('D')) // D codes (debug) + else if(*CMDBUFFER_CURRENT_STRING == 'D') // D codes (debug) { - switch((int)code_value()) + strchr_pointer = CMDBUFFER_CURRENT_STRING; + switch(code_value_short()) { /*! @@ -8889,23 +8700,31 @@ Sigma_Exit: */ case 1: dcode_1(); break; +#endif +#if defined DEBUG_DCODE2 || defined DEBUG_DCODES /*! ### D2 - Read/Write RAM D3: Read/Write RAM This command can be used without any additional parameters. It will read the entire RAM. #### Usage - D3 [ A | C | X ] + D2 [ A | C | X ] #### Parameters - - `A` - Address (0x0000-0x1fff) - - `C` - Count (0x0001-0x2000) + - `A` - Address (x0000-x1fff) + - `C` - Count (1-8192) - `X` - Data + + #### Notes + - The hex address needs to be lowercase without the 0 before the x + - Count is decimal + - The hex data needs to be lowercase + */ case 2: dcode_2(); break; #endif //DEBUG_DCODES -#ifdef DEBUG_DCODE3 +#if defined DEBUG_DCODE3 || defined DEBUG_DCODES /*! ### D3 - Read/Write EEPROM D3: Read/Write EEPROM @@ -8915,9 +8734,15 @@ Sigma_Exit: D3 [ A | C | X ] #### Parameters - - `A` - Address (0x0000-0x0fff) - - `C` - Count (0x0001-0x1000) - - `X` - Data + - `A` - Address (x0000-x0fff) + - `C` - Count (1-4096) + - `X` - Data (hex) + + #### Notes + - The hex address needs to be lowercase without the 0 before the x + - Count is decimal + - The hex data needs to be lowercase + */ case 3: dcode_3(); break; @@ -8940,26 +8765,31 @@ Sigma_Exit: case 4: dcode_4(); break; #endif //DEBUG_DCODES -#ifdef DEBUG_DCODE5 +#if defined DEBUG_DCODE5 || defined DEBUG_DCODES /*! ### D5 - Read/Write FLASH D5: Read/Write Flash This command can be used without any additional parameters. It will read the 1kb FLASH. #### Usage - D3 [ A | C | X | E ] + D5 [ A | C | X | E ] #### Parameters - - `A` - Address (0x00000-0x3ffff) - - `C` - Count (0x0001-0x2000) - - `X` - Data + - `A` - Address (x00000-x3ffff) + - `C` - Count (1-8192) + - `X` - Data (hex) - `E` - Erase - */ + + #### Notes + - The hex address needs to be lowercase without the 0 before the x + - Count is decimal + - The hex data needs to be lowercase + + */ case 5: dcode_5(); break; - break; #endif //DEBUG_DCODE5 -#ifdef DEBUG_DCODES +#if defined DEBUG_DCODE6 || defined DEBUG_DCODES /*! ### D6 - Read/Write external FLASH D6: Read/Write external Flash @@ -8967,6 +8797,8 @@ Sigma_Exit: */ case 6: dcode_6(); break; +#endif +#ifdef DEBUG_DCODES /*! ### D7 - Read/Write Bootloader D7: Read/Write Bootloader @@ -9018,10 +8850,93 @@ Sigma_Exit: /*! ### D12 - Time D12: Time - Writes the actual time in the log file. + Writes the current time in the log file. */ - #endif //DEBUG_DCODES + +#ifdef XFLASH_DUMP + /*! + ### D20 - Generate an offline crash dump D20: Generate an offline crash dump + Generate a crash dump for later retrival. + #### Usage + + D20 [E] + + ### Parameters + - `E` - Perform an emergency crash dump (resets the printer). + ### Notes + - A crash dump can be later recovered with D21, or cleared with D22. + - An emergency crash dump includes register data, but will cause the printer to reset after the dump + is completed. + */ + case 20: { + dcode_20(); + break; + }; + + /*! + ### D21 - Print crash dump to serial D21: Print crash dump to serial + Output the complete crash dump (if present) to the serial. + #### Usage + + D21 + + ### Notes + - The starting address can vary between builds, but it's always at the beginning of the data section. + */ + case 21: { + dcode_21(); + break; + }; + + /*! + ### D22 - Clear crash dump state D22: Clear crash dump state + Clear an existing internal crash dump. + #### Usage + + D22 + */ + case 22: { + dcode_22(); + break; + }; +#endif //XFLASH_DUMP + +#ifdef EMERGENCY_SERIAL_DUMP + /*! + ### D23 - Request emergency dump on serial D23: Request emergency dump on serial + On boards without offline dump support, request online dumps to the serial port on firmware faults. + When online dumps are enabled, the FW will dump memory on the serial before resetting. + #### Usage + + D23 [E] [R] + #### Parameters + - `E` - Perform an emergency crash dump (resets the printer). + - `R` - Disable online dumps. + */ + case 23: { + dcode_23(); + break; + }; +#endif + +#ifdef TEMP_MODEL_DEBUG + /*! + ## D70 - Enable low-level temperature model logging for offline simulation + #### Usage + + D70 [ S ] + + #### Parameters + - `S` - Enable 0-1 (default 0) + */ + case 70: { + if(code_seen('S')) + temp_model_log_enable(code_value_short()); + break; + } +#endif + #ifdef HEATBED_ANALYSIS /*! @@ -9040,28 +8955,7 @@ Sigma_Exit: - `J` - Offset Y (default 34) */ case 80: - { - float dimension_x = 40; - float dimension_y = 40; - int points_x = 40; - int points_y = 40; - float offset_x = 74; - float offset_y = 33; - - if (code_seen('E')) dimension_x = code_value(); - if (code_seen('F')) dimension_y = code_value(); - if (code_seen('G')) {points_x = code_value(); } - if (code_seen('H')) {points_y = code_value(); } - if (code_seen('I')) {offset_x = code_value(); } - if (code_seen('J')) {offset_y = code_value(); } - printf_P(PSTR("DIM X: %f\n"), dimension_x); - printf_P(PSTR("DIM Y: %f\n"), dimension_y); - printf_P(PSTR("POINTS X: %d\n"), points_x); - printf_P(PSTR("POINTS Y: %d\n"), points_y); - printf_P(PSTR("OFFSET X: %f\n"), offset_x); - printf_P(PSTR("OFFSET Y: %f\n"), offset_y); - bed_check(dimension_x,dimension_y,points_x,points_y,offset_x,offset_y); - }break; + dcode_80(); break; /*! ### D81 - Bed analysis D80: Bed analysis @@ -9079,24 +8973,7 @@ Sigma_Exit: - `J` - Offset Y (default 34) */ case 81: - { - float dimension_x = 40; - float dimension_y = 40; - int points_x = 40; - int points_y = 40; - float offset_x = 74; - float offset_y = 33; - - if (code_seen('E')) dimension_x = code_value(); - if (code_seen('F')) dimension_y = code_value(); - if (code_seen("G")) { strchr_pointer+=1; points_x = code_value(); } - if (code_seen("H")) { strchr_pointer+=1; points_y = code_value(); } - if (code_seen("I")) { strchr_pointer+=1; offset_x = code_value(); } - if (code_seen("J")) { strchr_pointer+=1; offset_y = code_value(); } - - bed_analysis(dimension_x,dimension_y,points_x,points_y,offset_x,offset_y); - - } break; + dcode_81(); break; #endif //HEATBED_ANALYSIS #ifdef DEBUG_DCODES @@ -9105,17 +8982,7 @@ Sigma_Exit: ### D106 - Print measured fan speed for different pwm values D106: Print measured fan speed for different pwm values */ case 106: - { - for (int i = 255; i > 0; i = i - 5) { - fanSpeed = i; - //delay_keep_alive(2000); - for (int j = 0; j < 100; j++) { - delay_keep_alive(100); - - } - printf_P(_N("%d: %d\n"), i, fan_speed[1]); - } - }break; + dcode_106(); break; #ifdef TMC2130 /*! @@ -9173,12 +9040,11 @@ Sigma_Exit: For more information see https://www.trinamic.com/fileadmin/assets/Products/ICs_Documents/TMC2130_datasheet.pdf * */ - case 2130: dcode_2130(); break; #endif //TMC2130 -#if (defined (FILAMENT_SENSOR) && defined(PAT9125)) +#if defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125) /*! ### D9125 - PAT9125 filament sensor D9125: PAT9125 filament sensor @@ -9196,9 +9062,12 @@ Sigma_Exit: */ case 9125: dcode_9125(); break; -#endif //FILAMENT_SENSOR +#endif //defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125) #endif //DEBUG_DCODES + + default: + printf_P(MSG_UNKNOWN_CODE, 'D', cmdbuffer + bufindr + CMDHDRSIZE); } } @@ -9217,8 +9086,8 @@ Sigma_Exit: #### End of D-Codes */ - /** @defgroup GCodes G-Code List - */ +/** @defgroup GCodes G-Code List +*/ // --------------------------------------------------- @@ -9233,8 +9102,8 @@ void FlushSerialRequestResend() // Execution of a command from a SD card will not be confirmed. void ClearToSend() { - previous_millis_cmd = _millis(); - if ((CMDBUFFER_CURRENT_TYPE == CMDBUFFER_CURRENT_TYPE_USB) || (CMDBUFFER_CURRENT_TYPE == CMDBUFFER_CURRENT_TYPE_USB_WITH_LINENR)) + previous_millis_cmd.start(); + if (buflen && ((CMDBUFFER_CURRENT_TYPE == CMDBUFFER_CURRENT_TYPE_USB) || (CMDBUFFER_CURRENT_TYPE == CMDBUFFER_CURRENT_TYPE_USB_WITH_LINENR))) SERIAL_PROTOCOLLNRPGM(MSG_OK); } @@ -9277,14 +9146,13 @@ void update_currents() { } #endif //MOTHERBOARD == BOARD_RAMBO_MINI_1_0 || MOTHERBOARD == BOARD_RAMBO_MINI_1_3 -void get_coordinates() -{ +void get_coordinates() { bool seen[4]={false,false,false,false}; for(int8_t i=0; i < NUM_AXIS; i++) { if(code_seen(axis_codes[i])) { - bool relative = axis_relative_modes[i]; - destination[i] = (float)code_value(); + bool relative = axis_relative_modes & (1 << i); + destination[i] = code_value(); if (i == E_AXIS) { float emult = extruder_multiplier[active_extruder]; if (emult != 1.) { @@ -9306,10 +9174,6 @@ void get_coordinates() } if(code_seen('F')) { next_feedrate = code_value(); -#ifdef MAX_SILENT_FEEDRATE - if (tmc2130_mode == TMC2130_MODE_SILENT) - if (next_feedrate > MAX_SILENT_FEEDRATE) next_feedrate = MAX_SILENT_FEEDRATE; -#endif //MAX_SILENT_FEEDRATE if(next_feedrate > 0.0) feedrate = next_feedrate; if (!seen[0] && !seen[1] && !seen[2] && seen[3]) { @@ -9319,31 +9183,6 @@ void get_coordinates() } } -void get_arc_coordinates() -{ -#ifdef SF_ARC_FIX - bool relative_mode_backup = relative_mode; - relative_mode = true; -#endif - get_coordinates(); -#ifdef SF_ARC_FIX - relative_mode=relative_mode_backup; -#endif - - if(code_seen('I')) { - offset[0] = code_value(); - } - else { - offset[0] = 0.0; - } - if(code_seen('J')) { - offset[1] = code_value(); - } - else { - offset[1] = 0.0; - } -} - void clamp_to_software_endstops(float target[3]) { #ifdef DEBUG_DISABLE_SWLIMITS @@ -9365,80 +9204,87 @@ void clamp_to_software_endstops(float target[3]) } } +uint16_t restore_interrupted_gcode() { + // When recovering from a previous print move, restore the originally + // calculated start position on the first USB/SD command. This accounts + // properly for relative moves + if ( + (saved_start_position[0] != SAVED_START_POSITION_UNSET) && ( + (CMDBUFFER_CURRENT_TYPE == CMDBUFFER_CURRENT_TYPE_SDCARD) || + (CMDBUFFER_CURRENT_TYPE == CMDBUFFER_CURRENT_TYPE_USB_WITH_LINENR) + ) + ) { + memcpy(current_position, saved_start_position, sizeof(current_position)); + saved_start_position[0] = SAVED_START_POSITION_UNSET; + return saved_segment_idx; + } + else + return 1; //begin with the first segment +} + #ifdef MESH_BED_LEVELING -void mesh_plan_buffer_line(const float &x, const float &y, const float &z, const float &e, const float &feed_rate, const uint8_t extruder) { +void mesh_plan_buffer_line(const float &x, const float &y, const float &z, const float &e, const float &feed_rate, uint16_t start_segment_idx = 0) { float dx = x - current_position[X_AXIS]; float dy = y - current_position[Y_AXIS]; - int n_segments = 0; + uint16_t n_segments = 0; if (mbl.active) { - float len = abs(dx) + abs(dy); + float len = fabs(dx) + fabs(dy); if (len > 0) // Split to 3cm segments or shorter. - n_segments = int(ceil(len / 30.f)); + n_segments = uint16_t(ceil(len / 30.f)); } - if (n_segments > 1) { - // In a multi-segment move explicitly set the final target in the plan - // as the move will be recalculated in it's entirety - float gcode_target[NUM_AXIS]; - gcode_target[X_AXIS] = x; - gcode_target[Y_AXIS] = y; - gcode_target[Z_AXIS] = z; - gcode_target[E_AXIS] = e; + if (n_segments > 1 && start_segment_idx) { float dz = z - current_position[Z_AXIS]; float de = e - current_position[E_AXIS]; - for (int i = 1; i < n_segments; ++ i) { + for (uint16_t i = start_segment_idx; i < n_segments; ++ i) { float t = float(i) / float(n_segments); plan_buffer_line(current_position[X_AXIS] + t * dx, current_position[Y_AXIS] + t * dy, current_position[Z_AXIS] + t * dz, current_position[E_AXIS] + t * de, - feed_rate, extruder, gcode_target); - if (waiting_inside_plan_buffer_line_print_aborted) + feed_rate, current_position, i); + if (planner_aborted) return; } } // The rest of the path. - plan_buffer_line(x, y, z, e, feed_rate, extruder); + plan_buffer_line(x, y, z, e, feed_rate, current_position); } #endif // MESH_BED_LEVELING -void prepare_move() +void prepare_move(uint16_t start_segment_idx) { clamp_to_software_endstops(destination); - previous_millis_cmd = _millis(); + previous_millis_cmd.start(); // Do not use feedmultiply for E or Z only moves - if( (current_position[X_AXIS] == destination [X_AXIS]) && (current_position[Y_AXIS] == destination [Y_AXIS])) { - plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate/60, active_extruder); + if((current_position[X_AXIS] == destination[X_AXIS]) && (current_position[Y_AXIS] == destination[Y_AXIS])) { + plan_buffer_line_destinationXYZE(feedrate/60); } else { #ifdef MESH_BED_LEVELING - mesh_plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate*feedmultiply*(1./(60.f*100.f)), active_extruder); + mesh_plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate*feedmultiply*(1./(60.f*100.f)), start_segment_idx); #else - plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate*feedmultiply*(1./(60.f*100.f)), active_extruder); + plan_buffer_line_destinationXYZE(feedrate*feedmultiply*(1./(60.f*100.f))); #endif } set_current_to_destination(); } -void prepare_arc_move(char isclockwise) { - float r = hypot(offset[X_AXIS], offset[Y_AXIS]); // Compute arc radius for mc_arc - - // Trace the arc - mc_arc(current_position, destination, offset, X_AXIS, Y_AXIS, Z_AXIS, feedrate*feedmultiply/60/100.0, r, isclockwise, active_extruder); - - // As far as the parser is concerned, the position is now == target. In reality the - // motion control system might still be processing the action and the real tool position - // in any intermediate location. - for(int8_t i=0; i < NUM_AXIS; i++) { - current_position[i] = destination[i]; - } - previous_millis_cmd = _millis(); +void prepare_arc_move(bool isclockwise, uint16_t start_segment_idx) { + float r = hypot(offset[X_AXIS], offset[Y_AXIS]); // Compute arc radius for mc_arc + // Trace the arc + mc_arc(current_position, destination, offset, feedrate * feedmultiply / 60 / 100.0, r, isclockwise, start_segment_idx); + // As far as the parser is concerned, the position is now == target. In reality the + // motion control system might still be processing the action and the real tool position + // in any intermediate location. + set_current_to_destination(); + previous_millis_cmd.start(); } #if defined(CONTROLLERFAN_PIN) && CONTROLLERFAN_PIN > -1 @@ -9488,39 +9334,6 @@ void controllerFan() } #endif -#ifdef TEMP_STAT_LEDS -static bool blue_led = false; -static bool red_led = false; -static uint32_t stat_update = 0; - -void handle_status_leds(void) { - float max_temp = 0.0; - if(_millis() > stat_update) { - stat_update += 500; // Update every 0.5s - for (int8_t cur_extruder = 0; cur_extruder < EXTRUDERS; ++cur_extruder) { - max_temp = max(max_temp, degHotend(cur_extruder)); - max_temp = max(max_temp, degTargetHotend(cur_extruder)); - } - #if defined(TEMP_BED_PIN) && TEMP_BED_PIN > -1 - max_temp = max(max_temp, degTargetBed()); - max_temp = max(max_temp, degBed()); - #endif - if((max_temp > 55.0) && (red_led == false)) { - digitalWrite(STAT_LED_RED, 1); - digitalWrite(STAT_LED_BLUE, 0); - red_led = true; - blue_led = false; - } - if((max_temp < 54.0) && (blue_led == false)) { - digitalWrite(STAT_LED_RED, 0); - digitalWrite(STAT_LED_BLUE, 1); - red_led = false; - blue_led = true; - } - } -} -#endif - #ifdef SAFETYTIMER /** * @brief Turn off heating after safetytimer_inactive_time milliseconds of inactivity @@ -9536,7 +9349,7 @@ static void handleSafetyTimer() #if (EXTRUDERS > 1) #error Implemented only for one extruder. #endif //(EXTRUDERS > 1) - if ((PRINTER_ACTIVE) || (!degTargetBed() && !degTargetHotend(0)) || (!safetytimer_inactive_time)) + if (printer_active() || (!degTargetBed() && !degTargetHotend(0)) || (!safetytimer_inactive_time)) { safetyTimer.stop(); } @@ -9547,74 +9360,19 @@ static void handleSafetyTimer() else if (safetyTimer.expired(farm_mode?FARM_DEFAULT_SAFETYTIMER_TIME_ms:safetytimer_inactive_time)) { setTargetBed(0); - setAllTargetHotends(0); - lcd_show_fullscreen_message_and_wait_P(_i("Heating disabled by safety timer."));////MSG_BED_HEATING_SAFETY_DISABLED + setTargetHotend(0); + lcd_show_fullscreen_message_and_wait_P(_i("Heating disabled by safety timer."));////MSG_BED_HEATING_SAFETY_DISABLED c=20 r=4 } } #endif //SAFETYTIMER void manage_inactivity(bool ignore_stepper_queue/*=false*/) //default argument set in Marlin.h { -bool bInhibitFlag; #ifdef FILAMENT_SENSOR - if (mmu_enabled == false) - { -//-// if (mcode_in_progress != 600) //M600 not in progress -#ifdef PAT9125 - bInhibitFlag=(menu_menu==lcd_menu_extruder_info); // Support::ExtruderInfo menu active -#endif // PAT9125 -#ifdef IR_SENSOR - bInhibitFlag=(menu_menu==lcd_menu_show_sensors_state); // Support::SensorInfo menu active -#endif // IR_SENSOR - if ((mcode_in_progress != 600) && (eFilamentAction != FilamentAction::AutoLoad) && (!bInhibitFlag)) //M600 not in progress, preHeat @ autoLoad menu not active, Support::ExtruderInfo/SensorInfo menu not active - { - if (!moves_planned() && !IS_SD_PRINTING && !is_usb_printing && (lcd_commands_type != LcdCommands::Layer1Cal) && ! eeprom_read_byte((uint8_t*)EEPROM_WIZARD_ACTIVE)) - { - if (fsensor_check_autoload()) - { -#ifdef PAT9125 - fsensor_autoload_check_stop(); -#endif //PAT9125 -//-// if (degHotend0() > EXTRUDE_MINTEMP) -if(0) - { - Sound_MakeCustom(50,1000,false); - loading_flag = true; - enquecommand_front_P((PSTR("M701"))); - } - else - { -/* - lcd_update_enable(false); - show_preheat_nozzle_warning(); - lcd_update_enable(true); -*/ - eFilamentAction=FilamentAction::AutoLoad; - bFilamentFirstRun=false; - if(target_temperature[0]>=EXTRUDE_MINTEMP) - { - bFilamentPreheatState=true; -// mFilamentItem(target_temperature[0],target_temperature_bed); - menu_submenu(mFilamentItemForce); - } - else - { - menu_submenu(lcd_generic_preheat_menu); - lcd_timeoutToStatus.start(); - } - } - } - } - else - { -#ifdef PAT9125 - fsensor_autoload_check_stop(); -#endif //PAT9125 - fsensor_update(); - } - } - } -#endif //FILAMENT_SENSOR + if (fsensor.update()) { + lcd_draw_update = 1; //cause lcd update so that fsensor event polling can be done from the lcd draw routine. + } +#endif #ifdef SAFETYTIMER handleSafetyTimer(); @@ -9629,11 +9387,11 @@ if(0) get_command(); } - if( (_millis() - previous_millis_cmd) > max_inactive_time ) + if(previous_millis_cmd.expired(max_inactive_time)) if(max_inactive_time) kill(_n("Inactivity Shutdown"), 4); if(stepper_inactive_time) { - if( (_millis() - previous_millis_cmd) > stepper_inactive_time ) + if(previous_millis_cmd.expired(stepper_inactive_time)) { if(blocks_queued() == false && ignore_stepper_queue == false) { disable_x(); @@ -9680,7 +9438,7 @@ if(0) controllerFan(); //Check if fan should be turned on to cool stepper drivers down #endif #ifdef EXTRUDER_RUNOUT_PREVENT - if( (_millis() - previous_millis_cmd) > EXTRUDER_RUNOUT_SECONDS*1000 ) + if(previous_millis_cmd.expired(EXTRUDER_RUNOUT_SECONDS*1000)) if(degHotend(active_extruder)>EXTRUDER_RUNOUT_MINTEMP) { bool oldstatus=READ(E0_ENABLE_PIN); @@ -9689,20 +9447,33 @@ if(0) float oldedes=destination[E_AXIS]; plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS]+EXTRUDER_RUNOUT_EXTRUDE*EXTRUDER_RUNOUT_ESTEPS/cs.axis_steps_per_unit[E_AXIS], - EXTRUDER_RUNOUT_SPEED/60.*EXTRUDER_RUNOUT_ESTEPS/cs.axis_steps_per_unit[E_AXIS], active_extruder); + EXTRUDER_RUNOUT_SPEED/60.*EXTRUDER_RUNOUT_ESTEPS/cs.axis_steps_per_unit[E_AXIS]); current_position[E_AXIS]=oldepos; destination[E_AXIS]=oldedes; plan_set_e_position(oldepos); - previous_millis_cmd=_millis(); + previous_millis_cmd.start(); st_synchronize(); WRITE(E0_ENABLE_PIN,oldstatus); } #endif - #ifdef TEMP_STAT_LEDS - handle_status_leds(); - #endif check_axes_activity(); - mmu_loop(); + MMU2::mmu2.mmu_loop(); + + // handle longpress + if(lcd_longpress_trigger) + { + // long press is not possible in modal mode, wait until ready + if (lcd_longpress_func && lcd_update_enabled) + { + lcd_longpress_func(); + lcd_longpress_trigger = 0; + } + } + +#if defined(AUTO_REPORT) + host_autoreport(); +#endif //AUTO_REPORT + host_keepalive(); } void kill(const char *full_screen_message, unsigned char id) @@ -9715,7 +9486,7 @@ void kill(const char *full_screen_message, unsigned char id) disable_x(); // SERIAL_ECHOLNPGM("kill - disable Y"); disable_y(); - disable_z(); + poweroff_z(); disable_e0(); disable_e1(); disable_e2(); @@ -9750,31 +9521,113 @@ void kill(const char *full_screen_message, unsigned char id) } // Wait for reset } -// Stop: Emergency stop used by overtemp functions which allows recovery -// -// In addition to stopping the print, this prevents subsequent G[0-3] commands to be -// processed via USB (using "Stopped") until the print is resumed via M999 or -// manually started from scratch with the LCD. -// -// Note that the current instruction is completely discarded, so resuming from Stop() -// will introduce either over/under extrusion on the current segment, and will not -// survive a power panic. Switching Stop() to use the pause machinery instead (with -// the addition of disabling the headers) could allow true recovery in the future. -void Stop() +void UnconditionalStop() { - disable_heater(); - if(Stopped == false) { - Stopped = true; - lcd_print_stop(); - Stopped_gcode_LastN = gcode_LastN; // Save last g_code for restart - SERIAL_ERROR_START; - SERIAL_ERRORLNRPGM(MSG_ERR_STOPPED); - LCD_MESSAGERPGM(_T(MSG_STOPPED)); - } + CRITICAL_SECTION_START; + + // Disable all heaters and unroll the temperature wait loop stack + disable_heater(); + cancel_heatup = true; + heating_status = HeatingStatus::NO_HEATING; + + // Clear any saved printing state + cancel_saved_printing(); + + // Abort the planner + planner_abort_hard(); + + // Reset the queue + cmdqueue_reset(); + cmdqueue_serial_disabled = false; + + // Reset the sd status + card.sdprinting = false; + card.closefile(); + + st_reset_timer(); + CRITICAL_SECTION_END; +} + +// Emergency stop used by overtemp functions which allows recovery +// WARNING: This function is called *continuously* during a thermal failure. +// +// This either pauses (for thermal model errors) or stops *without recovery* depending on +// "allow_recovery". If recovery is allowed, this forces a printer-initiated instantanenous pause +// (just like an LCD pause) that bypasses the host pausing functionality. In this state the printer +// is kept in busy state and *must* be recovered from the LCD. +void ThermalStop(bool allow_recovery) +{ + if(Stopped == false) { + Stopped = true; + + // Either pause or stop the print + if(allow_recovery && (IS_SD_PRINTING || usb_timer.running())) { + if (!isPrintPaused) { + lcd_setalertstatuspgm(_T(MSG_PAUSED_THERMAL_ERROR), LCD_STATUS_CRITICAL); + + // we cannot make a distinction for the host here, the pause must be instantaneous + // so we call the lcd_pause_print to save the print state internally. Thermal errors + // disable heaters and save the original temperatures to saved_*, which will get + // overwritten by stop_and_save_print_to_ram. For this corner-case, re-instate the + // original values after the pause handler is called. + float bed_temp = saved_bed_temperature; + float ext_temp = saved_extruder_temperature; + int fan_speed = saved_fan_speed; + lcd_pause_print(); + saved_bed_temperature = bed_temp; + saved_extruder_temperature = ext_temp; + saved_fan_speed = fan_speed; + } + } else { + // We got a hard thermal error and/or there is no print going on. Just stop. + print_stop(); + } + + // Report the error on the serial + serialprintPGM(allow_recovery ? echomagic : errormagic); + SERIAL_ERRORLNRPGM(MSG_ERR_STOPPED); + + // Eventually report the stopped status on the lcd (though this is usually overridden by a + // higher-priority alert status message) + LCD_MESSAGERPGM(_T(MSG_STOPPED)); + + // Make a warning sound! We cannot use Sound_MakeCustom as this would stop further moves. + // Turn on the speaker here (if not already), and turn it off when back in the main loop. + WRITE(BEEPER, HIGH); + + // Always return to the status screen to ensure the NEW error is immediately shown. + lcd_return_to_status(); + + if(!allow_recovery) { + // prevent menu access for all fatal errors + menu_set_block(MENU_BLOCK_THERMAL_ERROR); + } + } } bool IsStopped() { return Stopped; }; +void finishAndDisableSteppers() +{ + st_synchronize(); + disable_x(); + disable_y(); + disable_z(); + disable_e0(); + disable_e1(); + disable_e2(); + +#ifndef LA_NOCOMPAT + // Steppers are disabled both when a print is stopped and also via M84 (which is additionally + // checked-for to indicate a complete file), so abuse this function to reset the LA detection + // state for the next print. + la10c_reset(); +#endif + + //in the end of print set estimated time to end of print and extruders used during print to default values for next print + print_time_remaining_init(); +} + #ifdef FAST_PWM_FAN void setPwmFrequency(uint8_t pin, int val) { @@ -9845,62 +9698,21 @@ void setPwmFrequency(uint8_t pin, int val) } #endif //FAST_PWM_FAN -//! @brief Get and validate extruder number -//! -//! If it is not specified, active_extruder is returned in parameter extruder. -//! @param [in] code M code number -//! @param [out] extruder -//! @return error -//! @retval true Invalid extruder specified in T code -//! @retval false Valid extruder specified in T code, or not specifiead +void save_statistics(unsigned long _total_filament_used, unsigned long _total_print_time) { //_total_filament_used unit: mm/100; print time in s + uint32_t _previous_filament = eeprom_init_default_dword((uint32_t *)EEPROM_FILAMENTUSED, 0); //_previous_filament unit: cm + uint32_t _previous_time = eeprom_init_default_dword((uint32_t *)EEPROM_TOTALTIME, 0); //_previous_time unit: min -bool setTargetedHotend(int code, uint8_t &extruder) -{ - extruder = active_extruder; - if(code_seen('T')) { - extruder = code_value(); - if(extruder >= EXTRUDERS) { - SERIAL_ECHO_START; - switch(code){ - case 104: - SERIAL_ECHORPGM(_n("M104 Invalid extruder "));////MSG_M104_INVALID_EXTRUDER - break; - case 105: - SERIAL_ECHO(_n("M105 Invalid extruder "));////MSG_M105_INVALID_EXTRUDER - break; - case 109: - SERIAL_ECHO(_n("M109 Invalid extruder "));////MSG_M109_INVALID_EXTRUDER - break; - case 218: - SERIAL_ECHO(_n("M218 Invalid extruder "));////MSG_M218_INVALID_EXTRUDER - break; - case 221: - SERIAL_ECHO(_n("M221 Invalid extruder "));////MSG_M221_INVALID_EXTRUDER - break; - } - SERIAL_PROTOCOLLN((int)extruder); - return true; + eeprom_update_dword((uint32_t *)EEPROM_TOTALTIME, _previous_time + (_total_print_time / 60)); // EEPROM_TOTALTIME unit: min + eeprom_update_dword((uint32_t *)EEPROM_FILAMENTUSED, _previous_filament + (_total_filament_used / 1000)); + + total_filament_used = 0; + + if (MMU2::mmu2.Enabled()) { + eeprom_add_dword((uint32_t *)EEPROM_MMU_MATERIAL_CHANGES, MMU2::mmu2.ToolChangeCounter()); + // @@TODO why were EEPROM_MMU_FAIL_TOT and EEPROM_MMU_LOAD_FAIL_TOT behaving differently - i.e. updated with every change? + MMU2::mmu2.ClearToolChangeCounter(); + MMU2::mmu2.ClearTMCFailures(); // not stored into EEPROM } - } - return false; -} - -void save_statistics(unsigned long _total_filament_used, unsigned long _total_print_time) //_total_filament_used unit: mm/100; print time in s -{ - if (eeprom_read_byte((uint8_t *)EEPROM_TOTALTIME) == 255 && eeprom_read_byte((uint8_t *)EEPROM_TOTALTIME + 1) == 255 && eeprom_read_byte((uint8_t *)EEPROM_TOTALTIME + 2) == 255 && eeprom_read_byte((uint8_t *)EEPROM_TOTALTIME + 3) == 255) - { - eeprom_update_dword((uint32_t *)EEPROM_TOTALTIME, 0); - eeprom_update_dword((uint32_t *)EEPROM_FILAMENTUSED, 0); - } - - unsigned long _previous_filament = eeprom_read_dword((uint32_t *)EEPROM_FILAMENTUSED); //_previous_filament unit: cm - unsigned long _previous_time = eeprom_read_dword((uint32_t *)EEPROM_TOTALTIME); //_previous_time unit: min - - eeprom_update_dword((uint32_t *)EEPROM_TOTALTIME, _previous_time + (_total_print_time/60)); //EEPROM_TOTALTIME unit: min - eeprom_update_dword((uint32_t *)EEPROM_FILAMENTUSED, _previous_filament + (_total_filament_used / 1000)); - - total_filament_used = 0; - } float calculate_extruder_multiplier(float diameter) { @@ -9975,23 +9787,21 @@ static void wait_for_heater(long codenum, uint8_t extruder) { } else { - SERIAL_PROTOCOLLN("?"); + SERIAL_PROTOCOLLN('?'); } } #else - SERIAL_PROTOCOLLN(""); + SERIAL_PROTOCOLLN(); #endif codenum = _millis(); } - manage_heater(); - manage_inactivity(true); //do not disable steppers - lcd_update(0); + delay_keep_alive(0); //do not disable steppers #ifdef TEMP_RESIDENCY_TIME /* start/restart the TEMP_RESIDENCY_TIME timer whenever we reach target temp for the first time or when current temp falls outside the hysteresis after target temp was reached */ if ((residencyStart == -1 && target_direction && (degHotend(extruder) >= (degTargetHotend(extruder) - TEMP_WINDOW))) || (residencyStart == -1 && !target_direction && (degHotend(extruder) <= (degTargetHotend(extruder) + TEMP_WINDOW))) || - (residencyStart > -1 && labs(degHotend(extruder) - degTargetHotend(extruder)) > TEMP_HYSTERESIS)) + (residencyStart > -1 && fabs(degHotend(extruder) - degTargetHotend(extruder)) > TEMP_HYSTERESIS)) { residencyStart = _millis(); } @@ -10106,16 +9916,16 @@ void bed_check(float x_dimension, float y_dimension, int x_points_num, int y_poi card.openFile(filename_wldsd, false); /*destination[Z_AXIS] = mesh_home_z_search; - //plan_buffer_line_curposXYZE(Z_LIFT_FEEDRATE, active_extruder); + //plan_buffer_line_curposXYZE(Z_LIFT_FEEDRATE); - plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], Z_LIFT_FEEDRATE, active_extruder); + plan_buffer_line_destinationXYZE(Z_LIFT_FEEDRATE); for(int8_t i=0; i < NUM_AXIS; i++) { current_position[i] = destination[i]; } st_synchronize(); */ destination[Z_AXIS] = measure_z_height; - plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], Z_LIFT_FEEDRATE, active_extruder); + plan_buffer_line_destinationXYZE(Z_LIFT_FEEDRATE); for(int8_t i=0; i < NUM_AXIS; i++) { current_position[i] = destination[i]; } @@ -10140,9 +9950,9 @@ void bed_check(float x_dimension, float y_dimension, int x_points_num, int y_poi if (iy & 1) ix = (x_points_num - 1) - ix; // Zig zag float z0 = 0.f; /*destination[Z_AXIS] = mesh_home_z_search; - //plan_buffer_line_curposXYZE(Z_LIFT_FEEDRATE, active_extruder); + //plan_buffer_line_curposXYZE(Z_LIFT_FEEDRATE); - plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], Z_LIFT_FEEDRATE, active_extruder); + plan_buffer_line_destinationXYZE(Z_LIFT_FEEDRATE); for(int8_t i=0; i < NUM_AXIS; i++) { current_position[i] = destination[i]; } @@ -10155,8 +9965,8 @@ void bed_check(float x_dimension, float y_dimension, int x_points_num, int y_poi destination[X_AXIS] = ix * (x_dimension / (x_points_num - 1)) + shift_x; destination[Y_AXIS] = iy * (y_dimension / (y_points_num - 1)) + shift_y; - mesh_plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], XY_AXIS_FEEDRATE/6, active_extruder); - set_current_to_destination(); + mesh_plan_buffer_line_destinationXYZE(XY_AXIS_FEEDRATE/6); + set_current_to_destination(); st_synchronize(); // printf_P(PSTR("X = %f; Y= %f \n"), current_position[X_AXIS], current_position[Y_AXIS]); @@ -10286,7 +10096,7 @@ void bed_analysis(float x_dimension, float y_dimension, int x_points_num, int y_ // There shall be always enough space reserved for these commands. repeatcommand_front(); // repeat G80 with all its parameters - enquecommand_front_P((PSTR("G28 W0"))); + enquecommand_front_P(G28W0); enquecommand_front_P((PSTR("G1 Z5"))); return; } @@ -10446,15 +10256,15 @@ static void temp_compensation_start() { custom_message_type = CustomMsg::TempCompPreheat; custom_message_state = PINDA_HEAT_T + 1; lcd_update(2); - if (degHotend(active_extruder) > EXTRUDE_MINTEMP) { + if ((int)degHotend(active_extruder) > extrude_min_temp) { current_position[E_AXIS] -= default_retraction; } - plan_buffer_line_curposXYZE(400, active_extruder); + plan_buffer_line_curposXYZE(400); current_position[X_AXIS] = PINDA_PREHEAT_X; current_position[Y_AXIS] = PINDA_PREHEAT_Y; current_position[Z_AXIS] = PINDA_PREHEAT_Z; - plan_buffer_line_curposXYZE(3000 / 60, active_extruder); + plan_buffer_line_curposXYZE(3000 / 60); st_synchronize(); while (fabs(degBed() - target_temperature_bed) > 1) delay_keep_alive(1000); @@ -10473,17 +10283,17 @@ static void temp_compensation_apply() { int z_shift = 0; float z_shift_mm; - if (calibration_status() == CALIBRATION_STATUS_CALIBRATED) { + if (calibration_status_pinda()) { if (target_temperature_bed % 10 == 0 && target_temperature_bed >= 60 && target_temperature_bed <= 100) { i_add = (target_temperature_bed - 60) / 10; - EEPROM_read_B(EEPROM_PROBE_TEMP_SHIFT + i_add * 2, &z_shift); + z_shift = eeprom_read_word((uint16_t*)EEPROM_PROBE_TEMP_SHIFT + i_add); z_shift_mm = z_shift / cs.axis_steps_per_unit[Z_AXIS]; }else { //interpolation z_shift_mm = temp_comp_interpolation(target_temperature_bed) / cs.axis_steps_per_unit[Z_AXIS]; } printf_P(_N("\nZ shift applied:%.3f\n"), z_shift_mm); - plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS] - z_shift_mm, current_position[E_AXIS], homing_feedrate[Z_AXIS] / 40, active_extruder); + plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS] - z_shift_mm, current_position[E_AXIS], homing_feedrate[Z_AXIS] / 40); st_synchronize(); plan_set_z_position(current_position[Z_AXIS]); } @@ -10506,10 +10316,17 @@ float temp_comp_interpolation(float inp_temperature) { shift[0] = 0; for (i = 0; i < n; i++) { - if (i>0) EEPROM_read_B(EEPROM_PROBE_TEMP_SHIFT + (i-1) * 2, &shift[i]); //read shift in steps from EEPROM + if (i > 0) { + //read shift in steps from EEPROM + shift[i] = eeprom_read_word((uint16_t*)EEPROM_PROBE_TEMP_SHIFT + (i - 1)); + } temp_C[i] = 50 + i * 10; //temperature in C #ifdef PINDA_THERMISTOR - temp_C[i] = 35 + i * 5; //temperature in C + constexpr int start_compensating_temp = 35; + temp_C[i] = start_compensating_temp + i * 5; //temperature in degrees C +#ifdef SUPERPINDA_SUPPORT + static_assert(start_compensating_temp >= PINDA_MINTEMP, "Temperature compensation start point is lower than PINDA_MINTEMP."); +#endif //SUPERPINDA_SUPPORT #else temp_C[i] = 50 + i * 10; //temperature in C #endif @@ -10562,7 +10379,7 @@ float temp_comp_interpolation(float inp_temperature) { #ifdef PINDA_THERMISTOR float temp_compensation_pinda_thermistor_offset(float temperature_pinda) { - if (!temp_cal_active) return 0; + if (!eeprom_read_byte((unsigned char *)EEPROM_TEMP_CAL_ACTIVE)) return 0; if (!calibration_status_pinda()) return 0; return temp_comp_interpolation(temperature_pinda) / cs.axis_steps_per_unit[Z_AXIS]; } @@ -10574,34 +10391,49 @@ void long_pause() //long pause print start_pause_print = _millis(); // Stop heaters - setAllTargetHotends(0); + heating_status = HeatingStatus::NO_HEATING; + setTargetHotend(0); - //lift z - current_position[Z_AXIS] += Z_PAUSE_LIFT; - if (current_position[Z_AXIS] > Z_MAX_POS) current_position[Z_AXIS] = Z_MAX_POS; - plan_buffer_line_curposXYZE(15, active_extruder); + // Lift z + raise_z(Z_PAUSE_LIFT); - //Move XY to side - current_position[X_AXIS] = X_PAUSE_POS; - current_position[Y_AXIS] = Y_PAUSE_POS; - plan_buffer_line_curposXYZE(50, active_extruder); + // Move XY to side + if (axis_known_position[X_AXIS] && axis_known_position[Y_AXIS]) { + current_position[X_AXIS] = X_PAUSE_POS; + current_position[Y_AXIS] = Y_PAUSE_POS; + plan_buffer_line_curposXYZE(50); + } - // Turn off the print fan - fanSpeed = 0; + // did we come here from a thermal error? + if(get_temp_error()) { + // time to stop the error beep + WRITE(BEEPER, LOW); + } else { + // Turn off the print fan + fanSpeed = 0; + } } void serialecho_temperatures() { float tt = degHotend(active_extruder); SERIAL_PROTOCOLPGM("T:"); SERIAL_PROTOCOL(tt); - SERIAL_PROTOCOLPGM(" E:"); - SERIAL_PROTOCOL((int)active_extruder); - SERIAL_PROTOCOLPGM(" B:"); + SERIAL_PROTOCOLPGM(" E:0 B:"); SERIAL_PROTOCOL_F(degBed(), 1); - SERIAL_PROTOCOLLN(""); + SERIAL_PROTOCOLLN(); } #ifdef UVLO_SUPPORT +void uvlo_drain_reset() +{ + // burn all that residual power + wdt_enable(WDTO_1S); + WRITE(BEEPER,HIGH); + lcd_clear(); + lcd_puts_at_P(0, 1, MSG_POWERPANIC_DETECTED); + while(1); +} + void uvlo_() { @@ -10623,16 +10455,11 @@ void uvlo_() tmc2130_set_current_r(E_AXIS, 20); #endif //TMC2130 - - // Indicate that the interrupt has been triggered. - // SERIAL_ECHOLNPGM("UVLO"); - - // Read out the current Z motor microstep counter. This will be later used - // for reaching the zero full step before powering off. - uint16_t z_microsteps = 0; -#ifdef TMC2130 - z_microsteps = tmc2130_rd_MSCNT(Z_TMC2130_CS); -#endif //TMC2130 + // Stop all heaters + uint8_t saved_target_temperature_bed = target_temperature_bed; + uint16_t saved_target_temperature_ext = target_temperature[active_extruder]; + setTargetHotend(0); + setTargetBed(0); // Calculate the file position, from which to resume this print. long sd_position = sdpos_atomic; //atomic sd position of last command added in queue @@ -10645,87 +10472,102 @@ void uvlo_() } // save the global state at planning time + bool pos_invalid = XY_NO_RESTORE_FLAG; uint16_t feedrate_bckp; - if (blocks_queued()) + if (current_block && !pos_invalid) { - memcpy(saved_target, current_block->gcode_target, sizeof(saved_target)); + memcpy(saved_start_position, current_block->gcode_start_position, sizeof(saved_start_position)); feedrate_bckp = current_block->gcode_feedrate; + saved_segment_idx = current_block->segment_idx; } else { - saved_target[0] = SAVED_TARGET_UNSET; + saved_start_position[0] = SAVED_START_POSITION_UNSET; feedrate_bckp = feedrate; + saved_segment_idx = 0; } + // From this point on and up to the print recovery, Z should not move during X/Y travels and + // should be controlled precisely. Reset the MBL status before planner_abort_hard in order to + // get the physical Z for further manipulation. + bool mbl_was_active = mbl.active; + mbl.active = false; + // After this call, the planner queue is emptied and the current_position is set to a current logical coordinate. // The logical coordinate will likely differ from the machine coordinate if the skew calibration and mesh bed leveling // are in action. planner_abort_hard(); - // Store the current extruder position. - eeprom_update_float((float*)(EEPROM_UVLO_CURRENT_POSITION_E), st_get_position_mm(E_AXIS)); - eeprom_update_byte((uint8_t*)EEPROM_UVLO_E_ABS, axis_relative_modes[3]?0:1); - // Clean the input command queue. + // Store the print logical Z position, which we need to recover (a slight error here would be + // recovered on the next Gcode instruction, while a physical location error would not) + float logical_z = current_position[Z_AXIS]; + if(mbl_was_active) logical_z -= mbl.get_z(st_get_position_mm(X_AXIS), st_get_position_mm(Y_AXIS)); + eeprom_update_float((float*)EEPROM_UVLO_CURRENT_POSITION_Z, logical_z); + + // Store the print E position before we lose track + eeprom_update_float((float*)(EEPROM_UVLO_CURRENT_POSITION_E), current_position[E_AXIS]); + eeprom_update_byte((uint8_t*)EEPROM_UVLO_E_ABS, (axis_relative_modes & E_AXIS_MASK)?0:1); + + // Clean the input command queue, inhibit serial processing using saved_printing cmdqueue_reset(); card.sdprinting = false; -// card.closefile(); - // Enable stepper driver interrupt to move Z axis. - // This should be fine as the planner and command queues are empty and the SD card printing is disabled. - //FIXME one may want to disable serial lines at this point of time to avoid interfering with the command queue, - // though it should not happen that the command queue is touched as the plan_buffer_line always succeed without blocking. - sei(); - plan_buffer_line( - current_position[X_AXIS], - current_position[Y_AXIS], - current_position[Z_AXIS], - current_position[E_AXIS] - default_retraction, - 95, active_extruder); - - st_synchronize(); - disable_e0(); - - plan_buffer_line( - current_position[X_AXIS], - current_position[Y_AXIS], - current_position[Z_AXIS] + UVLO_Z_AXIS_SHIFT + float((1024 - z_microsteps + 7) >> 4) / cs.axis_steps_per_unit[Z_AXIS], - current_position[E_AXIS] - default_retraction, - 40, active_extruder); + saved_printing = true; + + // Enable stepper driver interrupt to move Z axis. This should be fine as the planner and + // command queues are empty, SD card printing is disabled, usb is inhibited. + planner_aborted = false; + sei(); + + // Retract + current_position[E_AXIS] -= default_retraction; + plan_buffer_line_curposXYZE(95); st_synchronize(); disable_e0(); - plan_buffer_line( - current_position[X_AXIS], - current_position[Y_AXIS], - current_position[Z_AXIS] + UVLO_Z_AXIS_SHIFT + float((1024 - z_microsteps + 7) >> 4) / cs.axis_steps_per_unit[Z_AXIS], - current_position[E_AXIS] - default_retraction, - 40, active_extruder); + // Read out the current Z motor microstep counter to move the axis up towards + // a full step before powering off. NOTE: we need to ensure to schedule more + // than "dropsegments" steps in order to move (this is always the case here + // due to UVLO_Z_AXIS_SHIFT being used) + uint16_t z_res = tmc2130_get_res(Z_AXIS); + uint16_t z_microsteps = tmc2130_rd_MSCNT(Z_AXIS); + current_position[Z_AXIS] += float(1024 - z_microsteps) + / (z_res * cs.axis_steps_per_unit[Z_AXIS]) + + UVLO_Z_AXIS_SHIFT; + plan_buffer_line_curposXYZE(homing_feedrate[Z_AXIS]/60); st_synchronize(); + poweroff_z(); - disable_e0(); - // Move Z up to the next 0th full step. // Write the file position. eeprom_update_dword((uint32_t*)(EEPROM_FILE_POSITION), sd_position); + // Store the mesh bed leveling offsets. This is 2*7*7=98 bytes, which takes 98*3.4us=333us in worst case. - for (int8_t mesh_point = 0; mesh_point < MESH_NUM_X_POINTS * MESH_NUM_Y_POINTS; ++ mesh_point) { + for (uint8_t mesh_point = 0; mesh_point < MESH_NUM_X_POINTS * MESH_NUM_Y_POINTS; ++ mesh_point) { uint8_t ix = mesh_point % MESH_NUM_X_POINTS; // from 0 to MESH_NUM_X_POINTS - 1 uint8_t iy = mesh_point / MESH_NUM_X_POINTS; // Scale the z value to 1u resolution. - int16_t v = mbl.active ? int16_t(floor(mbl.z_values[iy][ix] * 1000.f + 0.5f)) : 0; + int16_t v = mbl_was_active ? int16_t(floor(mbl.z_values[iy][ix] * 1000.f + 0.5f)) : 0; eeprom_update_word((uint16_t*)(EEPROM_UVLO_MESH_BED_LEVELING_FULL +2*mesh_point), *reinterpret_cast(&v)); } - // Read out the current Z motor microstep counter. This will be later used - // for reaching the zero full step before powering off. - eeprom_update_word((uint16_t*)(EEPROM_UVLO_Z_MICROSTEPS), z_microsteps); - // Store the current position. - eeprom_update_float((float*)(EEPROM_UVLO_CURRENT_POSITION + 0), current_position[X_AXIS]); - eeprom_update_float((float*)(EEPROM_UVLO_CURRENT_POSITION + 4), current_position[Y_AXIS]); - eeprom_update_float((float*)EEPROM_UVLO_CURRENT_POSITION_Z , current_position[Z_AXIS]); + // Write the _final_ Z position and motor microstep counter (unused). + eeprom_update_float((float*)EEPROM_UVLO_TINY_CURRENT_POSITION_Z, current_position[Z_AXIS]); + z_microsteps = tmc2130_rd_MSCNT(Z_AXIS); + eeprom_update_word((uint16_t*)(EEPROM_UVLO_Z_MICROSTEPS), z_microsteps); + + // Store the current position. + if (pos_invalid) + eeprom_update_float((float*)(EEPROM_UVLO_CURRENT_POSITION + 0), X_COORD_INVALID); + else + { + eeprom_update_float((float*)(EEPROM_UVLO_CURRENT_POSITION + 0), current_position[X_AXIS]); + eeprom_update_float((float*)(EEPROM_UVLO_CURRENT_POSITION + 4), current_position[Y_AXIS]); + } + // Store the current feed rate, temperatures, fan speed and extruder multipliers (flow rates) eeprom_update_word((uint16_t*)EEPROM_UVLO_FEEDRATE, feedrate_bckp); - EEPROM_save_B(EEPROM_UVLO_FEEDMULTIPLY, &feedmultiply); - eeprom_update_byte((uint8_t*)EEPROM_UVLO_TARGET_HOTEND, target_temperature[active_extruder]); - eeprom_update_byte((uint8_t*)EEPROM_UVLO_TARGET_BED, target_temperature_bed); + eeprom_update_word((uint16_t*)EEPROM_UVLO_FEEDMULTIPLY, feedmultiply); + eeprom_update_word((uint16_t*)EEPROM_UVLO_TARGET_HOTEND, saved_target_temperature_ext); + eeprom_update_byte((uint8_t*)EEPROM_UVLO_TARGET_BED, saved_target_temperature_bed); eeprom_update_byte((uint8_t*)EEPROM_UVLO_FAN_SPEED, fanSpeed); eeprom_update_float((float*)(EEPROM_EXTRUDER_MULTIPLIER_0), extruder_multiplier[0]); #if EXTRUDERS > 1 @@ -10736,11 +10578,17 @@ void uvlo_() #endif eeprom_update_word((uint16_t*)(EEPROM_EXTRUDEMULTIPLY), (uint16_t)extrudemultiply); + eeprom_update_float((float*)(EEPROM_UVLO_ACCELL), cs.acceleration); + eeprom_update_float((float*)(EEPROM_UVLO_RETRACT_ACCELL), cs.retract_acceleration); + eeprom_update_float((float*)(EEPROM_UVLO_TRAVEL_ACCELL), cs.travel_acceleration); + // Store the saved target - eeprom_update_float((float*)(EEPROM_UVLO_SAVED_TARGET+0*4), saved_target[X_AXIS]); - eeprom_update_float((float*)(EEPROM_UVLO_SAVED_TARGET+1*4), saved_target[Y_AXIS]); - eeprom_update_float((float*)(EEPROM_UVLO_SAVED_TARGET+2*4), saved_target[Z_AXIS]); - eeprom_update_float((float*)(EEPROM_UVLO_SAVED_TARGET+3*4), saved_target[E_AXIS]); + eeprom_update_float((float*)(EEPROM_UVLO_SAVED_START_POSITION+0*4), saved_start_position[X_AXIS]); + eeprom_update_float((float*)(EEPROM_UVLO_SAVED_START_POSITION+1*4), saved_start_position[Y_AXIS]); + eeprom_update_float((float*)(EEPROM_UVLO_SAVED_START_POSITION+2*4), saved_start_position[Z_AXIS]); + eeprom_update_float((float*)(EEPROM_UVLO_SAVED_START_POSITION+3*4), saved_start_position[E_AXIS]); + + eeprom_update_word((uint16_t*)EEPROM_UVLO_SAVED_SEGMENT_IDX, saved_segment_idx); #ifdef LIN_ADVANCE eeprom_update_float((float*)(EEPROM_UVLO_LA_K), extruder_advance_K); @@ -10749,71 +10597,89 @@ void uvlo_() // Finaly store the "power outage" flag. if(sd_print) eeprom_update_byte((uint8_t*)EEPROM_UVLO, 1); - st_synchronize(); - printf_P(_N("stps%d\n"), tmc2130_rd_MSCNT(Z_AXIS)); - // Increment power failure counter - eeprom_update_byte((uint8_t*)EEPROM_POWER_COUNT, eeprom_read_byte((uint8_t*)EEPROM_POWER_COUNT) + 1); - eeprom_update_word((uint16_t*)EEPROM_POWER_COUNT_TOT, eeprom_read_word((uint16_t*)EEPROM_POWER_COUNT_TOT) + 1); - printf_P(_N("UVLO - end %d\n"), _millis() - time_start); + eeprom_increment_byte((uint8_t*)EEPROM_POWER_COUNT); + eeprom_increment_word((uint16_t*)EEPROM_POWER_COUNT_TOT); -#if 0 - // Move the print head to the side of the print until all the power stored in the power supply capacitors is depleted. + printf_P(_N("UVLO - end %d\n"), _millis() - time_start); + WRITE(BEEPER,HIGH); + + // All is set: with all the juice left, try to move extruder away to detach the nozzle completely from the print + poweron_z(); current_position[X_AXIS] = (current_position[X_AXIS] < 0.5f * (X_MIN_POS + X_MAX_POS)) ? X_MIN_POS : X_MAX_POS; - plan_buffer_line_curposXYZE(500, active_extruder); + plan_buffer_line_curposXYZE(500); st_synchronize(); -#endif -wdt_enable(WDTO_500MS); -WRITE(BEEPER,HIGH); -while(1) - ; + + wdt_enable(WDTO_1S); + while(1); } void uvlo_tiny() { -uint16_t z_microsteps=0; + unsigned long time_start = _millis(); + + // Conserve power as soon as possible. + disable_x(); + disable_y(); + disable_e0(); -// Conserve power as soon as possible. -disable_x(); -disable_y(); -disable_e0(); - #ifdef TMC2130 -tmc2130_set_current_h(Z_AXIS, 20); -tmc2130_set_current_r(Z_AXIS, 20); + tmc2130_set_current_h(Z_AXIS, 20); + tmc2130_set_current_r(Z_AXIS, 20); #endif //TMC2130 -// Read out the current Z motor microstep counter -#ifdef TMC2130 -z_microsteps=tmc2130_rd_MSCNT(Z_TMC2130_CS); -#endif //TMC2130 -planner_abort_hard(); + // Stop all heaters + setTargetHotend(0); + setTargetBed(0); -//save current position only in case, where the printer is moving on Z axis, which is only when EEPROM_UVLO is 1 -//EEPROM_UVLO is 1 after normal uvlo or after recover_print(), when the extruder is moving on Z axis after rehome -if(eeprom_read_byte((uint8_t*)EEPROM_UVLO)!=2){ - eeprom_update_float((float*)(EEPROM_UVLO_TINY_CURRENT_POSITION_Z), current_position[Z_AXIS]); - eeprom_update_word((uint16_t*)(EEPROM_UVLO_TINY_Z_MICROSTEPS),z_microsteps); -} + // When power is interrupted on the _first_ recovery an attempt can be made to raise the + // extruder, causing the Z position to change. Similarly, when recovering, the Z position is + // lowered. In such cases we cannot just save Z, we need to re-align the steppers to a fullstep. + // Disable MBL (if not already) to work with physical coordinates. + mbl.active = false; + planner_abort_hard(); -//after multiple power panics current Z axis is unknow -//in this case we set EEPROM_UVLO_TINY_CURRENT_POSITION_Z to last know position which is EEPROM_UVLO_CURRENT_POSITION_Z -if(eeprom_read_float((float*)EEPROM_UVLO_TINY_CURRENT_POSITION_Z) < 0.001f){ - eeprom_update_float((float*)(EEPROM_UVLO_TINY_CURRENT_POSITION_Z), eeprom_read_float((float*)EEPROM_UVLO_CURRENT_POSITION_Z)); - eeprom_update_word((uint16_t*)(EEPROM_UVLO_TINY_Z_MICROSTEPS), eeprom_read_word((uint16_t*)EEPROM_UVLO_Z_MICROSTEPS)); -} + // Allow for small roundoffs to be ignored + if(fabs(current_position[Z_AXIS] - eeprom_read_float((float*)(EEPROM_UVLO_TINY_CURRENT_POSITION_Z))) >= 1.f/cs.axis_steps_per_unit[Z_AXIS]) + { + // Clean the input command queue, inhibit serial processing using saved_printing + cmdqueue_reset(); + card.sdprinting = false; + saved_printing = true; -// Finaly store the "power outage" flag. -eeprom_update_byte((uint8_t*)EEPROM_UVLO,2); + // Enable stepper driver interrupt to move Z axis. This should be fine as the planner and + // command queues are empty, SD card printing is disabled, usb is inhibited. + planner_aborted = false; + sei(); -// Increment power failure counter -eeprom_update_byte((uint8_t*)EEPROM_POWER_COUNT, eeprom_read_byte((uint8_t*)EEPROM_POWER_COUNT) + 1); -eeprom_update_word((uint16_t*)EEPROM_POWER_COUNT_TOT, eeprom_read_word((uint16_t*)EEPROM_POWER_COUNT_TOT) + 1); -wdt_enable(WDTO_500MS); -WRITE(BEEPER,HIGH); -while(1) - ; + // The axis was moved: adjust Z as done on a regular UVLO. + uint16_t z_res = tmc2130_get_res(Z_AXIS); + uint16_t z_microsteps = tmc2130_rd_MSCNT(Z_AXIS); + current_position[Z_AXIS] += float(1024 - z_microsteps) + / (z_res * cs.axis_steps_per_unit[Z_AXIS]) + + UVLO_TINY_Z_AXIS_SHIFT; + plan_buffer_line_curposXYZE(homing_feedrate[Z_AXIS]/60); + st_synchronize(); + poweroff_z(); + + // Update Z position + eeprom_update_float((float*)(EEPROM_UVLO_TINY_CURRENT_POSITION_Z), current_position[Z_AXIS]); + + // Update the _final_ Z motor microstep counter (unused). + z_microsteps = tmc2130_rd_MSCNT(Z_AXIS); + eeprom_update_word((uint16_t*)(EEPROM_UVLO_Z_MICROSTEPS), z_microsteps); + } + + // Update the the "power outage" flag. + eeprom_update_byte((uint8_t*)EEPROM_UVLO,2); + + // Increment power failure counter + eeprom_update_byte((uint8_t*)EEPROM_POWER_COUNT, eeprom_read_byte((uint8_t*)EEPROM_POWER_COUNT) + 1); + eeprom_update_word((uint16_t*)EEPROM_POWER_COUNT_TOT, eeprom_read_word((uint16_t*)EEPROM_POWER_COUNT_TOT) + 1); + + printf_P(_N("UVLO_TINY - end %d\n"), _millis() - time_start); + uvlo_drain_reset(); } #endif //UVLO_SUPPORT @@ -10860,19 +10726,26 @@ void setup_uvlo_interrupt() { DDRE &= ~(1 << 4); //input pin PORTE &= ~(1 << 4); //no internal pull-up - //sensing falling edge + // sensing falling edge EICRB |= (1 << 0); EICRB &= ~(1 << 1); - //enable INT4 interrupt + // enable INT4 interrupt EIMSK |= (1 << 4); + + // check if power was lost before we armed the interrupt + if(!(PINE & (1 << 4)) && eeprom_read_byte((uint8_t*)EEPROM_UVLO)) + { + SERIAL_ECHOLNPGM("INT4"); + uvlo_drain_reset(); + } } ISR(INT4_vect) { EIMSK &= ~(1 << 4); //disable INT4 interrupt to make sure that this code will be executed just once SERIAL_ECHOLNPGM("INT4"); //fire normal uvlo only in case where EEPROM_UVLO is 0 or if IS_SD_PRINTING is 1. - if(PRINTER_ACTIVE && (!(eeprom_read_byte((uint8_t*)EEPROM_UVLO)))) uvlo_(); + if(printer_active() && (!(eeprom_read_byte((uint8_t*)EEPROM_UVLO)))) uvlo_(); if(eeprom_read_byte((uint8_t*)EEPROM_UVLO)) uvlo_tiny(); } @@ -10880,46 +10753,57 @@ void recover_print(uint8_t automatic) { char cmd[30]; lcd_update_enable(true); lcd_update(2); - lcd_setstatuspgm(_i("Recovering print "));////MSG_RECOVERING_PRINT c=20 r=1 + lcd_setstatuspgm(_i("Recovering print"));////MSG_RECOVERING_PRINT c=20 - bool bTiny=(eeprom_read_byte((uint8_t*)EEPROM_UVLO)==2); - recover_machine_state_after_power_panic(bTiny); //recover position, temperatures and extrude_multipliers - // Lift the print head, so one may remove the excess priming material. - if(!bTiny&&(current_position[Z_AXIS]<25)) - enquecommand_P(PSTR("G1 Z25 F800")); + // Recover position, temperatures and extrude_multipliers + bool mbl_was_active = recover_machine_state_after_power_panic(); - // Home X and Y axes. Homing just X and Y shall not touch the babystep and the world2machine transformation status. + // Lift the print head 25mm, first to avoid collisions with oozed material with the print, + // and second also so one may remove the excess priming material. + if(eeprom_read_byte((uint8_t*)EEPROM_UVLO) == 1) + { + sprintf_P(cmd, PSTR("G1 Z%.3f F800"), current_position[Z_AXIS] + 25); + enquecommand(cmd); + } + + // Home X and Y axes. Homing just X and Y shall not touch the babystep and the world2machine + // transformation status. G28 will not touch Z when MBL is off. enquecommand_P(PSTR("G28 X Y")); // Set the target bed and nozzle temperatures and wait. + sprintf_P(cmd, PSTR("M104 S%d"), target_temperature[active_extruder]); + enquecommand(cmd); + sprintf_P(cmd, PSTR("M140 S%d"), target_temperature_bed); + enquecommand(cmd); sprintf_P(cmd, PSTR("M109 S%d"), target_temperature[active_extruder]); enquecommand(cmd); - sprintf_P(cmd, PSTR("M190 S%d"), target_temperature_bed); - enquecommand(cmd); + enquecommand_P(PSTR("M83")); //E axis relative mode - //enquecommand_P(PSTR("G1 E5 F120")); //Extrude some filament to stabilize pessure - // If not automatically recoreverd (long power loss), extrude extra filament to stabilize - if(automatic == 0){ - enquecommand_P(PSTR("G1 E5 F120")); //Extrude some filament to stabilize pessure - } - enquecommand_P(PSTR("G1 E" STRINGIFY(-default_retraction)" F480")); + + // If not automatically recoreverd (long power loss) + if(automatic == 0){ + //Extrude some filament to stabilize the pressure + enquecommand_P(PSTR("G1 E5 F120")); + // Retract to be consistent with a short pause + sprintf_P(cmd, PSTR("G1 E%-0.3f F2700"), default_retraction); + enquecommand(cmd); + } printf_P(_N("After waiting for temp:\nCurrent pos X_AXIS:%.3f\nCurrent pos Y_AXIS:%.3f\n"), current_position[X_AXIS], current_position[Y_AXIS]); // Restart the print. - restore_print_from_eeprom(); + restore_print_from_eeprom(mbl_was_active); printf_P(_N("Current pos Z_AXIS:%.3f\nCurrent pos E_AXIS:%.3f\n"), current_position[Z_AXIS], current_position[E_AXIS]); } -void recover_machine_state_after_power_panic(bool bTiny) +bool recover_machine_state_after_power_panic() { - char cmd[30]; - // 1) Recover the logical cordinates at the time of the power panic. - // The logical XY coordinates are needed to recover the machine Z coordinate corrected by the mesh bed leveling. - current_position[X_AXIS] = eeprom_read_float((float*)(EEPROM_UVLO_CURRENT_POSITION + 0)); - current_position[Y_AXIS] = eeprom_read_float((float*)(EEPROM_UVLO_CURRENT_POSITION + 4)); + // 1) Preset some dummy values for the XY axes + current_position[X_AXIS] = 0; + current_position[Y_AXIS] = 0; - // 2) Restore the mesh bed leveling offsets. This is 2*7*7=98 bytes, which takes 98*3.4us=333us in worst case. - mbl.active = false; + // 2) Restore the mesh bed leveling offsets, but not the MBL status. + // This is 2*7*7=98 bytes, which takes 98*3.4us=333us in worst case. + bool mbl_was_active = false; for (int8_t mesh_point = 0; mesh_point < MESH_NUM_X_POINTS * MESH_NUM_Y_POINTS; ++ mesh_point) { uint8_t ix = mesh_point % MESH_NUM_X_POINTS; // from 0 to MESH_NUM_X_POINTS - 1 uint8_t iy = mesh_point / MESH_NUM_X_POINTS; @@ -10927,37 +10811,16 @@ void recover_machine_state_after_power_panic(bool bTiny) int16_t v; eeprom_read_block(&v, (void*)(EEPROM_UVLO_MESH_BED_LEVELING_FULL+2*mesh_point), 2); if (v != 0) - mbl.active = true; + mbl_was_active = true; mbl.z_values[iy][ix] = float(v) * 0.001f; } - // Recover the logical coordinate of the Z axis at the time of the power panic. + // Recover the physical coordinate of the Z axis at the time of the power panic. // The current position after power panic is moved to the next closest 0th full step. - if(bTiny){ - current_position[Z_AXIS] = eeprom_read_float((float*)(EEPROM_UVLO_TINY_CURRENT_POSITION_Z)) - + float((1024 - eeprom_read_word((uint16_t*)(EEPROM_UVLO_TINY_Z_MICROSTEPS)) - + 7) >> 4) / cs.axis_steps_per_unit[Z_AXIS]; + current_position[Z_AXIS] = eeprom_read_float((float*)(EEPROM_UVLO_TINY_CURRENT_POSITION_Z)); - //after multiple power panics the print is slightly in the air so get it little bit down. - //Not exactly sure why is this happening, but it has something to do with bed leveling and world2machine coordinates - current_position[Z_AXIS] -= 0.4*mbl.get_z(current_position[X_AXIS], current_position[Y_AXIS]); - } - else{ - current_position[Z_AXIS] = eeprom_read_float((float*)(EEPROM_UVLO_CURRENT_POSITION_Z)) + - UVLO_Z_AXIS_SHIFT + float((1024 - eeprom_read_word((uint16_t*)(EEPROM_UVLO_Z_MICROSTEPS)) - + 7) >> 4) / cs.axis_steps_per_unit[Z_AXIS]; - } - if (eeprom_read_byte((uint8_t*)EEPROM_UVLO_E_ABS)) { - current_position[E_AXIS] = eeprom_read_float((float*)(EEPROM_UVLO_CURRENT_POSITION_E)); - sprintf_P(cmd, PSTR("G92 E")); - dtostrf(current_position[E_AXIS], 6, 3, cmd + strlen(cmd)); - enquecommand(cmd); - } - - memcpy(destination, current_position, sizeof(destination)); - - SERIAL_ECHOPGM("recover_machine_state_after_power_panic, initial "); - print_world_coordinates(); + // Recover last E axis position + current_position[E_AXIS] = eeprom_read_float((float*)(EEPROM_UVLO_CURRENT_POSITION_E)); // 3) Initialize the logical to physical coordinate system transformation. world2machine_initialize(); @@ -10968,20 +10831,20 @@ void recover_machine_state_after_power_panic(bool bTiny) // The baby stepping value is used to reset the physical Z axis when rehoming the Z axis. babystep_load(); - // 5) Set the physical positions from the logical positions using the world2machine transformation and the active bed leveling. - plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); - - // 6) Power up the motors, mark their positions as known. - //FIXME Verfiy, whether the X and Y axes should be powered up here, as they will later be re-homed anyway. - axis_known_position[X_AXIS] = true; enable_x(); - axis_known_position[Y_AXIS] = true; enable_y(); - axis_known_position[Z_AXIS] = true; enable_z(); - + // 5) Set the physical positions from the logical positions using the world2machine transformation + // This is only done to inizialize Z/E axes with physical locations, since X/Y are unknown. + clamp_to_software_endstops(current_position); + set_destination_to_current(); + plan_set_position_curposXYZE(); SERIAL_ECHOPGM("recover_machine_state_after_power_panic, initial "); - print_physical_coordinates(); + print_world_coordinates(); + + // 6) Power up the Z motors, mark their positions as known. + axis_known_position[Z_AXIS] = true; + enable_z(); // 7) Recover the target temperatures. - target_temperature[active_extruder] = eeprom_read_byte((uint8_t*)EEPROM_UVLO_TARGET_HOTEND); + target_temperature[active_extruder] = eeprom_read_word((uint16_t*)EEPROM_UVLO_TARGET_HOTEND); target_temperature_bed = eeprom_read_byte((uint8_t*)EEPROM_UVLO_TARGET_BED); // 8) Recover extruder multipilers @@ -10995,28 +10858,32 @@ void recover_machine_state_after_power_panic(bool bTiny) extrudemultiply = (int)eeprom_read_word((uint16_t*)(EEPROM_EXTRUDEMULTIPLY)); // 9) Recover the saved target - saved_target[X_AXIS] = eeprom_read_float((float*)(EEPROM_UVLO_SAVED_TARGET+0*4)); - saved_target[Y_AXIS] = eeprom_read_float((float*)(EEPROM_UVLO_SAVED_TARGET+1*4)); - saved_target[Z_AXIS] = eeprom_read_float((float*)(EEPROM_UVLO_SAVED_TARGET+2*4)); - saved_target[E_AXIS] = eeprom_read_float((float*)(EEPROM_UVLO_SAVED_TARGET+3*4)); + saved_start_position[X_AXIS] = eeprom_read_float((float*)(EEPROM_UVLO_SAVED_START_POSITION+0*4)); + saved_start_position[Y_AXIS] = eeprom_read_float((float*)(EEPROM_UVLO_SAVED_START_POSITION+1*4)); + saved_start_position[Z_AXIS] = eeprom_read_float((float*)(EEPROM_UVLO_SAVED_START_POSITION+2*4)); + saved_start_position[E_AXIS] = eeprom_read_float((float*)(EEPROM_UVLO_SAVED_START_POSITION+3*4)); + + saved_segment_idx = eeprom_read_word((uint16_t*)EEPROM_UVLO_SAVED_SEGMENT_IDX); #ifdef LIN_ADVANCE extruder_advance_K = eeprom_read_float((float*)EEPROM_UVLO_LA_K); #endif + + return mbl_was_active; } -void restore_print_from_eeprom() { +void restore_print_from_eeprom(bool mbl_was_active) { int feedrate_rec; int feedmultiply_rec; uint8_t fan_speed_rec; - char cmd[30]; - char filename[13]; + char cmd[48]; + char filename[FILENAME_LENGTH]; uint8_t depth = 0; char dir_name[9]; fan_speed_rec = eeprom_read_byte((uint8_t*)EEPROM_UVLO_FAN_SPEED); feedrate_rec = eeprom_read_word((uint16_t*)EEPROM_UVLO_FEEDRATE); - EEPROM_read_B(EEPROM_UVLO_FEEDMULTIPLY, &feedmultiply_rec); + feedmultiply_rec = eeprom_read_word((uint16_t*)EEPROM_UVLO_FEEDMULTIPLY); SERIAL_ECHOPGM("Feedrate:"); MYSERIAL.print(feedrate_rec); SERIAL_ECHOPGM(", feedmultiply:"); @@ -11025,17 +10892,17 @@ void restore_print_from_eeprom() { depth = eeprom_read_byte((uint8_t*)EEPROM_DIR_DEPTH); MYSERIAL.println(int(depth)); - for (int i = 0; i < depth; i++) { - for (int j = 0; j < 8; j++) { + for (uint8_t i = 0; i < depth; i++) { + for (uint8_t j = 0; j < 8; j++) { dir_name[j] = eeprom_read_byte((uint8_t*)EEPROM_DIRS + j + 8 * i); } dir_name[8] = '\0'; MYSERIAL.println(dir_name); - strcpy(dir_names[i], dir_name); - card.chdir(dir_name); + // strcpy(card.dir_names[i], dir_name); + card.chdir(dir_name, false); } - for (int i = 0; i < 8; i++) { + for (uint8_t i = 0; i < 8; i++) { filename[i] = eeprom_read_byte((uint8_t*)EEPROM_FILENAME + i); } filename[8] = '\0'; @@ -11046,33 +10913,49 @@ void restore_print_from_eeprom() { enquecommand(cmd); uint32_t position = eeprom_read_dword((uint32_t*)(EEPROM_FILE_POSITION)); SERIAL_ECHOPGM("Position read from eeprom:"); - MYSERIAL.println(position); - // E axis relative mode. - enquecommand_P(PSTR("M83")); - // Move to the XY print position in logical coordinates, where the print has been killed. - strcpy_P(cmd, PSTR("G1 X")); strcat(cmd, ftostr32(eeprom_read_float((float*)(EEPROM_UVLO_CURRENT_POSITION + 0)))); - strcat_P(cmd, PSTR(" Y")); strcat(cmd, ftostr32(eeprom_read_float((float*)(EEPROM_UVLO_CURRENT_POSITION + 4)))); - strcat_P(cmd, PSTR(" F2000")); - enquecommand(cmd); - //moving on Z axis ahead, set EEPROM_UVLO to 1, so normal uvlo can fire - eeprom_update_byte((uint8_t*)EEPROM_UVLO,1); - // Move the Z axis down to the print, in logical coordinates. - strcpy_P(cmd, PSTR("G1 Z")); strcat(cmd, ftostr32(eeprom_read_float((float*)(EEPROM_UVLO_CURRENT_POSITION_Z)))); + MYSERIAL.println(position); + + // Move to the XY print position in logical coordinates, where the print has been killed, but + // without shifting Z along the way. This requires performing the move without mbl. + float pos_x = eeprom_read_float((float*)(EEPROM_UVLO_CURRENT_POSITION + 0)); + float pos_y = eeprom_read_float((float*)(EEPROM_UVLO_CURRENT_POSITION + 4)); + if (pos_x != X_COORD_INVALID) + { + sprintf_P(cmd, PSTR("G1 X%f Y%f F3000"), pos_x, pos_y); + enquecommand(cmd); + } + + // Enable MBL and switch to logical positioning + if (mbl_was_active) + enquecommand_P(PSTR("PRUSA MBL V1")); + + // Move the Z axis down to the print, in logical coordinates. + sprintf_P(cmd, PSTR("G1 Z%f"), eeprom_read_float((float*)(EEPROM_UVLO_CURRENT_POSITION_Z))); enquecommand(cmd); + + // Restore acceleration settings + float acceleration = eeprom_read_float((float*)(EEPROM_UVLO_ACCELL)); + float retract_acceleration = eeprom_read_float((float*)(EEPROM_UVLO_RETRACT_ACCELL)); + float travel_acceleration = eeprom_read_float((float*)(EEPROM_UVLO_TRAVEL_ACCELL)); + sprintf_P(cmd, PSTR("M204 P%f R%f T%f"), acceleration, retract_acceleration, travel_acceleration); + enquecommand(cmd); + // Unretract. - enquecommand_P(PSTR("G1 E" STRINGIFY(2*default_retraction)" F480")); + sprintf_P(cmd, PSTR("G1 E%0.3f F2700"), default_retraction); + enquecommand(cmd); + // Recover final E axis position and mode + float pos_e = eeprom_read_float((float*)(EEPROM_UVLO_CURRENT_POSITION_E)); + sprintf_P(cmd, PSTR("G92 E%6.3f"), pos_e); + enquecommand(cmd); + if (eeprom_read_byte((uint8_t*)EEPROM_UVLO_E_ABS)) + enquecommand_P(PSTR("M82")); //E axis abslute mode // Set the feedrates saved at the power panic. sprintf_P(cmd, PSTR("G1 F%d"), feedrate_rec); enquecommand(cmd); sprintf_P(cmd, PSTR("M220 S%d"), feedmultiply_rec); enquecommand(cmd); - if (eeprom_read_byte((uint8_t*)EEPROM_UVLO_E_ABS)) - { - enquecommand_P(PSTR("M82")); //E axis abslute mode - } // Set the fan speed saved at the power panic. - strcpy_P(cmd, PSTR("M106 S")); - strcat(cmd, itostr3(int(fan_speed_rec))); + sprintf_P(cmd, PSTR("M106 S%u"), fan_speed_rec); enquecommand(cmd); // Set a position in the file. @@ -11116,7 +10999,7 @@ void stop_and_save_print_to_ram(float z_move, float e_move) saved_printing_type = PRINTING_TYPE_SD; } - else if (is_usb_printing) { //reuse saved_sdpos for storing line number + else if (usb_timer.running()) { //reuse saved_sdpos for storing line number saved_sdpos = gcode_LastN; //start with line number of command added recently to cmd queue //reuse planner_calc_sd_length function for getting number of lines of commands in planner: nlines = planner_calc_sd_length(); //number of lines of commands in planner @@ -11218,24 +11101,31 @@ void stop_and_save_print_to_ram(float z_move, float e_move) #endif // save the global state at planning time - if (blocks_queued()) + bool pos_invalid = XY_NO_RESTORE_FLAG; + if (current_block && !pos_invalid) { - memcpy(saved_target, current_block->gcode_target, sizeof(saved_target)); + memcpy(saved_start_position, current_block->gcode_start_position, sizeof(saved_start_position)); saved_feedrate2 = current_block->gcode_feedrate; + saved_segment_idx = current_block->segment_idx; + // printf_P(PSTR("stop_and_save_print_to_ram: %f, %f, %f, %f, %u\n"), saved_start_position[0], saved_start_position[1], saved_start_position[2], saved_start_position[3], saved_segment_idx); } else { - saved_target[0] = SAVED_TARGET_UNSET; + saved_start_position[0] = SAVED_START_POSITION_UNSET; saved_feedrate2 = feedrate; + saved_segment_idx = 0; } planner_abort_hard(); //abort printing + memcpy(saved_pos, current_position, sizeof(saved_pos)); + if (pos_invalid) saved_pos[X_AXIS] = X_COORD_INVALID; + saved_feedmultiply2 = feedmultiply; //save feedmultiply - saved_active_extruder = active_extruder; //save active_extruder saved_extruder_temperature = degTargetHotend(active_extruder); - saved_extruder_relative_mode = axis_relative_modes[E_AXIS]; - saved_fanSpeed = fanSpeed; + saved_bed_temperature = degTargetBed(); + saved_extruder_relative_mode = axis_relative_modes & E_AXIS_MASK; + saved_fan_speed = fanSpeed; cmdqueue_reset(); //empty cmdqueue card.sdprinting = false; // card.closefile(); @@ -11244,7 +11134,7 @@ void stop_and_save_print_to_ram(float z_move, float e_move) st_reset_timer(); sei(); if ((z_move != 0) || (e_move != 0)) { // extruder or z move -#if 1 + // Rather than calling plan_buffer_line directly, push the move into the command queue so that // the caller can continue processing. This is used during powerpanic to save the state as we // move away from the print. @@ -11276,16 +11166,19 @@ void stop_and_save_print_to_ram(float z_move, float e_move) // If this call is invoked from the main Arduino loop() function, let the caller know that the command // in the command queue is not the original command, but a new one, so it should not be removed from the queue. repeatcommand_front(); -#else - plan_buffer_line(saved_pos[X_AXIS], saved_pos[Y_AXIS], saved_pos[Z_AXIS] + z_move, saved_pos[E_AXIS] + e_move, homing_feedrate[Z_AXIS], active_extruder); - st_synchronize(); //wait moving - memcpy(current_position, saved_pos, sizeof(saved_pos)); - memcpy(destination, current_position, sizeof(destination)); -#endif - waiting_inside_plan_buffer_line_print_aborted = true; //unroll the stack } } +void restore_extruder_temperature_from_ram() { + if (degTargetHotend(active_extruder) != saved_extruder_temperature) + { + setTargetHotend(saved_extruder_temperature); + heating_status = HeatingStatus::EXTRUDER_HEATING; + wait_for_heater(_millis(), active_extruder); + heating_status = HeatingStatus::EXTRUDER_HEATING_COMPLETE; + } +} + //! @brief Restore print from ram //! //! Restore print saved by stop_and_save_print_to_ram(). Is blocking, restores @@ -11304,32 +11197,38 @@ void restore_print_from_ram_and_continue(float e_move) if ((fan_check_error != EFCE_OK) && (fan_check_error != EFCE_FIXED)) return; if (fan_check_error == EFCE_FIXED) fan_check_error = EFCE_OK; //reenable serial stream processing if printing from usb #endif - -// for (int axis = X_AXIS; axis <= E_AXIS; axis++) -// current_position[axis] = st_get_position_mm(axis); - active_extruder = saved_active_extruder; //restore active_extruder - fanSpeed = saved_fanSpeed; - if (degTargetHotend(saved_active_extruder) != saved_extruder_temperature) - { - setTargetHotendSafe(saved_extruder_temperature, saved_active_extruder); - heating_status = 1; - wait_for_heater(_millis(), saved_active_extruder); - heating_status = 2; - } - axis_relative_modes[E_AXIS] = saved_extruder_relative_mode; - float e = saved_pos[E_AXIS] - e_move; - plan_set_e_position(e); + + // Make sure fan is turned off + fanSpeed = 0; + + // restore bed temperature (bed can be disabled during a thermal warning) + if (degBed() != saved_bed_temperature) + setTargetBed(saved_bed_temperature); + restore_extruder_temperature_from_ram(); + + // Restore saved fan speed + fanSpeed = saved_fan_speed; + axis_relative_modes ^= (-saved_extruder_relative_mode ^ axis_relative_modes) & E_AXIS_MASK; + float e = saved_pos[E_AXIS] - e_move; + plan_set_e_position(e); #ifdef FANCHECK fans_check_enabled = false; #endif + // do not restore XY for commands that do not require that + if (saved_pos[X_AXIS] == X_COORD_INVALID) + { + saved_pos[X_AXIS] = current_position[X_AXIS]; + saved_pos[Y_AXIS] = current_position[Y_AXIS]; + } + //first move print head in XY to the saved position: - plan_buffer_line(saved_pos[X_AXIS], saved_pos[Y_AXIS], current_position[Z_AXIS], saved_pos[E_AXIS] - e_move, homing_feedrate[Z_AXIS]/13, active_extruder); + plan_buffer_line(saved_pos[X_AXIS], saved_pos[Y_AXIS], current_position[Z_AXIS], saved_pos[E_AXIS] - e_move, homing_feedrate[Z_AXIS]/13); //then move Z - plan_buffer_line(saved_pos[X_AXIS], saved_pos[Y_AXIS], saved_pos[Z_AXIS], saved_pos[E_AXIS] - e_move, homing_feedrate[Z_AXIS]/13, active_extruder); + plan_buffer_line(saved_pos[X_AXIS], saved_pos[Y_AXIS], saved_pos[Z_AXIS], saved_pos[E_AXIS] - e_move, homing_feedrate[Z_AXIS]/13); //and finaly unretract (35mm/s) - plan_buffer_line(saved_pos[X_AXIS], saved_pos[Y_AXIS], saved_pos[Z_AXIS], saved_pos[E_AXIS], FILAMENTCHANGE_RFEED, active_extruder); + plan_buffer_line(saved_pos[X_AXIS], saved_pos[Y_AXIS], saved_pos[Z_AXIS], saved_pos[E_AXIS], FILAMENTCHANGE_RFEED); st_synchronize(); #ifdef FANCHECK @@ -11341,7 +11240,7 @@ void restore_print_from_ram_and_continue(float e_move) feedmultiply = saved_feedmultiply2; memcpy(current_position, saved_pos, sizeof(saved_pos)); - memcpy(destination, current_position, sizeof(destination)); + set_destination_to_current(); if (saved_printing_type == PRINTING_TYPE_SD) { //was sd printing card.setIndex(saved_sdpos); sdpos_atomic = saved_sdpos; @@ -11356,17 +11255,17 @@ void restore_print_from_ram_and_continue(float e_move) //not sd printing nor usb printing } - SERIAL_PROTOCOLLNRPGM(MSG_OK); //dummy response because of octoprint is waiting for this - lcd_setstatuspgm(_T(WELCOME_MSG)); + lcd_setstatuspgm(MSG_WELCOME); saved_printing_type = PRINTING_TYPE_NONE; saved_printing = false; - waiting_inside_plan_buffer_line_print_aborted = true; //unroll the stack + planner_aborted = true; // unroll the stack } // Cancel the state related to a currently saved print void cancel_saved_printing() { - saved_target[0] = SAVED_TARGET_UNSET; + eeprom_update_byte((uint8_t*)EEPROM_UVLO, 0); + saved_start_position[0] = SAVED_START_POSITION_UNSET; saved_printing_type = PRINTING_TYPE_NONE; saved_printing = false; } @@ -11387,78 +11286,79 @@ void print_mesh_bed_leveling_table() for (int8_t y = 0; y < MESH_NUM_Y_POINTS; ++ y) for (int8_t x = 0; x < MESH_NUM_Y_POINTS; ++ x) { MYSERIAL.print(mbl.z_values[y][x], 3); - SERIAL_ECHOPGM(" "); + SERIAL_ECHO(' '); } - SERIAL_ECHOLNPGM(""); -} - -uint16_t print_time_remaining() { - uint16_t print_t = PRINT_TIME_REMAINING_INIT; -#ifdef TMC2130 - if (SilentModeMenu == SILENT_MODE_OFF) print_t = print_time_remaining_normal; - else print_t = print_time_remaining_silent; -#else - print_t = print_time_remaining_normal; -#endif //TMC2130 - if ((print_t != PRINT_TIME_REMAINING_INIT) && (feedmultiply != 0)) print_t = 100ul * print_t / feedmultiply; - return print_t; + SERIAL_ECHOLN(); } uint8_t calc_percent_done() { - //in case that we have information from M73 gcode return percentage counted by slicer, else return percentage counted as byte_printed/filesize - uint8_t percent_done = 0; + //in case that we have information from M73 gcode return percentage counted by slicer, else return percentage counted as byte_printed/filesize + uint8_t percent_done = 0; #ifdef TMC2130 - if (SilentModeMenu == SILENT_MODE_OFF && print_percent_done_normal <= 100) { - percent_done = print_percent_done_normal; - } - else if (print_percent_done_silent <= 100) { - percent_done = print_percent_done_silent; - } + if (SilentModeMenu == SILENT_MODE_OFF && print_percent_done_normal <= 100) + { + percent_done = print_percent_done_normal; + } + else if (print_percent_done_silent <= 100) + { + percent_done = print_percent_done_silent; + } #else - if (print_percent_done_normal <= 100) { - percent_done = print_percent_done_normal; - } + if (print_percent_done_normal <= 100) + { + percent_done = print_percent_done_normal; + } #endif //TMC2130 - else { - percent_done = card.percentDone(); - } - return percent_done; + else + { + percent_done = card.percentDone(); + } + return percent_done; } static void print_time_remaining_init() { - print_time_remaining_normal = PRINT_TIME_REMAINING_INIT; - print_time_remaining_silent = PRINT_TIME_REMAINING_INIT; - print_percent_done_normal = PRINT_PERCENT_DONE_INIT; - print_percent_done_silent = PRINT_PERCENT_DONE_INIT; + print_time_remaining_normal = PRINT_TIME_REMAINING_INIT; + print_percent_done_normal = PRINT_PERCENT_DONE_INIT; + print_time_remaining_silent = PRINT_TIME_REMAINING_INIT; + print_percent_done_silent = PRINT_PERCENT_DONE_INIT; + print_time_to_change_normal = PRINT_TIME_REMAINING_INIT; + print_time_to_change_silent = PRINT_TIME_REMAINING_INIT; } void load_filament_final_feed() { current_position[E_AXIS]+= FILAMENTCHANGE_FINALFEED; - plan_buffer_line_curposXYZE(FILAMENTCHANGE_EFEED_FINAL, active_extruder); + plan_buffer_line_curposXYZE(FILAMENTCHANGE_EFEED_FINAL); } //! @brief Wait for user to check the state //! @par nozzle_temp nozzle temperature to load filament void M600_check_state(float nozzle_temp) { - lcd_change_fil_state = 0; - while (lcd_change_fil_state != 1) + uint8_t lcd_change_filament_state = 0; + while (lcd_change_filament_state != 1) { - lcd_change_fil_state = 0; KEEPALIVE_STATE(PAUSED_FOR_USER); - lcd_alright(); + lcd_change_filament_state = lcd_alright(); KEEPALIVE_STATE(IN_HANDLER); - switch(lcd_change_fil_state) + switch(lcd_change_filament_state) { // Filament failed to load so load it again case 2: - if (mmu_enabled) - mmu_M600_load_filament(false, nozzle_temp); //nonautomatic load; change to "wrong filament loaded" option? - else + if (MMU2::mmu2.Enabled()){ + // Unload filament + mmu_M600_unload_filament(); + + // Ask to remove any old filament and load new + mmu_M600_wait_and_beep(); + + // After user clicks knob, MMU will load the filament + mmu_M600_load_filament(false, nozzle_temp); + } else { M600_load_filament_movements(); + } break; // Filament loaded properly but color is not clear @@ -11487,155 +11387,84 @@ void M600_wait_for_user(float HotendTempBckp) { KEEPALIVE_STATE(PAUSED_FOR_USER); - int counterBeep = 0; unsigned long waiting_start_time = _millis(); uint8_t wait_for_user_state = 0; lcd_display_message_fullscreen_P(_T(MSG_PRESS_TO_UNLOAD)); - bool bFirst=true; while (!(wait_for_user_state == 0 && lcd_clicked())){ manage_heater(); manage_inactivity(true); - - #if BEEPER > 0 - if (counterBeep == 500) { - counterBeep = 0; - } - SET_OUTPUT(BEEPER); - if (counterBeep == 0) { - if((eSoundMode==e_SOUND_MODE_BLIND)|| (eSoundMode==e_SOUND_MODE_LOUD)||((eSoundMode==e_SOUND_MODE_ONCE)&&bFirst)) - { - bFirst=false; - WRITE(BEEPER, HIGH); - } - } - if (counterBeep == 20) { - WRITE(BEEPER, LOW); - } - - counterBeep++; - #endif //BEEPER > 0 + if (wait_for_user_state != 2) sound_wait_for_user(); switch (wait_for_user_state) { case 0: //nozzle is hot, waiting for user to press the knob to unload filament delay_keep_alive(4); if (_millis() > waiting_start_time + (unsigned long)M600_TIMEOUT * 1000) { - lcd_display_message_fullscreen_P(_i("Press knob to preheat nozzle and continue."));////MSG_PRESS_TO_PREHEAT c=20 r=4 + lcd_display_message_fullscreen_P(_i("Press the knob to preheat nozzle and continue."));////MSG_PRESS_TO_PREHEAT c=20 r=4 wait_for_user_state = 1; - setAllTargetHotends(0); + setTargetHotend(0); st_synchronize(); disable_e0(); - disable_e1(); - disable_e2(); } break; case 1: //nozzle target temperature is set to zero, waiting for user to start nozzle preheat delay_keep_alive(4); if (lcd_clicked()) { - setTargetHotend(HotendTempBckp, active_extruder); + setTargetHotend(HotendTempBckp); lcd_wait_for_heater(); - wait_for_user_state = 2; } break; case 2: //waiting for nozzle to reach target temperature - - if (abs(degTargetHotend(active_extruder) - degHotend(active_extruder)) < 1) { + if (fabs(degTargetHotend(active_extruder) - degHotend(active_extruder)) < 1) { lcd_display_message_fullscreen_P(_T(MSG_PRESS_TO_UNLOAD)); waiting_start_time = _millis(); wait_for_user_state = 0; - } - else { - counterBeep = 20; //beeper will be inactive during waiting for nozzle preheat + } else { lcd_set_cursor(1, 4); - lcd_print(ftostr3(degHotend(active_extruder))); + lcd_printf_P(PSTR("%3d"), (int16_t)degHotend(active_extruder)); } break; - } - } - WRITE(BEEPER, LOW); + sound_wait_for_user_reset(); } void M600_load_filament_movements() { -#ifdef SNMM - display_loading(); - do - { - current_position[E_AXIS] += 0.002; - plan_buffer_line_curposXYZE(500, active_extruder); - delay_keep_alive(2); - } - while (!lcd_clicked()); - st_synchronize(); - current_position[E_AXIS] += bowden_length[mmu_extruder]; - plan_buffer_line_curposXYZE(3000, active_extruder); - current_position[E_AXIS] += FIL_LOAD_LENGTH - 60; - plan_buffer_line_curposXYZE(1400, active_extruder); - current_position[E_AXIS] += 40; - plan_buffer_line_curposXYZE(400, active_extruder); - current_position[E_AXIS] += 10; - plan_buffer_line_curposXYZE(50, active_extruder); -#else - current_position[E_AXIS]+= FILAMENTCHANGE_FIRSTFEED ; - plan_buffer_line_curposXYZE(FILAMENTCHANGE_EFEED_FIRST, active_extruder); -#endif + current_position[E_AXIS]+= FILAMENTCHANGE_FIRSTFEED; + plan_buffer_line_curposXYZE(FILAMENTCHANGE_EFEED_FIRST); load_filament_final_feed(); lcd_loading_filament(); st_synchronize(); } void M600_load_filament() { - //load filament for single material and SNMM + //load filament for single material and MMU lcd_wait_interact(); //load_filament_time = _millis(); KEEPALIVE_STATE(PAUSED_FOR_USER); -#ifdef PAT9125 - fsensor_autoload_check_start(); -#endif //PAT9125 while(!lcd_clicked()) { manage_heater(); manage_inactivity(true); #ifdef FILAMENT_SENSOR - if (fsensor_check_autoload()) - { - Sound_MakeCustom(50,1000,false); + if (fsensor.getFilamentLoadEvent()) { + Sound_MakeCustom(50,1000,false); break; } #endif //FILAMENT_SENSOR } -#ifdef PAT9125 - fsensor_autoload_check_stop(); -#endif //PAT9125 KEEPALIVE_STATE(IN_HANDLER); -#ifdef FSENSOR_QUALITY - fsensor_oq_meassure_start(70); -#endif //FSENSOR_QUALITY - M600_load_filament_movements(); - Sound_MakeCustom(50,1000,false); + Sound_MakeCustom(50,1000,false); -#ifdef FSENSOR_QUALITY - fsensor_oq_meassure_stop(); - - if (!fsensor_oq_result()) - { - bool disable = lcd_show_fullscreen_message_yes_no_and_wait_P(_i("Fil. sensor response is poor, disable it?"), false, true); - lcd_update_enable(true); - lcd_update(2); - if (disable) - fsensor_disable(); - } -#endif //FSENSOR_QUALITY lcd_update_enable(false); } @@ -11650,15 +11479,11 @@ void marlin_wait_for_click() lcd_consume_click(); while(!lcd_clicked()) { - manage_heater(); - manage_inactivity(true); - lcd_update(0); + delay_keep_alive(0); } KEEPALIVE_STATE(busy_state_backup); } -#define FIL_LOAD_LENGTH 60 - #ifdef PSU_Delta bool bEnableForce_z; @@ -11677,8 +11502,6 @@ if(!(bEnableForce_z||eeprom_read_byte((uint8_t*)EEPROM_SILENT))) void disable_force_z() { - uint16_t z_microsteps=0; - if(!bEnableForce_z) return; // motor already disabled (may be ;-p ) bEnableForce_z=false; @@ -11687,13 +11510,10 @@ void disable_force_z() #ifdef TMC2130 tmc2130_mode=TMC2130_MODE_SILENT; update_mode_profile(); - tmc2130_init(true); + tmc2130_init(TMCInitParams(true, FarmOrUserECool())); #endif // TMC2130 - - axis_known_position[Z_AXIS]=false; } - void enable_force_z() { if(bEnableForce_z) @@ -11704,7 +11524,7 @@ bEnableForce_z=true; #ifdef TMC2130 tmc2130_mode=eeprom_read_byte((uint8_t*)EEPROM_SILENT)?TMC2130_MODE_SILENT:TMC2130_MODE_NORMAL; update_mode_profile(); -tmc2130_init(true); +tmc2130_init(TMCInitParams(true, FarmOrUserECool())); #endif // TMC2130 WRITE(Z_ENABLE_PIN,Z_ENABLE_ON); // slightly redundant ;-p diff --git a/Firmware/Prusa_farm.cpp b/Firmware/Prusa_farm.cpp new file mode 100644 index 000000000..642e811ec --- /dev/null +++ b/Firmware/Prusa_farm.cpp @@ -0,0 +1,495 @@ +#include "Prusa_farm.h" +#include "macros.h" +#include "Marlin.h" +#include "cmdqueue.h" +#include "temperature.h" +#include "cardreader.h" +#include "util.h" +#include "ultralcd.h" +#include "Filament_sensor.h" + +#ifdef PRUSA_FARM +uint8_t farm_mode = 0; + +static ShortTimer NcTime; +static uint8_t farm_timer = 8; +static uint8_t status_number = 0; +static bool no_response = false; +#ifdef PRUSA_M28 +#define CHUNK_SIZE 64 // bytes +#define SAFETY_MARGIN 1 +bool prusa_sd_card_upload = false; +char chunk[CHUNK_SIZE+SAFETY_MARGIN]; +#endif + + +static void prusa_statistics_err(char c); +static void prusa_stat_printerstatus(uint8_t _status); +static void prusa_stat_farm_number(); +static void prusa_stat_diameter(); +static void prusa_stat_temperatures(); +static void prusa_stat_printinfo(); +static void lcd_send_status(); +#ifdef FARM_CONNECT_MESSAGE +static void proc_commands(); +static void lcd_connect_printer(); +#endif //FARM_CONNECT_MESSAGE +#ifdef PRUSA_M28 +static void trace(); +#endif + + +static void prusa_statistics_err(char c) { + SERIAL_ECHOPGM("{[ERR:"); + SERIAL_ECHO(c); + SERIAL_ECHO(']'); + prusa_stat_farm_number(); +} + +static void prusa_statistics_case0(uint8_t statnr) { + SERIAL_ECHO('{'); + prusa_stat_printerstatus(statnr); + prusa_stat_farm_number(); + prusa_stat_printinfo(); +} + +static void prusa_stat_printerstatus(uint8_t _status) { + SERIAL_ECHOPGM("[PRN:"); + SERIAL_ECHO(_status); + SERIAL_ECHO(']'); +} + +static void prusa_stat_farm_number() { + SERIAL_ECHOPGM("[PFN:0]"); +} + +static void prusa_stat_diameter() { + SERIAL_ECHOPGM("[DIA:"); + SERIAL_ECHO(eeprom_read_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM)); + SERIAL_ECHO(']'); +} + +static void prusa_stat_temperatures() { + SERIAL_ECHOPGM("[ST0:"); + SERIAL_ECHO(target_temperature[0]); + SERIAL_ECHOPGM("][STB:"); + SERIAL_ECHO(target_temperature_bed); + SERIAL_ECHOPGM("][AT0:"); + SERIAL_ECHO(current_temperature[0]); + SERIAL_ECHOPGM("][ATB:"); + SERIAL_ECHO(current_temperature_bed); + SERIAL_ECHO(']'); +} + +static void prusa_stat_printinfo() { + SERIAL_ECHOPGM("[TFU:"); + SERIAL_ECHO(total_filament_used); + SERIAL_ECHOPGM("][PCD:"); + SERIAL_ECHO((int)card.percentDone()); + SERIAL_ECHOPGM("][FEM:"); + SERIAL_ECHO(feedmultiply); + SERIAL_ECHOPGM("][FNM:"); + SERIAL_ECHO(card.longFilename[0] ? card.longFilename : card.filename); + SERIAL_ECHOPGM("][TIM:"); + if (starttime != 0) { + SERIAL_ECHO(_millis() / 1000 - starttime / 1000); + } + else { + SERIAL_ECHO(0); + } + SERIAL_ECHOPGM("][FWR:"); + SERIAL_ECHORPGM(FW_VERSION_STR_P()); + SERIAL_ECHO(']'); + prusa_stat_diameter(); +} + +static void lcd_send_status() { + if (farm_mode && no_response && (NcTime.expired(NC_TIME * 1000))) { + //send important status messages periodicaly + prusa_statistics(8); + NcTime.start(); +#ifdef FARM_CONNECT_MESSAGE + lcd_connect_printer(); +#endif //FARM_CONNECT_MESSAGE + } +} + +#ifdef FARM_CONNECT_MESSAGE +static void proc_commands() { + if (buflen) { + process_commands(); + if (!cmdbuffer_front_already_processed) + cmdqueue_pop_front(); + cmdbuffer_front_already_processed = false; + } +} + +static void lcd_connect_printer() { + lcd_update_enable(false); + lcd_clear(); + + int i = 0; + int t = 0; + lcd_puts_at_P(0, 0, PSTR("Connect printer to")); + lcd_puts_at_P(0, 1, PSTR("monitoring or hold")); + lcd_puts_at_P(0, 2, PSTR("the knob to continue")); + while (no_response) { + i++; + t++; + delay_keep_alive(100); + proc_commands(); + if (t == 10) { + prusa_statistics(8); + t = 0; + } + if (READ(BTN_ENC)) { //if button is not pressed + i = 0; + lcd_puts_at_P(0, 3, PSTR(" ")); + } + if (i != 0) + lcd_putc_at((i * 20) / (NC_BUTTON_LONG_PRESS * 10), 3, LCD_STR_SOLID_BLOCK[0]); + if (i == NC_BUTTON_LONG_PRESS * 10) + no_response = false; + } + lcd_update_enable(true); + lcd_update(2); +} +#endif //FARM_CONNECT_MESSAGE + +#ifdef PRUSA_M28 +static void trace() { + Sound_MakeCustom(25,440,true); +} + +void serial_read_stream() { + + setTargetHotend(0); + setTargetBed(0); + + lcd_clear(); + lcd_puts_P(PSTR(" Upload in progress")); + + // first wait for how many bytes we will receive + uint32_t bytesToReceive; + + // receive the four bytes + char bytesToReceiveBuffer[4]; + for (int i=0; i<4; i++) { + int data; + while ((data = MYSERIAL.read()) == -1) {}; + bytesToReceiveBuffer[i] = data; + + } + + // make it a uint32 + memcpy(&bytesToReceive, &bytesToReceiveBuffer, 4); + + // we're ready, notify the sender + MYSERIAL.write('+'); + + // lock in the routine + uint32_t receivedBytes = 0; + while (prusa_sd_card_upload) { + int i; + for (i=0; i on + eeprom_update_byte((uint8_t*)EEPROM_FAN_CHECK_ENABLED, true); + } +} + +bool farm_prusa_code_seen() { + if (!farm_mode) + return false; + + if (code_seen_P(PSTR("PRN"))) { // PRUSA PRN + printf_P(_N("%u"), status_number); + } + else if (code_seen_P(PSTR("thx"))) { // PRUSA thx + no_response = false; + } +#ifdef PRUSA_M28 + else if (code_seen_P(PSTR("M28"))) { // PRUSA M28 + trace(); + prusa_sd_card_upload = true; + card.openFileWrite(strchr_pointer+4); + } +#endif //PRUSA_M28 + else if (code_seen_P(PSTR("fv"))) { // PRUSA fv + // get file version +#ifdef SDSUPPORT + card.openFileReadFilteredGcode(strchr_pointer + 3, true); + while (true) { + uint16_t readByte = card.getFilteredGcodeChar(); + MYSERIAL.write(readByte); + if (readByte == '\n') { + break; + } + } + card.closefile(); +#endif // SDSUPPORT + } + else { + return false; + } + + return true; +} + +void farm_gcode_g98() { + farm_mode = 1; + eeprom_update_byte((unsigned char *)EEPROM_FARM_MODE, farm_mode); + SilentModeMenu = SILENT_MODE_OFF; + eeprom_update_byte((unsigned char *)EEPROM_SILENT, SilentModeMenu); + fCheckModeInit(); // alternatively invoke printer reset +} + +void farm_gcode_g99() { + farm_disable(); + lcd_update(2); + fCheckModeInit(); // alternatively invoke printer reset +} + +void farm_disable() { + farm_mode = false; + eeprom_update_byte((uint8_t*)EEPROM_FARM_MODE, farm_mode); +} + +#else //PRUSA_FARM + +void prusa_statistics(_UNUSED uint8_t message) { +} + +void prusa_statistics_update_from_status_screen() { +} + +void prusa_statistics_update_from_lcd_update() { +} + +void farm_mode_init() { +} + +bool farm_prusa_code_seen() { + return false; +} + +void farm_gcode_g98() { +} + +void farm_gcode_g99() { +} + +void farm_disable() { +} + +#endif //PRUSA_FARM + diff --git a/Firmware/Prusa_farm.h b/Firmware/Prusa_farm.h new file mode 100644 index 000000000..21f7e6986 --- /dev/null +++ b/Firmware/Prusa_farm.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include "config.h" + +#define FARM_PREHEAT_HOTEND_TEMP 250 +#define FARM_PREHEAT_HPB_TEMP 80 + +#define FARM_DEFAULT_SAFETYTIMER_TIME_ms (45*60*1000ul) +#define NC_TIME 10 //time in s for periodic important status messages sending which needs reponse from monitoring +#define NC_BUTTON_LONG_PRESS 15 //time in s + +//#define FARM_CONNECT_MESSAGE + +#ifdef PRUSA_FARM +extern uint8_t farm_mode; +#else +#define farm_mode 0 +#endif + +#ifdef PRUSA_M28 +extern bool prusa_sd_card_upload; +extern void serial_read_stream(); +#endif +extern void prusa_statistics(uint8_t _message); +extern void prusa_statistics_update_from_status_screen(); +extern void prusa_statistics_update_from_lcd_update(); +extern void farm_mode_init(); +extern bool farm_prusa_code_seen(); +extern void farm_gcode_g98(); +extern void farm_gcode_g99(); +extern void farm_disable(); diff --git a/Firmware/Sd2Card.cpp b/Firmware/Sd2Card.cpp index 449590f17..aa0d3ccfe 100644 --- a/Firmware/Sd2Card.cpp +++ b/Firmware/Sd2Card.cpp @@ -205,14 +205,14 @@ uint32_t Sd2Card::cardSize() { } //------------------------------------------------------------------------------ void Sd2Card::chipSelectHigh() { - digitalWrite(chipSelectPin_, HIGH); + WRITE(SDSS, 1); } //------------------------------------------------------------------------------ void Sd2Card::chipSelectLow() { #ifndef SOFTWARE_SPI spiInit(spiRate_); #endif // SOFTWARE_SPI - digitalWrite(chipSelectPin_, LOW); + WRITE(SDSS, 0); } //------------------------------------------------------------------------------ /** Erase a range of blocks. @@ -283,26 +283,25 @@ bool Sd2Card::eraseSingleBlockEnable() { * the value zero, false, is returned for failure. The reason for failure * can be determined by calling errorCode() and errorData(). */ -bool Sd2Card::init(uint8_t sckRateID, uint8_t chipSelectPin) { +bool Sd2Card::init(uint8_t sckRateID) { errorCode_ = type_ = 0; - chipSelectPin_ = chipSelectPin; // 16-bit init start time allows over a minute uint16_t t0 = (uint16_t)_millis(); uint32_t arg; // set pin modes - pinMode(chipSelectPin_, OUTPUT); chipSelectHigh(); - pinMode(SPI_MISO_PIN, INPUT); - pinMode(SPI_MOSI_PIN, OUTPUT); - pinMode(SPI_SCK_PIN, OUTPUT); + SET_OUTPUT(SDSS); + SET_INPUT(MISO); + SET_OUTPUT(MOSI); + SET_OUTPUT(SCK); #ifndef SOFTWARE_SPI // SS must be in output mode even it is not chip select - pinMode(SS_PIN, OUTPUT); + SET_OUTPUT(SS); // set SS high - may be chip select for another SPI device #if SET_SPI_SS_HIGH - digitalWrite(SS_PIN, HIGH); + WRITE(SS, 1); #endif // SET_SPI_SS_HIGH // set SCK rate for initialization commands spiRate_ = SPI_SD_INIT_RATE; @@ -312,13 +311,16 @@ bool Sd2Card::init(uint8_t sckRateID, uint8_t chipSelectPin) { // must supply min of 74 clock cycles with CS high. for (uint8_t i = 0; i < 10; i++) spiSend(0XFF); + WRITE(MISO, 1); // temporarily enable the MISO line pullup // command to go idle in SPI mode while ((status_ = cardCommand(CMD0, 0)) != R1_IDLE_STATE) { if (((uint16_t)_millis() - t0) > SD_INIT_TIMEOUT) { + WRITE(MISO, 0); // disable the MISO line pullup error(SD_CARD_ERROR_CMD0); goto fail; } } + WRITE(MISO, 0); // disable the MISO line pullup // send 0xFF until 0xFF received to give card some clock cycles t0 = (uint16_t)_millis(); @@ -767,6 +769,9 @@ uint8_t Sd2Card::waitStartBlock(void) { // Toshiba FlashAir support, copied from // https://flashair-developers.com/en/documents/tutorials/arduino/ +// However, the official website was closed in September 2019. +// There is an archived website (written in Japanese). +// https://flashair-developers.github.io/website/docs/tutorials/arduino/2.html //------------------------------------------------------------------------------ /** Perform Extention Read. */ @@ -774,7 +779,7 @@ uint8_t Sd2Card::readExt(uint32_t arg, uint8_t* dst, uint16_t count) { uint16_t i; // send command and argument. - if (cardCommand(CMD48, arg)) { + if (cardCommand(CMD48, arg) && cardCommand(CMD17, arg)) { // CMD48 for W-03, CMD17 for W-04 error(SD_CARD_ERROR_CMD48); goto fail; } diff --git a/Firmware/Sd2Card.h b/Firmware/Sd2Card.h index f4cc59d76..ae78f6d7b 100644 --- a/Firmware/Sd2Card.h +++ b/Firmware/Sd2Card.h @@ -28,7 +28,6 @@ * \brief Sd2Card class for V2 SD/SDHC cards */ #include "SdFatConfig.h" -#include "Sd2PinMap.h" #include "SdInfo.h" //------------------------------------------------------------------------------ // SPI speed is F_CPU/2^(1 + index), 0 <= index <= 6 @@ -133,22 +132,7 @@ uint8_t const SD_CARD_TYPE_SDHC = 3; //------------------------------------------------------------------------------ // SPI pin definitions - do not edit here - change in SdFatConfig.h // -#ifndef SOFTWARE_SPI -// hardware pin defs -/** The default chip select pin for the SD card is SS. */ -uint8_t const SD_CHIP_SELECT_PIN = SS_PIN; -// The following three pins must not be redefined for hardware SPI. -/** SPI Master Out Slave In pin */ -uint8_t const SPI_MOSI_PIN = MOSI_PIN; -/** SPI Master In Slave Out pin */ -uint8_t const SPI_MISO_PIN = MISO_PIN; -/** SPI Clock pin */ -uint8_t const SPI_SCK_PIN = SCK_PIN; - -#else // SOFTWARE_SPI - -/** SPI chip select pin */ -uint8_t const SD_CHIP_SELECT_PIN = SOFT_SPI_CS_PIN; +#ifdef SOFTWARE_SPI /** SPI Master Out Slave In pin */ uint8_t const SPI_MOSI_PIN = SOFT_SPI_MOSI_PIN; /** SPI Master In Slave Out pin */ @@ -176,17 +160,16 @@ class Sd2Card { /** * \return error code for last error. See Sd2Card.h for a list of error codes. */ - int errorCode() const {return errorCode_;} + uint8_t errorCode() const {return errorCode_;} /** \return error data for last error. */ - int errorData() const {return status_;} + uint8_t errorData() const {return status_;} /** * Initialize an SD flash memory card with default clock rate and chip - * select pin. See sd2Card::init(uint8_t sckRateID, uint8_t chipSelectPin). + * select pin. See sd2Card::init(uint8_t sckRateID). * * \return true for success or false for failure. */ - bool init(uint8_t sckRateID = SPI_FULL_SPEED, - uint8_t chipSelectPin = SD_CHIP_SELECT_PIN); + bool init(uint8_t sckRateID = SPI_FULL_SPEED); bool readBlock(uint32_t block, uint8_t* dst); /** * Read a card's CID register. The CID contains card identification @@ -232,7 +215,6 @@ class Sd2Card { private: //---------------------------------------------------------------------------- - uint8_t chipSelectPin_; uint8_t errorCode_; uint8_t spiRate_; uint8_t status_; diff --git a/Firmware/Sd2PinMap.h b/Firmware/Sd2PinMap.h deleted file mode 100644 index 8a608731e..000000000 --- a/Firmware/Sd2PinMap.h +++ /dev/null @@ -1,368 +0,0 @@ -/* Arduino SdFat Library - * Copyright (C) 2010 by William Greiman - * - * This file is part of the Arduino SdFat Library - * - * This Library is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This Library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with the Arduino SdFat Library. If not, see - * . - */ -// Warning this file was generated by a program. -#include "Marlin.h" -#ifdef SDSUPPORT - -#ifndef Sd2PinMap_h -#define Sd2PinMap_h -#include -//------------------------------------------------------------------------------ -/** struct for mapping digital pins */ -struct pin_map_t { - volatile uint8_t* ddr; - volatile uint8_t* pin; - volatile uint8_t* port; - uint8_t bit; -}; -//------------------------------------------------------------------------------ -#if defined(__AVR_ATmega1280__)\ -|| defined(__AVR_ATmega2560__) -// Mega - -// Two Wire (aka I2C) ports -uint8_t const SDA_PIN = 20; // D1 -uint8_t const SCL_PIN = 21; // D0 - -#undef MOSI_PIN -#undef MISO_PIN -// SPI port -uint8_t const SS_PIN = 53; // B0 -uint8_t const MOSI_PIN = 51; // B2 -uint8_t const MISO_PIN = 50; // B3 -uint8_t const SCK_PIN = 52; // B1 - -static const pin_map_t digitalPinMap[] = { - {&DDRE, &PINE, &PORTE, 0}, // E0 0 - {&DDRE, &PINE, &PORTE, 1}, // E1 1 - {&DDRE, &PINE, &PORTE, 4}, // E4 2 - {&DDRE, &PINE, &PORTE, 5}, // E5 3 - {&DDRG, &PING, &PORTG, 5}, // G5 4 - {&DDRE, &PINE, &PORTE, 3}, // E3 5 - {&DDRH, &PINH, &PORTH, 3}, // H3 6 - {&DDRH, &PINH, &PORTH, 4}, // H4 7 - {&DDRH, &PINH, &PORTH, 5}, // H5 8 - {&DDRH, &PINH, &PORTH, 6}, // H6 9 - {&DDRB, &PINB, &PORTB, 4}, // B4 10 - {&DDRB, &PINB, &PORTB, 5}, // B5 11 - {&DDRB, &PINB, &PORTB, 6}, // B6 12 - {&DDRB, &PINB, &PORTB, 7}, // B7 13 - {&DDRJ, &PINJ, &PORTJ, 1}, // J1 14 - {&DDRJ, &PINJ, &PORTJ, 0}, // J0 15 - {&DDRH, &PINH, &PORTH, 1}, // H1 16 - {&DDRH, &PINH, &PORTH, 0}, // H0 17 - {&DDRD, &PIND, &PORTD, 3}, // D3 18 - {&DDRD, &PIND, &PORTD, 2}, // D2 19 - {&DDRD, &PIND, &PORTD, 1}, // D1 20 - {&DDRD, &PIND, &PORTD, 0}, // D0 21 - {&DDRA, &PINA, &PORTA, 0}, // A0 22 - {&DDRA, &PINA, &PORTA, 1}, // A1 23 - {&DDRA, &PINA, &PORTA, 2}, // A2 24 - {&DDRA, &PINA, &PORTA, 3}, // A3 25 - {&DDRA, &PINA, &PORTA, 4}, // A4 26 - {&DDRA, &PINA, &PORTA, 5}, // A5 27 - {&DDRA, &PINA, &PORTA, 6}, // A6 28 - {&DDRA, &PINA, &PORTA, 7}, // A7 29 - {&DDRC, &PINC, &PORTC, 7}, // C7 30 - {&DDRC, &PINC, &PORTC, 6}, // C6 31 - {&DDRC, &PINC, &PORTC, 5}, // C5 32 - {&DDRC, &PINC, &PORTC, 4}, // C4 33 - {&DDRC, &PINC, &PORTC, 3}, // C3 34 - {&DDRC, &PINC, &PORTC, 2}, // C2 35 - {&DDRC, &PINC, &PORTC, 1}, // C1 36 - {&DDRC, &PINC, &PORTC, 0}, // C0 37 - {&DDRD, &PIND, &PORTD, 7}, // D7 38 - {&DDRG, &PING, &PORTG, 2}, // G2 39 - {&DDRG, &PING, &PORTG, 1}, // G1 40 - {&DDRG, &PING, &PORTG, 0}, // G0 41 - {&DDRL, &PINL, &PORTL, 7}, // L7 42 - {&DDRL, &PINL, &PORTL, 6}, // L6 43 - {&DDRL, &PINL, &PORTL, 5}, // L5 44 - {&DDRL, &PINL, &PORTL, 4}, // L4 45 - {&DDRL, &PINL, &PORTL, 3}, // L3 46 - {&DDRL, &PINL, &PORTL, 2}, // L2 47 - {&DDRL, &PINL, &PORTL, 1}, // L1 48 - {&DDRL, &PINL, &PORTL, 0}, // L0 49 - {&DDRB, &PINB, &PORTB, 3}, // B3 50 - {&DDRB, &PINB, &PORTB, 2}, // B2 51 - {&DDRB, &PINB, &PORTB, 1}, // B1 52 - {&DDRB, &PINB, &PORTB, 0}, // B0 53 - {&DDRF, &PINF, &PORTF, 0}, // F0 54 - {&DDRF, &PINF, &PORTF, 1}, // F1 55 - {&DDRF, &PINF, &PORTF, 2}, // F2 56 - {&DDRF, &PINF, &PORTF, 3}, // F3 57 - {&DDRF, &PINF, &PORTF, 4}, // F4 58 - {&DDRF, &PINF, &PORTF, 5}, // F5 59 - {&DDRF, &PINF, &PORTF, 6}, // F6 60 - {&DDRF, &PINF, &PORTF, 7}, // F7 61 - {&DDRK, &PINK, &PORTK, 0}, // K0 62 - {&DDRK, &PINK, &PORTK, 1}, // K1 63 - {&DDRK, &PINK, &PORTK, 2}, // K2 64 - {&DDRK, &PINK, &PORTK, 3}, // K3 65 - {&DDRK, &PINK, &PORTK, 4}, // K4 66 - {&DDRK, &PINK, &PORTK, 5}, // K5 67 - {&DDRK, &PINK, &PORTK, 6}, // K6 68 - {&DDRK, &PINK, &PORTK, 7} // K7 69 -}; -//------------------------------------------------------------------------------ -#elif defined(__AVR_ATmega644P__)\ -|| defined(__AVR_ATmega644__)\ -|| defined(__AVR_ATmega1284P__) -// Sanguino - -// Two Wire (aka I2C) ports -uint8_t const SDA_PIN = 17; // C1 -uint8_t const SCL_PIN = 18; // C2 - -// SPI port -uint8_t const SS_PIN = 4; // B4 -uint8_t const MOSI_PIN = 5; // B5 -uint8_t const MISO_PIN = 6; // B6 -uint8_t const SCK_PIN = 7; // B7 - -static const pin_map_t digitalPinMap[] = { - {&DDRB, &PINB, &PORTB, 0}, // B0 0 - {&DDRB, &PINB, &PORTB, 1}, // B1 1 - {&DDRB, &PINB, &PORTB, 2}, // B2 2 - {&DDRB, &PINB, &PORTB, 3}, // B3 3 - {&DDRB, &PINB, &PORTB, 4}, // B4 4 - {&DDRB, &PINB, &PORTB, 5}, // B5 5 - {&DDRB, &PINB, &PORTB, 6}, // B6 6 - {&DDRB, &PINB, &PORTB, 7}, // B7 7 - {&DDRD, &PIND, &PORTD, 0}, // D0 8 - {&DDRD, &PIND, &PORTD, 1}, // D1 9 - {&DDRD, &PIND, &PORTD, 2}, // D2 10 - {&DDRD, &PIND, &PORTD, 3}, // D3 11 - {&DDRD, &PIND, &PORTD, 4}, // D4 12 - {&DDRD, &PIND, &PORTD, 5}, // D5 13 - {&DDRD, &PIND, &PORTD, 6}, // D6 14 - {&DDRD, &PIND, &PORTD, 7}, // D7 15 - {&DDRC, &PINC, &PORTC, 0}, // C0 16 - {&DDRC, &PINC, &PORTC, 1}, // C1 17 - {&DDRC, &PINC, &PORTC, 2}, // C2 18 - {&DDRC, &PINC, &PORTC, 3}, // C3 19 - {&DDRC, &PINC, &PORTC, 4}, // C4 20 - {&DDRC, &PINC, &PORTC, 5}, // C5 21 - {&DDRC, &PINC, &PORTC, 6}, // C6 22 - {&DDRC, &PINC, &PORTC, 7}, // C7 23 - {&DDRA, &PINA, &PORTA, 7}, // A7 24 - {&DDRA, &PINA, &PORTA, 6}, // A6 25 - {&DDRA, &PINA, &PORTA, 5}, // A5 26 - {&DDRA, &PINA, &PORTA, 4}, // A4 27 - {&DDRA, &PINA, &PORTA, 3}, // A3 28 - {&DDRA, &PINA, &PORTA, 2}, // A2 29 - {&DDRA, &PINA, &PORTA, 1}, // A1 30 - {&DDRA, &PINA, &PORTA, 0} // A0 31 -}; -//------------------------------------------------------------------------------ -#elif defined(__AVR_ATmega32U4__) -// Teensy 2.0 - -// Two Wire (aka I2C) ports -uint8_t const SDA_PIN = 6; // D1 -uint8_t const SCL_PIN = 5; // D0 - -// SPI port -uint8_t const SS_PIN = 0; // B0 -uint8_t const MOSI_PIN = 2; // B2 -uint8_t const MISO_PIN = 3; // B3 -uint8_t const SCK_PIN = 1; // B1 - -static const pin_map_t digitalPinMap[] = { - {&DDRB, &PINB, &PORTB, 0}, // B0 0 - {&DDRB, &PINB, &PORTB, 1}, // B1 1 - {&DDRB, &PINB, &PORTB, 2}, // B2 2 - {&DDRB, &PINB, &PORTB, 3}, // B3 3 - {&DDRB, &PINB, &PORTB, 7}, // B7 4 - {&DDRD, &PIND, &PORTD, 0}, // D0 5 - {&DDRD, &PIND, &PORTD, 1}, // D1 6 - {&DDRD, &PIND, &PORTD, 2}, // D2 7 - {&DDRD, &PIND, &PORTD, 3}, // D3 8 - {&DDRC, &PINC, &PORTC, 6}, // C6 9 - {&DDRC, &PINC, &PORTC, 7}, // C7 10 - {&DDRD, &PIND, &PORTD, 6}, // D6 11 - {&DDRD, &PIND, &PORTD, 7}, // D7 12 - {&DDRB, &PINB, &PORTB, 4}, // B4 13 - {&DDRB, &PINB, &PORTB, 5}, // B5 14 - {&DDRB, &PINB, &PORTB, 6}, // B6 15 - {&DDRF, &PINF, &PORTF, 7}, // F7 16 - {&DDRF, &PINF, &PORTF, 6}, // F6 17 - {&DDRF, &PINF, &PORTF, 5}, // F5 18 - {&DDRF, &PINF, &PORTF, 4}, // F4 19 - {&DDRF, &PINF, &PORTF, 1}, // F1 20 - {&DDRF, &PINF, &PORTF, 0}, // F0 21 - {&DDRD, &PIND, &PORTD, 4}, // D4 22 - {&DDRD, &PIND, &PORTD, 5}, // D5 23 - {&DDRE, &PINE, &PORTE, 6} // E6 24 -}; -//------------------------------------------------------------------------------ -#elif defined(__AVR_AT90USB646__)\ -|| defined(__AVR_AT90USB1286__) -// Teensy++ 1.0 & 2.0 - -// Two Wire (aka I2C) ports -uint8_t const SDA_PIN = 1; // D1 -uint8_t const SCL_PIN = 0; // D0 - -// SPI port -uint8_t const SS_PIN = 20; // B0 -uint8_t const MOSI_PIN = 22; // B2 -uint8_t const MISO_PIN = 23; // B3 -uint8_t const SCK_PIN = 21; // B1 - -static const pin_map_t digitalPinMap[] = { - {&DDRD, &PIND, &PORTD, 0}, // D0 0 - {&DDRD, &PIND, &PORTD, 1}, // D1 1 - {&DDRD, &PIND, &PORTD, 2}, // D2 2 - {&DDRD, &PIND, &PORTD, 3}, // D3 3 - {&DDRD, &PIND, &PORTD, 4}, // D4 4 - {&DDRD, &PIND, &PORTD, 5}, // D5 5 - {&DDRD, &PIND, &PORTD, 6}, // D6 6 - {&DDRD, &PIND, &PORTD, 7}, // D7 7 - {&DDRE, &PINE, &PORTE, 0}, // E0 8 - {&DDRE, &PINE, &PORTE, 1}, // E1 9 - {&DDRC, &PINC, &PORTC, 0}, // C0 10 - {&DDRC, &PINC, &PORTC, 1}, // C1 11 - {&DDRC, &PINC, &PORTC, 2}, // C2 12 - {&DDRC, &PINC, &PORTC, 3}, // C3 13 - {&DDRC, &PINC, &PORTC, 4}, // C4 14 - {&DDRC, &PINC, &PORTC, 5}, // C5 15 - {&DDRC, &PINC, &PORTC, 6}, // C6 16 - {&DDRC, &PINC, &PORTC, 7}, // C7 17 - {&DDRE, &PINE, &PORTE, 6}, // E6 18 - {&DDRE, &PINE, &PORTE, 7}, // E7 19 - {&DDRB, &PINB, &PORTB, 0}, // B0 20 - {&DDRB, &PINB, &PORTB, 1}, // B1 21 - {&DDRB, &PINB, &PORTB, 2}, // B2 22 - {&DDRB, &PINB, &PORTB, 3}, // B3 23 - {&DDRB, &PINB, &PORTB, 4}, // B4 24 - {&DDRB, &PINB, &PORTB, 5}, // B5 25 - {&DDRB, &PINB, &PORTB, 6}, // B6 26 - {&DDRB, &PINB, &PORTB, 7}, // B7 27 - {&DDRA, &PINA, &PORTA, 0}, // A0 28 - {&DDRA, &PINA, &PORTA, 1}, // A1 29 - {&DDRA, &PINA, &PORTA, 2}, // A2 30 - {&DDRA, &PINA, &PORTA, 3}, // A3 31 - {&DDRA, &PINA, &PORTA, 4}, // A4 32 - {&DDRA, &PINA, &PORTA, 5}, // A5 33 - {&DDRA, &PINA, &PORTA, 6}, // A6 34 - {&DDRA, &PINA, &PORTA, 7}, // A7 35 - {&DDRE, &PINE, &PORTE, 4}, // E4 36 - {&DDRE, &PINE, &PORTE, 5}, // E5 37 - {&DDRF, &PINF, &PORTF, 0}, // F0 38 - {&DDRF, &PINF, &PORTF, 1}, // F1 39 - {&DDRF, &PINF, &PORTF, 2}, // F2 40 - {&DDRF, &PINF, &PORTF, 3}, // F3 41 - {&DDRF, &PINF, &PORTF, 4}, // F4 42 - {&DDRF, &PINF, &PORTF, 5}, // F5 43 - {&DDRF, &PINF, &PORTF, 6}, // F6 44 - {&DDRF, &PINF, &PORTF, 7} // F7 45 -}; -//------------------------------------------------------------------------------ -#elif defined(__AVR_ATmega168__)\ -||defined(__AVR_ATmega168P__)\ -||defined(__AVR_ATmega328P__) -// 168 and 328 Arduinos - -// Two Wire (aka I2C) ports -uint8_t const SDA_PIN = 18; // C4 -uint8_t const SCL_PIN = 19; // C5 - -// SPI port -uint8_t const SS_PIN = 10; // B2 -uint8_t const MOSI_PIN = 11; // B3 -uint8_t const MISO_PIN = 12; // B4 -uint8_t const SCK_PIN = 13; // B5 - -static const pin_map_t digitalPinMap[] = { - {&DDRD, &PIND, &PORTD, 0}, // D0 0 - {&DDRD, &PIND, &PORTD, 1}, // D1 1 - {&DDRD, &PIND, &PORTD, 2}, // D2 2 - {&DDRD, &PIND, &PORTD, 3}, // D3 3 - {&DDRD, &PIND, &PORTD, 4}, // D4 4 - {&DDRD, &PIND, &PORTD, 5}, // D5 5 - {&DDRD, &PIND, &PORTD, 6}, // D6 6 - {&DDRD, &PIND, &PORTD, 7}, // D7 7 - {&DDRB, &PINB, &PORTB, 0}, // B0 8 - {&DDRB, &PINB, &PORTB, 1}, // B1 9 - {&DDRB, &PINB, &PORTB, 2}, // B2 10 - {&DDRB, &PINB, &PORTB, 3}, // B3 11 - {&DDRB, &PINB, &PORTB, 4}, // B4 12 - {&DDRB, &PINB, &PORTB, 5}, // B5 13 - {&DDRC, &PINC, &PORTC, 0}, // C0 14 - {&DDRC, &PINC, &PORTC, 1}, // C1 15 - {&DDRC, &PINC, &PORTC, 2}, // C2 16 - {&DDRC, &PINC, &PORTC, 3}, // C3 17 - {&DDRC, &PINC, &PORTC, 4}, // C4 18 - {&DDRC, &PINC, &PORTC, 5} // C5 19 -}; -#else // defined(__AVR_ATmega1280__) -#error unknown chip -#endif // defined(__AVR_ATmega1280__) -//------------------------------------------------------------------------------ -static const uint8_t digitalPinCount = sizeof(digitalPinMap)/sizeof(pin_map_t); - -uint8_t badPinNumber(void) - __attribute__((error("Pin number is too large or not a constant"))); - -static inline __attribute__((always_inline)) - bool getPinMode(uint8_t pin) { - if (__builtin_constant_p(pin) && pin < digitalPinCount) { - return (*digitalPinMap[pin].ddr >> digitalPinMap[pin].bit) & 1; - } else { - return badPinNumber(); - } -} -static inline __attribute__((always_inline)) - void setPinMode(uint8_t pin, uint8_t mode) { - if (__builtin_constant_p(pin) && pin < digitalPinCount) { - if (mode) { - *digitalPinMap[pin].ddr |= 1 << digitalPinMap[pin].bit; - } else { - *digitalPinMap[pin].ddr &= ~(1 << digitalPinMap[pin].bit); - } - } else { - badPinNumber(); - } -} -static inline __attribute__((always_inline)) - bool fastDigitalRead(uint8_t pin) { - if (__builtin_constant_p(pin) && pin < digitalPinCount) { - return (*digitalPinMap[pin].pin >> digitalPinMap[pin].bit) & 1; - } else { - return badPinNumber(); - } -} -static inline __attribute__((always_inline)) - void fastDigitalWrite(uint8_t pin, uint8_t value) { - if (__builtin_constant_p(pin) && pin < digitalPinCount) { - if (value) { - *digitalPinMap[pin].port |= 1 << digitalPinMap[pin].bit; - } else { - *digitalPinMap[pin].port &= ~(1 << digitalPinMap[pin].bit); - } - } else { - badPinNumber(); - } -} -#endif // Sd2PinMap_h - - -#endif \ No newline at end of file diff --git a/Firmware/SdBaseFile.cpp b/Firmware/SdBaseFile.cpp index b9e881ef2..08b55651a 100644 --- a/Firmware/SdBaseFile.cpp +++ b/Firmware/SdBaseFile.cpp @@ -530,9 +530,9 @@ bool SdBaseFile::mkdir(SdBaseFile* parent, const uint8_t dname[11]) { * \return The value one, true, is returned for success and * the value zero, false, is returned for failure. */ - bool SdBaseFile::open(const char* path, uint8_t oflag) { - return open(cwd_, path, oflag); - } +bool SdBaseFile::open(const char* path, uint8_t oflag) { + return open(cwd_, path, oflag); +} //------------------------------------------------------------------------------ /** Open a file or directory by name. * @@ -1015,7 +1015,7 @@ void SdBaseFile::printFatTime( uint16_t fatTime) { * the value zero, false, is returned for failure. */ bool SdBaseFile::printName() { - char name[13]; + char name[FILENAME_LENGTH]; if (!getFilename(name)) return false; MYSERIAL.print(name); return true; diff --git a/Firmware/SdBaseFile.h b/Firmware/SdBaseFile.h index 923a391dd..a6bd311fe 100644 --- a/Firmware/SdBaseFile.h +++ b/Firmware/SdBaseFile.h @@ -281,8 +281,10 @@ class SdBaseFile { static void printFatDate(uint16_t fatDate); static void printFatTime( uint16_t fatTime); bool printName(); +protected: int16_t read(); int16_t read(void* buf, uint16_t nbyte); +public: int8_t readDir(dir_t* dir, char* longFilename); static bool remove(SdBaseFile* dirFile, const char* path); bool remove(); @@ -321,7 +323,7 @@ class SdBaseFile { SdVolume* volume() const {return vol_;} int16_t write(const void* buf, uint16_t nbyte); //------------------------------------------------------------------------------ - private: + protected: // allow SdFat to set cwd_ friend class SdFat; // global pointer to cwd dir diff --git a/Firmware/SdFatUtil.cpp b/Firmware/SdFatUtil.cpp index 51da4ee2b..50206ab94 100644 --- a/Firmware/SdFatUtil.cpp +++ b/Firmware/SdFatUtil.cpp @@ -48,24 +48,16 @@ void SdFatUtil::set_stack_guard() { uint32_t *stack_guard; - stack_guard = (uint32_t*)&__bss_end; + stack_guard = (uint32_t*)(&__bss_end + STACK_GUARD_MARGIN); *stack_guard = STACK_GUARD_TEST_VALUE; } bool SdFatUtil::test_stack_integrity() { - uint32_t* stack_guard = (uint32_t*)&__bss_end; + uint32_t* stack_guard = (uint32_t*)(&__bss_end + STACK_GUARD_MARGIN); return (*stack_guard == STACK_GUARD_TEST_VALUE); } -uint32_t SdFatUtil::get_stack_guard_test_value() -{ - uint32_t* stack_guard; - uint32_t output; - stack_guard = (uint32_t*)&__bss_end; - output = *stack_guard; - return(output); -} //------------------------------------------------------------------------------ /** %Print a string in flash memory. * diff --git a/Firmware/SdFatUtil.h b/Firmware/SdFatUtil.h index c42b74b1c..2a70d98a1 100644 --- a/Firmware/SdFatUtil.h +++ b/Firmware/SdFatUtil.h @@ -41,11 +41,10 @@ namespace SdFatUtil { void SerialPrintln_P(PGM_P str); void set_stack_guard(); bool test_stack_integrity(); - uint32_t get_stack_guard_test_value(); } using namespace SdFatUtil; // NOLINT #endif // #define SdFatUtil_h -#endif \ No newline at end of file +#endif diff --git a/Firmware/SdFile.cpp b/Firmware/SdFile.cpp index 2fb4d5943..e21144ee1 100644 --- a/Firmware/SdFile.cpp +++ b/Firmware/SdFile.cpp @@ -30,6 +30,194 @@ */ SdFile::SdFile(const char* path, uint8_t oflag) : SdBaseFile(path, oflag) { } + +bool SdFile::openFilteredGcode(SdBaseFile* dirFile, const char* path){ + if( open(dirFile, path, O_READ) ){ + // compute the block to start with + if( ! gfComputeNextFileBlock() ) + return false; + gfReset(); + return true; + } else { + return false; + } +} + +bool SdFile::seekSetFilteredGcode(uint32_t pos){ + if(! seekSet(pos) )return false; + if(! gfComputeNextFileBlock() )return false; + gfReset(); + return true; +} + +const uint8_t *SdFile::gfBlockBuffBegin() const { + return vol_->cache()->data; // this is constant for the whole time, so it should be fast and sleek +} + +void SdFile::gfReset(){ + // reset cache read ptr to its begin + gfReadPtr = gfBlockBuffBegin() + gfOffset; +} + +// think twice before allowing this to inline - manipulating 4B longs is costly +// moreover - this function has its parameters in registers only, so no heavy stack usage besides the call/ret +void __attribute__((noinline)) SdFile::gfUpdateCurrentPosition(uint16_t inc){ + curPosition_ += inc; +} + +#define find_endl(resultP, startP) \ +__asm__ __volatile__ ( \ +"cycle: \n" \ +"ld r22, Z+ \n" \ +"cpi r22, 0x0A \n" \ +"brne cycle \n" \ +: "=z" (resultP) /* result of the ASM code - in our case the Z register (R30:R31) */ \ +: "z" (startP) /* input of the ASM code - in our case the Z register as well (R30:R31) */ \ +: "r22" /* modifying register R22 - so that the compiler knows */ \ +) + +// avoid calling the default heavy-weight read() for just one byte +int16_t SdFile::readFilteredGcode(){ + if( ! gfEnsureBlock() ){ + goto eof_or_fail; // this is unfortunate :( ... other calls are using the cache and we can loose the data block of our gcode file + } + // assume, we have the 512B block cache filled and terminated with a '\n' + { + const uint8_t *start = gfReadPtr; + + // It may seem unreasonable to copy the variable into a local one and copy it back at the end of this method, + // but there is an important point of view: the compiler is unsure whether it can optimize the reads/writes + // to gfReadPtr within this method, because it is a class member variable. + // The compiler cannot see, if omitting read/write won't have any incorrect side-effects to the rest of the whole FW. + // So this trick explicitly states, that rdPtr is a local variable limited to the scope of this method, + // therefore the compiler can omit read/write to it (keep it in registers!) as it sees fit. + // And it does! Codesize dropped by 68B! + const uint8_t *rdPtr = gfReadPtr; + + // the same applies to gfXBegin, codesize dropped another 100B! + const uint8_t *blockBuffBegin = gfBlockBuffBegin(); + + uint8_t consecutiveCommentLines = 0; + while( *rdPtr == ';' ){ + for(;;){ + + //while( *(++gfReadPtr) != '\n' ); // skip until a newline is found - suboptimal code! + // Wondering, why this "nice while cycle" is done in such a weird way using a separate find_endl() function? + // Have a look at the ASM code GCC produced! + + // At first - a separate find_endl() makes the compiler understand, + // that I don't need to store gfReadPtr every time, I'm only interested in the final address where the '\n' was found + // - the cycle can run on CPU registers only without touching memory besides reading the character being compared. + // Not only makes the code run considerably faster, but is also 40B shorter! + // This was the generated code: + //FORCE_INLINE const uint8_t * find_endl(const uint8_t *p){ + // while( *(++p) != '\n' ); // skip until a newline is found + // return p; } + // 11c5e: movw r30, r18 + // 11c60: subi r18, 0xFF ; 255 + // 11c62: sbci r19, 0xFF ; 255 + // 11c64: ld r22, Z + // 11c66: cpi r22, 0x0A ; 10 + // 11c68: brne .-12 ; 0x11c5e + + // Still, even that was suboptimal as the compiler seems not to understand the usage of ld r22, Z+ (the plus is important) + // aka automatic increment of the Z register (R30:R31 pair) + // There is no other way than pure ASM! + find_endl(rdPtr, rdPtr); + + // found a newline, prepare the next block if block cache end reached + if( rdPtr - blockBuffBegin > 512 ){ + // at the end of block cache, fill new data in + gfUpdateCurrentPosition( rdPtr - start - 1 ); + if( ! gfComputeNextFileBlock() )goto eof_or_fail; + if( ! gfEnsureBlock() )goto eof_or_fail; // fetch it into RAM + rdPtr = start = blockBuffBegin; + } else { + if(consecutiveCommentLines >= 250){ + --rdPtr; // unget the already consumed newline + goto emit_char; + } + // peek the next byte - we are inside the block at least at 511th index - still safe + if( *rdPtr == ';' ){ + // consecutive comment + ++consecutiveCommentLines; + } else { + --rdPtr; // unget the already consumed newline + goto emit_char; + } + break; // found the real end of the line even across many blocks + } + } + } +emit_char: + { + gfUpdateCurrentPosition( rdPtr - start + 1 ); + int16_t rv = *rdPtr++; + + if( curPosition_ >= fileSize_ ){ + // past the end of file + goto eof_or_fail; + } else if( rdPtr - blockBuffBegin >= 512 ){ + // past the end of current bufferred block - prepare the next one... + if( ! gfComputeNextFileBlock() )goto eof_or_fail; + // don't need to force fetch the block here, it will get loaded on the next call + rdPtr = blockBuffBegin; + } + + // save the current read ptr for the next run + gfReadPtr = rdPtr; + return rv; + } + +} + +eof_or_fail: + // make the rdptr point to a safe location - end of file + gfReadPtr = gfBlockBuffBegin() + 512; + return -1; +} + +bool SdFile::gfEnsureBlock(){ + // this comparison is heavy-weight, especially when there is another one inside cacheRawBlock + // but it is necessary to avoid computing of terminateOfs if not needed + if( gfBlock != vol_->cacheBlockNumber_ ){ + if ( ! vol_->cacheRawBlock(gfBlock, SdVolume::CACHE_FOR_READ)){ + return false; + } + // terminate with a '\n' + const uint32_t terminateOfs = fileSize_ - gfOffset; + vol_->cache()->data[ terminateOfs < 512 ? terminateOfs : 512 ] = '\n'; + } + return true; +} + +bool SdFile::gfComputeNextFileBlock() { + // error if not open or write only + if (!isOpen() || !(flags_ & O_READ)) return false; + + gfOffset = curPosition_ & 0X1FF; // offset in block + if (type_ == FAT_FILE_TYPE_ROOT_FIXED) { + // SHR by 9 means skip the last byte and shift just 3 bytes by 1 + // -> should be 8 instructions... and not the horrible loop shifting 4 bytes at once + // still need to get some work on this + gfBlock = vol_->rootDirStart() + (curPosition_ >> 9); + } else { + uint8_t blockOfCluster = vol_->blockOfCluster(curPosition_); + if (gfOffset == 0 && blockOfCluster == 0) { + // start of new cluster + if (curPosition_ == 0) { + // use first cluster in file + curCluster_ = firstCluster_; + } else { + // get next cluster from FAT + if (!vol_->fatGet(curCluster_, &curCluster_)) return false; + } + } + gfBlock = vol_->clusterStartBlock(curCluster_) + blockOfCluster; + } + return true; +} + //------------------------------------------------------------------------------ /** Write data to an open file. * diff --git a/Firmware/SdFile.h b/Firmware/SdFile.h index 60e2f5deb..465224623 100644 --- a/Firmware/SdFile.h +++ b/Firmware/SdFile.h @@ -34,7 +34,24 @@ * \brief SdBaseFile with Print. */ class SdFile : public SdBaseFile/*, public Print*/ { - public: + // GCode filtering vars and methods - due to optimization reasons not wrapped in a separate class + + // beware - this read ptr is manipulated inside just 2 methods - readFilteredGcode and gfReset + // If you even want to call gfReset from readFilteredGcode, you must make sure + // to update gfReadPtr inside readFilteredGcode from a local copy (see explanation of this trick in readFilteredGcode) + const uint8_t *gfReadPtr; + + uint32_t gfBlock; // remember the current file block to be kept in cache - due to reuse of the memory, the block may fall out a must be read back + uint16_t gfOffset; + + const uint8_t *gfBlockBuffBegin()const; + + void gfReset(); + + bool gfEnsureBlock(); + bool gfComputeNextFileBlock(); + void gfUpdateCurrentPosition(uint16_t inc); +public: SdFile() {} SdFile(const char* name, uint8_t oflag); #if ARDUINO >= 100 @@ -43,6 +60,9 @@ class SdFile : public SdBaseFile/*, public Print*/ { void write(uint8_t b); #endif + bool openFilteredGcode(SdBaseFile* dirFile, const char* path); + int16_t readFilteredGcode(); + bool seekSetFilteredGcode(uint32_t pos); int16_t write(const void* buf, uint16_t nbyte); void write(const char* str); void write_P(PGM_P str); @@ -51,4 +71,4 @@ class SdFile : public SdBaseFile/*, public Print*/ { #endif // SdFile_h -#endif \ No newline at end of file +#endif diff --git a/Firmware/SdVolume.h b/Firmware/SdVolume.h index 2ff2b6eb9..17699190e 100644 --- a/Firmware/SdVolume.h +++ b/Firmware/SdVolume.h @@ -36,7 +36,7 @@ */ union cache_t { /** Used to access cached file data blocks. */ - uint8_t data[512]; + uint8_t data[512 + 1]; // abuse the last byte for saving '\n' - ugly optimization of read_filtered's inner skipping loop /** Used to access cached FAT16 entries. */ uint16_t fat16[256]; /** Used to access cached FAT32 entries. */ @@ -119,6 +119,7 @@ class SdVolume { bool dbgFat(uint32_t n, uint32_t* v) {return fatGet(n, v);} //------------------------------------------------------------------------------ private: + friend class SdFile; // Allow SdBaseFile access to SdVolume private data. friend class SdBaseFile; @@ -211,4 +212,4 @@ class SdVolume { #endif // ALLOW_DEPRECATED_FUNCTIONS }; #endif // SdVolume -#endif \ No newline at end of file +#endif diff --git a/Firmware/Servo.cpp b/Firmware/Servo.cpp index 5f8c7efe3..bcb4b074f 100644 --- a/Firmware/Servo.cpp +++ b/Firmware/Servo.cpp @@ -231,7 +231,7 @@ static void finISR(timer16_Sequence_t timer) #endif } -static boolean isTimerActive(timer16_Sequence_t timer) +static bool isTimerActive(timer16_Sequence_t timer) { // returns true if any servo is active on this timer for(uint8_t channel=0; channel < SERVOS_PER_TIMER; channel++) { diff --git a/Firmware/SpoolJoin.cpp b/Firmware/SpoolJoin.cpp new file mode 100644 index 000000000..3944af9e3 --- /dev/null +++ b/Firmware/SpoolJoin.cpp @@ -0,0 +1,67 @@ +#include "SpoolJoin.h" +#include "Marlin.h" +#include "eeprom.h" +#include "messages.h" +#include "language.h" + +namespace SpoolJoin { + +SpoolJoin spooljoin; + +SpoolJoin::SpoolJoin() + : currentMMUSlot(0) +{ +} + +void SpoolJoin::initSpoolJoinStatus() +{ + // Useful information to see during bootup + SERIAL_ECHOPGM("SpoolJoin is "); + uint8_t status = eeprom_init_default_byte((uint8_t*)EEPROM_SPOOL_JOIN, (uint8_t)EEPROM::Disabled); + if (status == (uint8_t)EEPROM::Enabled) + { + SERIAL_ECHOLNRPGM(_O(MSG_ON)); + } else { + SERIAL_ECHOLNRPGM(_O(MSG_OFF)); + } +} + +void SpoolJoin::toggleSpoolJoin() +{ + if (eeprom_read_byte((uint8_t*)EEPROM_SPOOL_JOIN) == (uint8_t)EEPROM::Disabled) + { + eeprom_write_byte((uint8_t*)EEPROM_SPOOL_JOIN, (uint8_t)EEPROM::Enabled); + } else { + eeprom_write_byte((uint8_t*)EEPROM_SPOOL_JOIN, (uint8_t)EEPROM::Disabled); + } +} + +bool SpoolJoin::isSpoolJoinEnabled() +{ + if(eeprom_read_byte((uint8_t*)EEPROM_SPOOL_JOIN) == (uint8_t)EEPROM::Enabled) { + return true; + } else { + return false; + } +} + +void SpoolJoin::setSlot(uint8_t slot) +{ + currentMMUSlot = slot; +} + +uint8_t SpoolJoin::nextSlot() +{ + SERIAL_ECHOPGM("SpoolJoin: "); + SERIAL_ECHO((int)currentMMUSlot); + + if (currentMMUSlot >= 4) currentMMUSlot = 0; + else currentMMUSlot++; + + SERIAL_ECHOPGM(" -> "); + SERIAL_ECHOLN((int)currentMMUSlot); + + return currentMMUSlot; +} + +} diff --git a/Firmware/SpoolJoin.h b/Firmware/SpoolJoin.h new file mode 100644 index 000000000..51ae5f947 --- /dev/null +++ b/Firmware/SpoolJoin.h @@ -0,0 +1,47 @@ +/// @file +#pragma once +#include +#include "eeprom.h" + +// See documentation here: https://help.prusa3d.com/article/spooljoin-mmu2s_134252 + +namespace SpoolJoin { + +class SpoolJoin { +public: + SpoolJoin(); + + enum class EEPROM : uint8_t { + Unknown, ///< SpoolJoin is unknown while printer is booting up + Enabled, ///< SpoolJoin is enabled in EEPROM + Disabled, ///< SpoolJoin is disabled in EEPROM + Empty = 0xFF ///< EEPROM has not been set before and all bits are 1 (0xFF) - either a new printer or user erased the memory + }; + + /// @brief Called when EEPROM is ready to be read + void initSpoolJoinStatus(); + + /// @brief Toggle SpoolJoin + static void toggleSpoolJoin(); + + /// @brief Check if SpoolJoin is enabled + /// @returns true if enabled, false if disabled + bool isSpoolJoinEnabled(); + + /// @brief Update the saved MMU slot number so SpoolJoin can determine the next slot to use + /// @param slot number of the slot to set + void setSlot(uint8_t slot); + + /// @brief Fetch the next slot number should count from 0 to 4. + /// When filament slot 4 is depleted, the next slot should be 0. + /// @returns the next slot, ranges from 0 to 4 + uint8_t nextSlot(); + +private: + /// @brief Currently used slot, ranges from 0 to 4 + uint8_t currentMMUSlot; +}; + +extern SpoolJoin spooljoin; + +} // namespace SpoolJoin diff --git a/Firmware/Tcodes.cpp b/Firmware/Tcodes.cpp new file mode 100644 index 000000000..30ea49c7b --- /dev/null +++ b/Firmware/Tcodes.cpp @@ -0,0 +1,105 @@ +#include "Tcodes.h" +#include "SpoolJoin.h" +#include "Marlin.h" +#include "language.h" +#include "messages.h" +#include "mmu2.h" +#include "stepper.h" +#include "ultralcd.h" +#include +#include +#include +#include + +static const char duplicate_Tcode_ignored[] PROGMEM = "Duplicate T-code ignored."; + +inline bool IsInvalidTCode(char *const s, uint8_t i) { + return ((s[i] < '0' || s[i] > '4') && s[i] != '?' && s[i] != 'x' && s[i] != 'c'); +} + +inline void TCodeInvalid() { + SERIAL_ECHOLNPGM("Invalid T code."); +} + +struct SChooseFromMenu { + uint8_t slot:7; + uint8_t loadToNozzle:1; + inline constexpr SChooseFromMenu(uint8_t slot, bool loadToNozzle):slot(slot), loadToNozzle(loadToNozzle){} + inline constexpr SChooseFromMenu():slot(0), loadToNozzle(false) { } +}; + +SChooseFromMenu TCodeChooseFromMenu() { + if (MMU2::mmu2.Enabled()) { + return SChooseFromMenu( choose_menu_P(_T(MSG_SELECT_FILAMENT), _T(MSG_FILAMENT)), true ); + } else { + return SChooseFromMenu( choose_menu_P(_T(MSG_SELECT_EXTRUDER), _T(MSG_EXTRUDER)), false ); + } +} + +void TCodes(char *const strchr_pointer, uint8_t codeValue) { + uint8_t index = 1; + for ( /*nothing*/ ; strchr_pointer[index] == ' ' || strchr_pointer[index] == '\t'; index++) + ; + + strchr_pointer[index] = tolower(strchr_pointer[index]); + + if (IsInvalidTCode(strchr_pointer, index)){ + TCodeInvalid(); + } else if (strchr_pointer[index] == 'x'){ + // load to extruder gears; if mmu is not present do nothing + if (MMU2::mmu2.Enabled()) { + MMU2::mmu2.tool_change(strchr_pointer[index], choose_menu_P(_T(MSG_SELECT_EXTRUDER), _T(MSG_EXTRUDER))); + } + } else if (strchr_pointer[index] == 'c'){ + // load from extruder gears to nozzle (nozzle should be preheated) + if (MMU2::mmu2.Enabled()) { + MMU2::mmu2.tool_change(strchr_pointer[index], MMU2::mmu2.get_current_tool()); + } + } else { + SChooseFromMenu selectedSlot; + if (strchr_pointer[index] == '?') { + selectedSlot = TCodeChooseFromMenu(); + /*} else if (MMU2::mmu2.Enabled() && SpoolJoin::spooljoin.isSpoolJoinEnabled()) { + // TODO: What if the next slot has no filament? + selectedSlot.slot = SpoolJoin::spooljoin.nextSlot();*/ + } else { + selectedSlot.slot = codeValue; + } + st_synchronize(); + + if (MMU2::mmu2.Enabled()) { + if (selectedSlot.slot == MMU2::mmu2.get_current_tool()){ + // don't execute the same T-code twice in a row + puts_P(duplicate_Tcode_ignored); + } else { +#if defined(MMU_HAS_CUTTER) && defined(MMU_ALWAYS_CUT) + if (EEPROM_MMU_CUTTER_ENABLED_always == eeprom_read_byte((uint8_t *)EEPROM_MMU_CUTTER_ENABLED)) { + MMU2::mmu2.cut_filament(selectedSlot.slot); + } +#endif // defined(MMU_HAS_CUTTER) && defined(MMU_ALWAYS_CUT) + if (selectedSlot.loadToNozzle){ // for single material usage with mmu + MMU2::mmu2.load_filament_to_nozzle(selectedSlot.slot); + } else { + MMU2::mmu2.tool_change(selectedSlot.slot); + } + } + } else { + if (selectedSlot.slot >= EXTRUDERS) { + SERIAL_ECHO_START; + SERIAL_ECHO('T'); + SERIAL_ECHOLN(selectedSlot.slot + '0'); + SERIAL_ECHOLNRPGM(_n("Invalid extruder")); ////MSG_INVALID_EXTRUDER + } else { +// @@TODO if (code_seen('F')) { +// next_feedrate = code_value(); +// if (next_feedrate > 0.0) { +// feedrate = next_feedrate; +// } +// } + SERIAL_ECHO_START; + SERIAL_ECHORPGM(_n("Active Extruder: ")); ////MSG_ACTIVE_EXTRUDER + SERIAL_ECHOLN(active_extruder + '0'); // this is not changed in our FW at all, can be optimized away + } + } + } +} diff --git a/Firmware/Tcodes.h b/Firmware/Tcodes.h new file mode 100644 index 000000000..684475780 --- /dev/null +++ b/Firmware/Tcodes.h @@ -0,0 +1,5 @@ +/// @file +#pragma once +#include + +void TCodes(char * const strchr_pointer, uint8_t codeValue); diff --git a/Firmware/Timer.cpp b/Firmware/Timer.cpp index a94a8586a..d0a552b0a 100644 --- a/Firmware/Timer.cpp +++ b/Firmware/Timer.cpp @@ -64,5 +64,24 @@ bool Timer::expired(T msPeriod) return expired; } +/** + * @brief Ticks since the timer was started + * + * This function returns 0 if the timer is not started. Otherwise, it returns + * the time in milliseconds since the timer was started. + * This function is expected to handle wrap around of time register well. + * The maximum elapsed time is dictated by the template type + */ +template +T Timer::elapsed() { + return m_isRunning ? (_millis() - m_started) : 0; +} + +template +bool Timer::expired_cont(T msPeriod) +{ + return !m_isRunning || expired(msPeriod); +} + template class Timer; template class Timer; diff --git a/Firmware/Timer.h b/Firmware/Timer.h index e2e84eff9..9cb18a304 100644 --- a/Firmware/Timer.h +++ b/Firmware/Timer.h @@ -20,10 +20,12 @@ public: Timer(); void start(); void stop(){m_isRunning = false;} - bool running(){return m_isRunning;} - bool expired(T msPeriod); + bool running()const {return m_isRunning;} + bool expired(T msPeriod); // returns true only once after expiration, then stops running + T elapsed(); // returns the time in milliseconds since the timer was started or 0 otherwise + bool expired_cont(T msPeriod); // return true when continuosly when expired / not running protected: - T started(){return m_started;} + T started()const {return m_started;} private: bool m_isRunning; T m_started; diff --git a/Firmware/adc.c b/Firmware/adc.c deleted file mode 100644 index 3144990da..000000000 --- a/Firmware/adc.c +++ /dev/null @@ -1,95 +0,0 @@ -//adc.c - -#include "adc.h" -#include -#include -#include -#include "pins.h" - -uint8_t adc_state; -uint8_t adc_count; -uint16_t adc_values[ADC_CHAN_CNT]; -uint16_t adc_sim_mask; - - -#ifdef ADC_CALLBACK - extern void ADC_CALLBACK(void); -#endif //ADC_CALLBACK - - -void adc_init(void) -{ - printf_P(PSTR("adc_init\n")); - adc_sim_mask = 0x00; - ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); - ADMUX |= (1 << REFS0); - ADCSRA |= (1 << ADEN); -// ADCSRA |= (1 << ADIF) | (1 << ADSC); - DIDR0 = ((ADC_CHAN_MSK & ADC_DIDR_MSK) & 0xff); - DIDR2 = ((ADC_CHAN_MSK & ADC_DIDR_MSK) >> 8); - adc_reset(); -// adc_sim_mask = 0b0101; -// adc_sim_mask = 0b100101; -// adc_values[0] = 1023 * 16; -// adc_values[2] = 1023 * 16; -// adc_values[5] = 1002 * 16; -} - -void adc_reset(void) -{ - adc_state = 0; - adc_count = 0; - uint8_t i; for (i = 0; i < ADC_CHAN_CNT; i++) - if ((adc_sim_mask & (1 << i)) == 0) - adc_values[i] = 0; -} - -void adc_setmux(uint8_t ch) -{ - ch &= 0x0f; - if (ch & 0x08) ADCSRB |= (1 << MUX5); - else ADCSRB &= ~(1 << MUX5); - ADMUX = (ADMUX & ~(0x07)) | (ch & 0x07); -} - -uint8_t adc_chan(uint8_t index) -{ - uint8_t chan = 0; - uint16_t mask = 1; - while (mask) - { - if ((mask & ADC_CHAN_MSK) && (index-- == 0)) break; - mask <<= 1; - chan++; - } - return chan; -} - -void adc_cycle(void) -{ - if (adc_state & 0x80) - { - uint8_t index = adc_state & 0x0f; - if ((adc_sim_mask & (1 << index)) == 0) - adc_values[index] += ADC; - if (++index >= ADC_CHAN_CNT) - { - index = 0; - adc_count++; - if (adc_count >= ADC_OVRSAMPL) - { -#ifdef ADC_CALLBACK - ADC_CALLBACK(); -#endif //ADC_CALLBACK - adc_reset(); - } - } - adc_setmux(adc_chan(index)); - adc_state = index; - } - else - { - ADCSRA |= (1 << ADSC); //start conversion - adc_state |= 0x80; - } -} diff --git a/Firmware/adc.cpp b/Firmware/adc.cpp new file mode 100644 index 000000000..7503fdcba --- /dev/null +++ b/Firmware/adc.cpp @@ -0,0 +1,81 @@ +#include "adc.h" +#include +#include +#include +#include +#include +#include "pins.h" + +static uint8_t adc_count; //used for oversampling +static uint8_t adc_channel_idx; //bitmask index +volatile uint8_t adc_channel; //regular index +volatile uint16_t adc_values[ADC_CHAN_CNT]; + +static void adc_reset(); +static void adc_setmux(uint8_t ch); + +void adc_init() +{ + puts_P(PSTR("adc_init")); + DIDR0 = ((ADC_CHAN_MSK & ADC_DIDR_MSK) & 0xff); //disable digital inputs PORTF + DIDR2 = ((ADC_CHAN_MSK & ADC_DIDR_MSK) >> 8); //disable digital inputs PORTK + ADMUX |= (1 << REFS0); //use AVCC as reference + + //enable ADC, set prescaler/128, enable interrupt + ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0) | (1 << ADIF) | (1 << ADIE); +} + +static void adc_reset() +{ + static const uint8_t first_channel_idx = 0; + static_assert((1 << first_channel_idx) & ADC_CHAN_MSK); + + ADCSRA &= ~(1 << ADSC); //stop conversion just in case + adc_count = 0; + adc_channel = 0; + adc_channel_idx = first_channel_idx; + adc_setmux(adc_channel_idx); + memset((void*)adc_values, 0, sizeof(adc_values)); +} + +static void adc_setmux(uint8_t ch) +{ + ch &= 0x0f; + if (ch & 0x08) ADCSRB |= (1 << MUX5); + else ADCSRB &= ~(1 << MUX5); + ADMUX = (ADMUX & ~(0x07)) | (ch & 0x07); +} + +void adc_start_cycle() { + adc_reset(); + ADCSRA |= (1 << ADSC); //start conversion +} + +#ifdef ADC_CALLBACK +extern void ADC_CALLBACK(); +#endif //ADC_CALLBACK + +ISR(ADC_vect) +{ + adc_values[adc_channel] += ADC; + if (++adc_count == ADC_OVRSAMPL) + { + // go to the next channel + if (++adc_channel == ADC_CHAN_CNT) { +#ifdef ADC_CALLBACK + ADC_CALLBACK(); +#endif + return; // do not start the next measurement since there are no channels remaining + } + + // find the next channel + while (++adc_channel_idx) { + if (ADC_CHAN_MSK & (1 << adc_channel_idx)) { + adc_setmux(adc_channel_idx); + adc_count = 0; + break; + } + } + } + ADCSRA |= (1 << ADSC); //start conversion +} diff --git a/Firmware/adc.h b/Firmware/adc.h index 9ff137dfd..ac110dc04 100644 --- a/Firmware/adc.h +++ b/Firmware/adc.h @@ -1,15 +1,8 @@ -//adc.h -#ifndef _ADC_H -#define _ADC_H +#pragma once #include #include "config.h" - -#if defined(__cplusplus) -extern "C" { -#endif //defined(__cplusplus) - /* http://resnet.uoregon.edu/~gurney_j/jmpc/bitwise.html */ @@ -22,24 +15,11 @@ http://resnet.uoregon.edu/~gurney_j/jmpc/bitwise.html # error "ADC_CHAN_MSK oes not match ADC_CHAN_CNT" #endif -extern uint8_t adc_state; -extern uint8_t adc_count; -extern uint16_t adc_values[ADC_CHAN_CNT]; -extern uint16_t adc_sim_mask; +#define VOLT_DIV_REF 5 //[V] +extern volatile uint8_t adc_channel; +extern volatile uint16_t adc_values[ADC_CHAN_CNT]; -extern void adc_init(void); - -extern void adc_reset(void); - -extern void adc_setmux(uint8_t ch); - -extern uint8_t adc_chan(uint8_t index); - -extern void adc_cycle(void); - - -#if defined(__cplusplus) -} -#endif //defined(__cplusplus) -#endif //_ADC_H +extern void adc_init(); +extern void adc_start_cycle(); //should be called from an atomic context only +static inline bool adc_cycle_done() { return adc_channel >= ADC_CHAN_CNT; } diff --git a/Firmware/asm.h b/Firmware/asm.h new file mode 100644 index 000000000..0c3d74572 --- /dev/null +++ b/Firmware/asm.h @@ -0,0 +1,21 @@ +#pragma once +#include +#include "macros.h" + +#ifdef __AVR_ATmega2560__ + +// return the current PC (on AVRs with 22bit PC) +FORCE_INLINE __uint24 GETPC(void) +{ + __uint24 ret; + asm ( + "rcall .\n" + "pop %A0\n" + "pop %B0\n" + "pop %C0\n" + : "=&r" (ret) + ); + return ret; +} + +#endif diff --git a/Firmware/backlight.cpp b/Firmware/backlight.cpp index 6500837e5..ef7206d26 100644 --- a/Firmware/backlight.cpp +++ b/Firmware/backlight.cpp @@ -1,10 +1,10 @@ //backlight.cpp #include "backlight.h" +#include "macros.h" #include #include #include "eeprom.h" -#include "Marlin.h" #include "pins.h" #include "fastio.h" #include "Timer.h" @@ -91,30 +91,22 @@ void backlight_init() backlightSupport = !READ(LCD_BL_PIN); if (!backlightSupport) return; -//initialize backlight - backlightMode = eeprom_read_byte((uint8_t *)EEPROM_BACKLIGHT_MODE); - if (backlightMode == 0xFF) //set default values - { - backlightMode = BACKLIGHT_MODE_AUTO; - backlightLevel_HIGH = 130; - backlightLevel_LOW = 50; - backlightTimer_period = 10; //in seconds - backlight_save(); - } - backlightLevel_HIGH = eeprom_read_byte((uint8_t *)EEPROM_BACKLIGHT_LEVEL_HIGH); - backlightLevel_LOW = eeprom_read_byte((uint8_t *)EEPROM_BACKLIGHT_LEVEL_LOW); - backlightTimer_period = eeprom_read_word((uint16_t *)EEPROM_BACKLIGHT_TIMEOUT); - + //initialize backlight + backlightMode = eeprom_init_default_byte((uint8_t *)EEPROM_BACKLIGHT_MODE, BACKLIGHT_MODE_AUTO); + backlightLevel_HIGH = eeprom_init_default_byte((uint8_t *)EEPROM_BACKLIGHT_LEVEL_HIGH, 130); + backlightLevel_LOW = eeprom_init_default_byte((uint8_t *)EEPROM_BACKLIGHT_LEVEL_LOW, 50); + backlightTimer_period = eeprom_init_default_word((uint16_t *)EEPROM_BACKLIGHT_TIMEOUT, 10); // in seconds + SET_OUTPUT(LCD_BL_PIN); backlightTimer_reset(); } #else //LCD_BL_PIN -void force_bl_on(__attribute__((unused)) bool section_start) {} +void force_bl_on(bool) {} void backlight_update() {} void backlight_init() {} void backlight_save() {} -void backlight_wake(__attribute__((unused)) const uint8_t flashNo) {} +void backlight_wake(const uint8_t) {} -#endif //LCD_BL_PIN \ No newline at end of file +#endif //LCD_BL_PIN diff --git a/Firmware/bootapp.c b/Firmware/bootapp.c index 4fd67db2b..78614d8f7 100644 --- a/Firmware/bootapp.c +++ b/Firmware/bootapp.c @@ -9,6 +9,8 @@ extern FILE _uartout; #define uartout (&_uartout) +extern void softReset(); + void bootapp_print_vars(void) { fprintf_P(uartout, PSTR("boot_src_addr =0x%08lx\n"), boot_src_addr); @@ -26,21 +28,11 @@ void bootapp_ram2flash(uint16_t rptr, uint16_t fptr, uint16_t size) boot_app_magic = BOOT_APP_MAGIC; boot_app_flags |= BOOT_APP_FLG_COPY; boot_app_flags |= BOOT_APP_FLG_ERASE; -/* uint16_t ui; for (ui = 0; ui < size; ui++) - { - uint8_t uc = ram_array[ui+rptr]; - if (pgm_read_byte(ui+fptr) & uc != uc) - { - boot_app_flags |= BOOT_APP_FLG_ERASE; - break; - } - }*/ boot_copy_size = (uint16_t)size; boot_src_addr = (uint32_t)rptr; boot_dst_addr = (uint32_t)fptr; bootapp_print_vars(); - wdt_enable(WDTO_15MS); - while(1); + softReset(); } void bootapp_reboot_user0(uint8_t reserved) @@ -50,6 +42,5 @@ void bootapp_reboot_user0(uint8_t reserved) boot_app_flags = BOOT_APP_FLG_USER0; boot_reserved = reserved; bootapp_print_vars(); - wdt_enable(WDTO_15MS); - while(1); + softReset(); } diff --git a/Firmware/bootapp.h b/Firmware/bootapp.h index 3fb0306a1..9a77c5abd 100644 --- a/Firmware/bootapp.h +++ b/Firmware/bootapp.h @@ -3,11 +3,11 @@ #define BOOTAPP_H #include "config.h" +#include #include -#define RAMSIZE 0x2000 -#define ram_array ((uint8_t*)(0)) +#define RAMSIZE (RAMEND+1-RAMSTART) #define boot_src_addr (*((uint32_t*)(RAMSIZE - 16))) #define boot_dst_addr (*((uint32_t*)(RAMSIZE - 12))) #define boot_copy_size (*((uint16_t*)(RAMSIZE - 8))) diff --git a/Firmware/cardreader.cpp b/Firmware/cardreader.cpp index 545316d39..4f8ee6c58 100644 --- a/Firmware/cardreader.cpp +++ b/Firmware/cardreader.cpp @@ -1,9 +1,12 @@ #include "Marlin.h" +#include "cmdqueue.h" #include "cardreader.h" #include "ultralcd.h" +#include "menu.h" #include "stepper.h" #include "temperature.h" #include "language.h" +#include "Prusa_farm.h" #ifdef SDSUPPORT @@ -14,11 +17,6 @@ CardReader::CardReader() #ifdef SDCARD_SORT_ALPHA sort_count = 0; - #if SDSORT_GCODE - sort_alpha = true; - sort_folders = FOLDER_SORTING; - //sort_reverse = false; - #endif #endif filesize = 0; @@ -27,12 +25,11 @@ CardReader::CardReader() cardOK = false; saving = false; logging = false; - autostart_atmillis=0; workDirDepth = 0; file_subcall_ctr=0; memset(workDirParents, 0, sizeof(workDirParents)); + presort_flag = false; - autostart_stilltocheck=true; //the SD start is delayed, because otherwise the serial cannot answer fast enough to make contact with the host software. lastnr=0; //power to SD reader #if SDPOWER > -1 @@ -40,7 +37,7 @@ CardReader::CardReader() WRITE(SDPOWER,HIGH); #endif //SDPOWER - autostart_atmillis=_millis()+5000; + autostart_atmillis.start(); // reset timer } char *createFilename(char *buffer,const dir_t &p) //buffer>12characters @@ -61,18 +58,33 @@ char *createFilename(char *buffer,const dir_t &p) //buffer>12characters /** +* Dive into a folder and recurse depth-first to perform a pre-set operation lsAction: -+* LS_Count - Add +1 to nrFiles for every file within the parent -+* LS_GetFilename - Get the filename of the file indexed by nrFiles -+* LS_SerialPrint - Print the full path and size of each file to serial output ++* LS_Count - Add +1 to nrFiles for every file within the parent ++* LS_GetFilename - Get the filename of the file indexed by nrFiles ++* LS_SerialPrint - Print the full path and size of each file to serial output +*/ -void CardReader::lsDive(const char *prepend, SdFile parent, const char * const match/*=NULL*/) { +void CardReader::lsDive(const char *prepend, SdFile parent, const char * const match/*=NULL*/, LsAction lsAction, ls_param lsParams) { + static uint8_t recursionCnt = 0; + // RAII incrementer for the recursionCnt + class _incrementer + { + public: + _incrementer() {recursionCnt++;} + ~_incrementer() {recursionCnt--;} + } recursionCntIncrementer; + dir_t p; uint8_t cnt = 0; // Read the next entry from a directory - while (parent.readDir(p, longFilename) > 0) { - // If the entry is a directory and the action is LS_SerialPrint - if (DIR_IS_SUBDIR(&p) && lsAction != LS_Count && lsAction != LS_GetFilename) { + for (position = parent.curPosition(); parent.readDir(p, longFilename) > 0; position = parent.curPosition()) { + if (recursionCnt > MAX_DIR_DEPTH) + return; + uint8_t pn0 = p.name[0]; + if (pn0 == DIR_NAME_FREE) break; + if (pn0 == DIR_NAME_DELETED || pn0 == '.') continue; + if (longFilename[0] == '.') continue; + if (!DIR_IS_FILE_OR_SUBDIR(&p) || (p.attributes & DIR_ATT_HIDDEN)) continue; + if (DIR_IS_SUBDIR(&p) && lsAction == LS_SerialPrint) { // If the entry is a directory and the action is LS_SerialPrint // Get the short name for the item, which we know is a folder char lfilename[FILENAME_LENGTH]; createFilename(lfilename, p); @@ -89,23 +101,23 @@ void CardReader::lsDive(const char *prepend, SdFile parent, const char * const m // Serial.print(path); // Get a new directory object using the full path // and dive recursively into it. + + if (lsParams.LFN) + printf_P(PSTR("DIR_ENTER: %s \"%s\"\n"), path, longFilename[0] ? longFilename : lfilename); + SdFile dir; if (!dir.open(parent, lfilename, O_READ)) { - if (lsAction == LS_SerialPrint) { - //SERIAL_ECHO_START(); - //SERIAL_ECHOPGM(_i("Cannot open subdir"));////MSG_SD_CANT_OPEN_SUBDIR - //SERIAL_ECHOLN(lfilename); - } + //SERIAL_ECHO_START(); + //SERIAL_ECHOPGM(_i("Cannot open subdir"));////MSG_SD_CANT_OPEN_SUBDIR + //SERIAL_ECHOLN(lfilename); } - lsDive(path, dir); + lsDive(path, dir, NULL, lsAction, lsParams); // close() is done automatically by destructor of SdFile + + if (lsParams.LFN) + puts_P(PSTR("DIR_EXIT")); } else { - uint8_t pn0 = p.name[0]; - if (pn0 == DIR_NAME_FREE) break; - if (pn0 == DIR_NAME_DELETED || pn0 == '.') continue; - if (longFilename[0] == '.') continue; - if (!DIR_IS_FILE_OR_SUBDIR(&p) || (p.attributes & DIR_ATT_HIDDEN)) continue; filenameIsDir = DIR_IS_SUBDIR(&p); if (!filenameIsDir && (p.name[8] != 'G' || p.name[9] == '~')) continue; switch (lsAction) { @@ -117,15 +129,33 @@ void CardReader::lsDive(const char *prepend, SdFile parent, const char * const m createFilename(filename, p); SERIAL_PROTOCOL(prepend); SERIAL_PROTOCOL(filename); + MYSERIAL.write(' '); - SERIAL_PROTOCOLLN(p.fileSize); + SERIAL_PROTOCOL(p.fileSize); + + if (lsParams.timestamp) + { + crmodDate = p.lastWriteDate; + crmodTime = p.lastWriteTime; + if( crmodDate < p.creationDate || ( crmodDate == p.creationDate && crmodTime < p.creationTime ) ){ + crmodDate = p.creationDate; + crmodTime = p.creationTime; + } + printf_P(PSTR(" %#lx"), ((uint32_t)crmodDate << 16) | crmodTime); + } + + if (lsParams.LFN) + printf_P(PSTR(" \"%s\""), LONGEST_FILENAME); + + SERIAL_PROTOCOLLN(); + manage_heater(); break; case LS_GetFilename: - //SERIAL_ECHOPGM("File: "); + //SERIAL_ECHOPGM("File: "); createFilename(filename, p); - cluster = parent.curCluster(); - position = parent.curPosition(); + // cluster = parent.curCluster(); + // position = parent.curPosition(); /*MYSERIAL.println(filename); SERIAL_ECHOPGM("Write date: "); writeDate = p.lastWriteDate; @@ -136,8 +166,17 @@ void CardReader::lsDive(const char *prepend, SdFile parent, const char * const m SERIAL_ECHOPGM("Access date: "); MYSERIAL.println(p.lastAccessDate); SERIAL_ECHOLNPGM("");*/ - modificationDate = p.lastWriteDate; - modificationTime = p.lastWriteTime; + crmodDate = p.lastWriteDate; + crmodTime = p.lastWriteTime; + // There are scenarios when simple modification time is not enough (on MS Windows) + // For example - extract an old g-code from an archive onto the SD card. + // In such case the creation time is current time (which is correct), but the modification time + // stays the same - i.e. old. + // Therefore let's pick the most recent timestamp from both creation and modification timestamps + if( crmodDate < p.creationDate || ( crmodDate == p.creationDate && crmodTime < p.creationTime ) ){ + crmodDate = p.creationDate; + crmodTime = p.creationTime; + } //writeDate = p.lastAccessDate; if (match != NULL) { if (strcasecmp(match, filename) == 0) return; @@ -150,37 +189,26 @@ void CardReader::lsDive(const char *prepend, SdFile parent, const char * const m } // while readDir } -void CardReader::ls() +void CardReader::ls(ls_param params) { - lsAction=LS_SerialPrint; - //if(lsAction==LS_Count) - //nrFiles=0; - root.rewind(); - lsDive("",root); + lsDive("",root, NULL, LS_SerialPrint, params); } -void CardReader::initsd() +void CardReader::initsd(bool doPresort/* = true*/) { cardOK = false; if(root.isOpen()) root.close(); #ifdef SDSLOW - if (!card.init(SPI_HALF_SPEED,SDSS) - #if defined(LCD_SDSS) && (LCD_SDSS != SDSS) - && !card.init(SPI_HALF_SPEED,LCD_SDSS) - #endif + if (!card.init(SPI_HALF_SPEED) ) #else - if (!card.init(SPI_FULL_SPEED,SDSS) - #if defined(LCD_SDSS) && (LCD_SDSS != SDSS) - && !card.init(SPI_FULL_SPEED,LCD_SDSS) - #endif + if (!card.init(SPI_FULL_SPEED) ) #endif { - //if (!card.init(SPI_HALF_SPEED,SDSS)) SERIAL_ECHO_START; SERIAL_ECHOLNRPGM(_n("SD init fail"));////MSG_SD_INIT_FAIL } @@ -205,7 +233,8 @@ void CardReader::initsd() workDirDepth = 0; #ifdef SDCARD_SORT_ALPHA - presort(); + if (doPresort) + presort(); #endif /* @@ -217,23 +246,25 @@ void CardReader::initsd() } -void CardReader::setroot() +void CardReader::setroot(bool doPresort) { - /*if(!workDir.openRoot(&volume)) - { - SERIAL_ECHOLNPGM(MSG_SD_WORKDIR_FAIL); - }*/ workDir=root; + workDirDepth = 0; curDir=&workDir; - #ifdef SDCARD_SORT_ALPHA - presort(); - #endif +#ifdef SDCARD_SORT_ALPHA + if (doPresort) + presort(); + else + presort_flag = true; +#endif } void CardReader::release() { sdprinting = false; cardOK = false; + SERIAL_ECHO_START; + SERIAL_ECHOLNRPGM(_n("SD card released"));////MSG_SD_CARD_RELEASED } void CardReader::startFileprint() @@ -241,7 +272,6 @@ void CardReader::startFileprint() if(cardOK) { sdprinting = true; - Stopped = false; #ifdef SDCARD_SORT_ALPHA //flush_presort(); #endif @@ -251,7 +281,7 @@ void CardReader::startFileprint() void CardReader::openLogFile(const char* name) { logging = true; - openFile(name, false); + openFileWrite(name); } void CardReader::getDirName(char* name, uint8_t level) @@ -259,7 +289,7 @@ void CardReader::getDirName(char* name, uint8_t level) workDirParents[level].getFilename(name); } -uint16_t CardReader::getWorkDirDepth() { +uint8_t CardReader::getWorkDirDepth() { return workDirDepth; } @@ -273,11 +303,23 @@ void CardReader::getAbsFilename(char *t) while(*t!=0 && cnt< MAXPATHNAMELENGTH) {t++;cnt++;} //crawl counter forward. } - if(cnt(dirname_end-dirname_start))>maxLen) ? maxLen : (dirname_end-dirname_start); strncpy(subdirname, dirname_start, len); - SERIAL_ECHOLN(subdirname); - if (!dir.open(curDir, subdirname, O_READ)) - { - SERIAL_PROTOCOLRPGM(MSG_SD_OPEN_FILE_FAIL); - SERIAL_PROTOCOL(subdirname); - SERIAL_PROTOCOLLNPGM("."); - return; - } - else - { - //SERIAL_ECHOLN("dive ok"); - } + subdirname[len] = 0; + if (!chdir(subdirname, false)) + return 0; - curDir = &dir; + curDir = &workDir; dirname_start = dirname_end + 1; } else // the reminder after all /fsa/fdsa/ is the filename @@ -347,100 +377,147 @@ void CardReader::diveSubfolder (const char *fileName, SdFile& dir) { curDir = &workDir; } + return 1; } -void CardReader::openFile(const char* name,bool read, bool replace_current/*=true*/) -{ - if(!cardOK) - return; - if(file.isOpen()) //replacing current file by new file, or subfile call - { - if(!replace_current) - { - if((int)file_subcall_ctr>(int)SD_PROCEDURE_DEPTH-1) - { - // SERIAL_ERROR_START; - // SERIAL_ERRORPGM("trying to call sub-gcode files with too many levels. MAX level is:"); - // SERIAL_ERRORLN(SD_PROCEDURE_DEPTH); - kill(_n("trying to call sub-gcode files with too many levels."), 1); - return; - } - - SERIAL_ECHO_START; - SERIAL_ECHOPGM("SUBROUTINE CALL target:\""); - SERIAL_ECHO(name); - SERIAL_ECHOPGM("\" parent:\""); - - //store current filename and position - getAbsFilename(filenames[file_subcall_ctr]); - - SERIAL_ECHO(filenames[file_subcall_ctr]); - SERIAL_ECHOPGM("\" pos"); - SERIAL_ECHOLN(sdpos); - filespos[file_subcall_ctr]=sdpos; - file_subcall_ctr++; - } - else - { - SERIAL_ECHO_START; - SERIAL_ECHOPGM("Now doing file: "); - SERIAL_ECHOLN(name); - } - file.close(); - } - else //opening fresh file - { - file_subcall_ctr=0; //resetting procedure depth in case user cancels print while in procedure - SERIAL_ECHO_START; - SERIAL_ECHOPGM("Now fresh file: "); - SERIAL_ECHOLN(name); - } - sdprinting = false; +static const char ofKill[] PROGMEM = "trying to call sub-gcode files with too many levels."; +static const char ofSubroutineCallTgt[] PROGMEM = "SUBROUTINE CALL target:\""; +static const char ofParent[] PROGMEM = "\" parent:\""; +static const char ofPos[] PROGMEM = "\" pos"; +static const char ofNowDoingFile[] PROGMEM = "Now doing file: "; +static const char ofNowFreshFile[] PROGMEM = "Now fresh file: "; +static const char ofFileOpened[] PROGMEM = "File opened: "; +static const char ofSize[] PROGMEM = " Size: "; +static const char ofFileSelected[] PROGMEM = "File selected"; +static const char ofSDPrinting[] PROGMEM = "SD-PRINTING"; +static const char ofWritingToFile[] PROGMEM = "Writing to file: "; - SdFile myDir; - const char *fname=name; - diveSubfolder(fname,myDir); - - if(read) - { - if (file.open(curDir, fname, O_READ)) - { - filesize = file.fileSize(); - SERIAL_PROTOCOLRPGM(_N("File opened: "));////MSG_SD_FILE_OPENED - SERIAL_PROTOCOL(fname); - SERIAL_PROTOCOLRPGM(_n(" Size: "));////MSG_SD_SIZE - SERIAL_PROTOCOLLN(filesize); - sdpos = 0; - - SERIAL_PROTOCOLLNRPGM(_N("File selected"));////MSG_SD_FILE_SELECTED - getfilename(0, fname); - lcd_setstatus(longFilename[0] ? longFilename : fname); - lcd_setstatus("SD-PRINTING "); +void CardReader::openFileReadFilteredGcode(const char* name, bool replace_current/* = false*/){ + if(!cardOK) + return; + + if(file.isOpen()){ //replacing current file by new file, or subfile call + if(!replace_current){ + if((int)file_subcall_ctr>(int)SD_PROCEDURE_DEPTH-1){ + // SERIAL_ERROR_START; + // SERIAL_ERRORPGM("trying to call sub-gcode files with too many levels. MAX level is:"); + // SERIAL_ERRORLN(SD_PROCEDURE_DEPTH); + kill(ofKill, 1); + return; + } + + SERIAL_ECHO_START; + SERIAL_ECHORPGM(ofSubroutineCallTgt); + SERIAL_ECHO(name); + SERIAL_ECHORPGM(ofParent); + + //store current filename and position + getAbsFilename(filenames[file_subcall_ctr]); + + SERIAL_ECHO(filenames[file_subcall_ctr]); + SERIAL_ECHORPGM(ofPos); + SERIAL_ECHOLN(sdpos); + filespos[file_subcall_ctr]=sdpos; + file_subcall_ctr++; + } else { + SERIAL_ECHO_START; + SERIAL_ECHORPGM(ofNowDoingFile); + SERIAL_ECHOLN(name); + } + file.close(); + } else { //opening fresh file + file_subcall_ctr=0; //resetting procedure depth in case user cancels print while in procedure + SERIAL_ECHO_START; + SERIAL_ECHORPGM(ofNowFreshFile); + SERIAL_ECHOLN(name); } - else - { - SERIAL_PROTOCOLRPGM(MSG_SD_OPEN_FILE_FAIL); - SERIAL_PROTOCOL(fname); - SERIAL_PROTOCOLLNPGM("."); - } - } - else - { //write - if (!file.open(curDir, fname, O_CREAT | O_APPEND | O_WRITE | O_TRUNC)) - { - SERIAL_PROTOCOLRPGM(MSG_SD_OPEN_FILE_FAIL); - SERIAL_PROTOCOL(fname); - SERIAL_PROTOCOLLNPGM("."); - } - else - { - saving = true; - SERIAL_PROTOCOLRPGM(_N("Writing to file: "));////MSG_SD_WRITE_TO_FILE - SERIAL_PROTOCOLLN(name); - lcd_setstatus(fname); - } - } + sdprinting = false; + const char *fname=name; + if (!diveSubfolder(fname)) + return; + + if (file.openFilteredGcode(curDir, fname)) { + getfilename(0, fname); + filesize = file.fileSize(); + SERIAL_PROTOCOLRPGM(ofFileOpened);////MSG_SD_FILE_OPENED + printAbsFilenameFast(); + SERIAL_PROTOCOLRPGM(ofSize);////MSG_SD_SIZE + SERIAL_PROTOCOLLN(filesize); + sdpos = 0; + + SERIAL_PROTOCOLLNRPGM(ofFileSelected);////MSG_SD_FILE_SELECTED + lcd_setstatuspgm(ofFileSelected); + scrollstuff = 0; + } else { + SERIAL_PROTOCOLRPGM(MSG_SD_OPEN_FILE_FAIL); + SERIAL_PROTOCOL(fname); + SERIAL_PROTOCOLLN('.'); + } +} + +void CardReader::openFileWrite(const char* name) +{ + if(!cardOK) + return; + if(file.isOpen()){ //replacing current file by new file, or subfile call +#if 0 + // I doubt chained files support is necessary for file saving: + // Intentionally disabled because it takes a lot of code size while being not used + + if((int)file_subcall_ctr>(int)SD_PROCEDURE_DEPTH-1){ + // SERIAL_ERROR_START; + // SERIAL_ERRORPGM("trying to call sub-gcode files with too many levels. MAX level is:"); + // SERIAL_ERRORLN(SD_PROCEDURE_DEPTH); + kill(ofKill, 1); + return; + } + + SERIAL_ECHO_START; + SERIAL_ECHORPGM(ofSubroutineCallTgt); + SERIAL_ECHO(name); + SERIAL_ECHORPGM(ofParent); + + //store current filename and position + getAbsFilename(filenames[file_subcall_ctr]); + + SERIAL_ECHO(filenames[file_subcall_ctr]); + SERIAL_ECHORPGM(ofPos); + SERIAL_ECHOLN(sdpos); + filespos[file_subcall_ctr]=sdpos; + file_subcall_ctr++; + file.close(); +#else + SERIAL_ECHOLNPGM("File already opened"); +#endif + } else { //opening fresh file + file_subcall_ctr=0; //resetting procedure depth in case user cancels print while in procedure + SERIAL_ECHO_START; + SERIAL_ECHORPGM(ofNowFreshFile); + SERIAL_ECHOLN(name); + } + sdprinting = false; + + const char *fname=name; + if (!diveSubfolder(fname)) + return; + + //write + if (!file.open(curDir, fname, O_CREAT | O_APPEND | O_WRITE | O_TRUNC)){ + SERIAL_PROTOCOLRPGM(MSG_SD_OPEN_FILE_FAIL); + SERIAL_PROTOCOL(fname); + SERIAL_PROTOCOLLN('.'); + } else { + saving = true; + getfilename(0, fname); + SERIAL_PROTOCOLRPGM(ofWritingToFile);////MSG_SD_WRITE_TO_FILE + printAbsFilenameFast(); + SERIAL_PROTOCOLLN(); + + SERIAL_PROTOCOLLNRPGM(ofFileSelected);////MSG_SD_FILE_SELECTED + lcd_setstatuspgm(ofFileSelected); + scrollstuff = 0; + } } void CardReader::removeFile(const char* name) @@ -449,9 +526,9 @@ void CardReader::removeFile(const char* name) file.close(); sdprinting = false; - SdFile myDir; const char *fname=name; - diveSubfolder(fname,myDir); + if (!diveSubfolder(fname)) + return; if (file.remove(curDir, fname)) { @@ -466,7 +543,7 @@ void CardReader::removeFile(const char* name) { SERIAL_PROTOCOLPGM("Deletion failed, File: "); SERIAL_PROTOCOL(fname); - SERIAL_PROTOCOLLNPGM("."); + SERIAL_PROTOCOLLN('.'); } } @@ -476,50 +553,42 @@ uint32_t CardReader::getFileSize() return filesize; } -void CardReader::getStatus() +void CardReader::getStatus(bool arg_P) { - if(sdprinting) - { - if (isPrintPaused) { - SERIAL_PROTOCOLLNPGM("SD print paused"); - } - else if (saved_printing) { - SERIAL_PROTOCOLLNPGM("Print saved"); - } - else { - SERIAL_PROTOCOL(longFilename); - SERIAL_PROTOCOLPGM("\n"); - SERIAL_PROTOCOLRPGM(_N("SD printing byte "));////MSG_SD_PRINTING_BYTE - SERIAL_PROTOCOL(sdpos); - SERIAL_PROTOCOLPGM("/"); - SERIAL_PROTOCOLLN(filesize); - uint16_t time = _millis()/60000 - starttime/60000; - SERIAL_PROTOCOL(itostr2(time/60)); - SERIAL_PROTOCOL(':'); - SERIAL_PROTOCOL(itostr2(time%60)); - SERIAL_PROTOCOLPGM("\n"); - } - } - else { - SERIAL_PROTOCOLLNPGM("Not SD printing"); - } + if (isPrintPaused) + { + if (saved_printing && (saved_printing_type == PRINTING_TYPE_SD)) + SERIAL_PROTOCOLLNPGM("SD print paused"); + else + SERIAL_PROTOCOLLNPGM("Print saved"); + } + else if (sdprinting) + { + if (arg_P) + { + printAbsFilenameFast(); + SERIAL_PROTOCOLLN(); + } + else + SERIAL_PROTOCOLLN(LONGEST_FILENAME); + + SERIAL_PROTOCOLRPGM(_N("SD printing byte "));////MSG_SD_PRINTING_BYTE + SERIAL_PROTOCOL(sdpos); + SERIAL_PROTOCOL('/'); + SERIAL_PROTOCOLLN(filesize); + uint16_t time = ( _millis() - starttime ) / 60000U; + SERIAL_PROTOCOL((int)(time / 60)); + SERIAL_PROTOCOL(':'); + SERIAL_PROTOCOLLN((int)(time % 60)); + } + else + SERIAL_PROTOCOLLNPGM("Not SD printing"); } void CardReader::write_command(char *buf) { - char* begin = buf; - char* npos = 0; - char* end = buf + strlen(buf) - 1; - file.writeError = false; - if((npos = strchr(buf, 'N')) != NULL) - { - begin = strchr(npos, ' ') + 1; - end = strchr(npos, '*') - 1; - } - end[1] = '\r'; - end[2] = '\n'; - end[3] = '\0'; - file.write(begin); + file.write(buf); //write command + file.write("\r\n"); //write line termination if (file.writeError) { SERIAL_ERROR_START; @@ -536,21 +605,24 @@ void CardReader::write_command_no_newline(char *buf) { SERIAL_ERROR_START; SERIAL_ERRORLNRPGM(MSG_SD_ERR_WRITE_TO_FILE); - MYSERIAL.println("An error while writing to the SD Card."); + SERIAL_PROTOCOLLNPGM("An error while writing to the SD Card."); } } void CardReader::checkautostart(bool force) { + // The SD start is delayed because otherwise the serial cannot answer + // fast enough to make contact with the host software. + static bool autostart_stilltocheck = true; if(!force) { if(!autostart_stilltocheck) return; - if(autostart_atmillis<_millis()) + if(autostart_atmillis.expired(5000)) return; } - autostart_stilltocheck=false; + autostart_stilltocheck = false; if(!cardOK) { initsd(); @@ -612,34 +684,39 @@ void CardReader::closefile(bool store_location) void CardReader::getfilename(uint16_t nr, const char * const match/*=NULL*/) { curDir=&workDir; - lsAction=LS_GetFilename; nrFiles=nr; curDir->rewind(); - lsDive("",*curDir,match); + lsDive("",*curDir,match, LS_GetFilename); } -void CardReader::getfilename_simple(uint32_t position, const char * const match/*=NULL*/) +void CardReader::getfilename_simple(uint16_t entry, const char * const match/*=NULL*/) { curDir = &workDir; - lsAction = LS_GetFilename; nrFiles = 0; + curDir->seekSet((uint32_t)entry << 5); + lsDive("", *curDir, match, LS_GetFilename); +} + +void CardReader::getfilename_next(uint32_t position, const char * const match/*=NULL*/) +{ + curDir = &workDir; + nrFiles = 1; curDir->seekSet(position); - lsDive("", *curDir, match); + lsDive("", *curDir, match, LS_GetFilename); } uint16_t CardReader::getnrfilenames() { curDir=&workDir; - lsAction=LS_Count; nrFiles=0; curDir->rewind(); - lsDive("",*curDir); + lsDive("",*curDir, NULL, LS_Count); //SERIAL_ECHOLN(nrFiles); return nrFiles; } -void CardReader::chdir(const char * relpath) +bool CardReader::chdir(const char * relpath, bool doPresort) { SdFile newfile; SdFile *parent=&root; @@ -647,23 +724,32 @@ void CardReader::chdir(const char * relpath) if(workDir.isOpen()) parent=&workDir; - if(!newfile.open(*parent,relpath, O_READ)) + if(!newfile.open(*parent,relpath, O_READ) || ((workDirDepth + 1) >= MAX_DIR_DEPTH)) { SERIAL_ECHO_START; SERIAL_ECHORPGM(_n("Cannot enter subdir: "));////MSG_SD_CANT_ENTER_SUBDIR SERIAL_ECHOLN(relpath); + return 0; } else { + strcpy(dir_names[workDirDepth], relpath); + puts(relpath); + if (workDirDepth < MAX_DIR_DEPTH) { - for (int d = ++workDirDepth; d--;) + for (uint8_t d = ++workDirDepth; d--;) workDirParents[d+1] = workDirParents[d]; workDirParents[0]=*parent; } workDir=newfile; - #ifdef SDCARD_SORT_ALPHA + +#ifdef SDCARD_SORT_ALPHA + if (doPresort) presort(); - #endif + else + presort_flag = true; +#endif + return 1; } } @@ -673,7 +759,7 @@ void CardReader::updir() { --workDirDepth; workDir = workDirParents[0]; - for (unsigned int d = 0; d < workDirDepth; d++) + for (uint8_t d = 0; d < workDirDepth; d++) { workDirParents[d] = workDirParents[d+1]; } @@ -688,13 +774,19 @@ void CardReader::updir() /** * Get the name of a file in the current directory by sort-index */ -void CardReader::getfilename_sorted(const uint16_t nr) { - getfilename( - #if SDSORT_GCODE - sort_alpha && - #endif - (nr < sort_count) ? sort_order[nr] : nr - ); +void CardReader::getfilename_sorted(const uint16_t nr, uint8_t sdSort) { + if (nr < sort_count) + getfilename_simple(sort_entries[(sdSort == SD_SORT_ALPHA) ? (sort_count - nr - 1) : nr]); + else + getfilename_afterMaxSorting(nr); +} + +void CardReader::getfilename_afterMaxSorting(uint16_t entry, const char * const match/*=NULL*/) +{ + curDir = &workDir; + nrFiles = entry - sort_count + 1; + curDir->seekSet(lastSortedFilePosition << 5); + lsDive("", *curDir, match, LS_GetFilename); } /** @@ -706,269 +798,206 @@ void CardReader::getfilename_sorted(const uint16_t nr) { * - Most RAM: Buffer the directory and return filenames from RAM */ void CardReader::presort() { - if (farm_mode || IS_SD_INSERTED == false) return; //sorting is not used in farm mode - uint8_t sdSort = eeprom_read_byte((uint8_t*)EEPROM_SD_SORT); - - if (sdSort == SD_SORT_NONE) return; //sd sort is turned off - - #if SDSORT_GCODE - if (!sort_alpha) return; - #endif - KEEPALIVE_STATE(IN_HANDLER); - // Throw away old sort index flush_presort(); + + if (IS_SD_INSERTED == false) return; //sorting is not used in farm mode + uint8_t sdSort = eeprom_read_byte((uint8_t*)EEPROM_SD_SORT); + + KEEPALIVE_STATE(IN_HANDLER); // If there are files, sort up to the limit uint16_t fileCnt = getnrfilenames(); if (fileCnt > 0) { - // Never sort more than the max allowed // If you use folders to organize, 20 may be enough if (fileCnt > SDSORT_LIMIT) { - lcd_show_fullscreen_message_and_wait_P(_i("Some files will not be sorted. Max. No. of files in 1 folder for sorting is 100."));////MSG_FILE_CNT c=20 r=4 + if ((sdSort != SD_SORT_NONE) && !farm_mode) { + lcd_show_fullscreen_message_and_wait_P(_i("Some files will not be sorted. Max. No. of files in 1 folder for sorting is 100."));////MSG_FILE_CNT c=20 r=6 + } fileCnt = SDSORT_LIMIT; } - lcd_clear(); - #if !SDSORT_USES_RAM - lcd_set_progress(); - #endif - lcd_puts_at_P(0, 1, _i("Sorting files"));////MSG_SORTING c=20 r=1 - // Sort order is always needed. May be static or dynamic. - #if SDSORT_DYNAMIC_RAM - sort_order = new uint8_t[fileCnt]; - #endif + sort_count = fileCnt; + + // Init sort order. + for (uint16_t i = 0; i < fileCnt; i++) { + if (!IS_SD_INSERTED) return; + manage_heater(); + if (i == 0) + getfilename(0); + else + getfilename_next(position); + sort_entries[i] = position >> 5; + } - // Use RAM to store the entire directory during pre-sort. - // SDSORT_LIMIT should be set to prevent over-allocation. - #if SDSORT_USES_RAM + if ((fileCnt > 1) && (sdSort != SD_SORT_NONE) && !farm_mode) { - // If using dynamic ram for names, allocate on the heap. - #if SDSORT_CACHE_NAMES - #if SDSORT_DYNAMIC_RAM - sortshort = new char*[fileCnt]; - sortnames = new char*[fileCnt]; - #endif - #elif SDSORT_USES_STACK - char sortnames[fileCnt][LONG_FILENAME_LENGTH]; - uint16_t modification_time[fileCnt]; - uint16_t modification_date[fileCnt]; - #endif +#ifdef SORTING_SPEEDTEST + LongTimer sortingSpeedtestTimer; + sortingSpeedtestTimer.start(); +#endif //SORTING_SPEEDTEST + lastSortedFilePosition = position >> 5; - // Folder sorting needs 1 bit per entry for flags. - #if HAS_FOLDER_SORTING - #if SDSORT_DYNAMIC_RAM - isDir = new uint8_t[(fileCnt + 7) >> 3]; - #elif SDSORT_USES_STACK - uint8_t isDir[(fileCnt + 7) >> 3]; - #endif - #endif + // By default re-read the names from SD for every compare + // retaining only two filenames at a time. This is very + // slow but is safest and uses minimal RAM. + char name1[LONG_FILENAME_LENGTH]; + uint16_t crmod_time_bckp; + uint16_t crmod_date_bckp; - #else // !SDSORT_USES_RAM +#ifdef INSERTSORT - uint32_t positions[fileCnt]; +#define _SORT_CMP_NODIR() (strcasecmp(name1, name2) < 0) //true if lowercase(name1) < lowercase(name2) +#define _SORT_CMP_TIME_NODIR() (((crmod_date_bckp == crmodDate) && (crmod_time_bckp > crmodTime)) || (crmod_date_bckp > crmodDate)) - // By default re-read the names from SD for every compare - // retaining only two filenames at a time. This is very - // slow but is safest and uses minimal RAM. - char name1[LONG_FILENAME_LENGTH + 1]; - uint16_t modification_time_bckp; - uint16_t modification_date_bckp; +#if HAS_FOLDER_SORTING +#define _SORT_CMP_DIR(fs) ((dir1 == filenameIsDir) ? _SORT_CMP_NODIR() : (fs < 0 ? dir1 : !dir1)) +#define _SORT_CMP_TIME_DIR(fs) ((dir1 == filenameIsDir) ? _SORT_CMP_TIME_NODIR() : (fs < 0 ? dir1 : !dir1)) +#endif - #endif - position = 0; - if (fileCnt > 1) { - // Init sort order. - for (uint16_t i = 0; i < fileCnt; i++) { - if (!IS_SD_INSERTED) return; - manage_heater(); - sort_order[i] = i; - positions[i] = position; - getfilename(i); - // If using RAM then read all filenames now. - #if SDSORT_USES_RAM - getfilename(i); - #if SDSORT_DYNAMIC_RAM - // Use dynamic method to copy long filename - sortnames[i] = strdup(LONGEST_FILENAME); - #if SDSORT_CACHE_NAMES - // When caching also store the short name, since - // we're replacing the getfilename() behavior. - sortshort[i] = strdup(filename); - #endif - #else - // Copy filenames into the static array - strcpy(sortnames[i], LONGEST_FILENAME); - modification_time[i] = modificationTime; - modification_date[i] = modificationDate; - #if SDSORT_CACHE_NAMES - strcpy(sortshort[i], filename); - #endif - #endif - // char out[30]; - // sprintf_P(out, PSTR("---- %i %s %s"), i, filenameIsDir ? "D" : " ", sortnames[i]); - // SERIAL_ECHOLN(out); - #if HAS_FOLDER_SORTING - const uint16_t bit = i & 0x07, ind = i >> 3; - if (bit == 0) isDir[ind] = 0x00; - if (filenameIsDir) isDir[ind] |= _BV(bit); - #endif - #endif - } + uint16_t counter = 0; + menu_progressbar_init(fileCnt * fileCnt / 2, _T(MSG_SORTING_FILES)); -#ifdef QUICKSORT - quicksort(0, fileCnt - 1); -#else //Qicksort not defined, use Bubble Sort - uint32_t counter = 0; - uint16_t total = 0.5*(fileCnt - 1)*(fileCnt); + for (uint16_t i = 1; i < fileCnt; ++i){ + // if (!IS_SD_INSERTED) return; + menu_progressbar_update(counter); + counter += i; - // Compare names from the array or just the two buffered names - #if SDSORT_USES_RAM - #define _SORT_CMP_NODIR() (strcasecmp(sortnames[o1], sortnames[o2]) > 0) - #define _SORT_CMP_TIME_NODIR() (((modification_date[o1] == modification_date[o2]) && (modification_time[o1] < modification_time[o2])) || \ - (modification_date[o1] < modification_date [o2])) - #else - #define _SORT_CMP_NODIR() (strcasecmp(name1, name2) > 0) //true if lowercase(name1) > lowercase(name2) - #define _SORT_CMP_TIME_NODIR() (((modification_date_bckp == modificationDate) && (modification_time_bckp > modificationTime)) || \ - (modification_date_bckp > modificationDate)) + /// pop the position + const uint16_t o1 = sort_entries[i]; + getfilename_simple(o1); + strcpy(name1, LONGEST_FILENAME); // save (or getfilename below will trounce it) + crmod_date_bckp = crmodDate; + crmod_time_bckp = crmodTime; + #if HAS_FOLDER_SORTING + bool dir1 = filenameIsDir; + #endif - #endif + /// find proper place + uint16_t j = i; + for (; j > 0; --j){ + if (!IS_SD_INSERTED) return; + + #ifdef SORTING_DUMP + for (uint16_t z = 0; z < fileCnt; z++){ + printf_P(PSTR("%2u "), sort_entries[z]); + } + MYSERIAL.println(); + #endif + + manage_heater(); + const uint16_t o2 = sort_entries[j - 1]; - #if HAS_FOLDER_SORTING - #if SDSORT_USES_RAM - // Folder sorting needs an index and bit to test for folder-ness. - const uint8_t ind1 = o1 >> 3, bit1 = o1 & 0x07, - ind2 = o2 >> 3, bit2 = o2 & 0x07; - #define _SORT_CMP_DIR(fs) \ - (((isDir[ind1] & _BV(bit1)) != 0) == ((isDir[ind2] & _BV(bit2)) != 0) \ - ? _SORT_CMP_NODIR() \ - : (isDir[fs > 0 ? ind1 : ind2] & (fs > 0 ? _BV(bit1) : _BV(bit2))) != 0) - #define _SORT_CMP_TIME_DIR(fs) \ - (((isDir[ind1] & _BV(bit1)) != 0) == ((isDir[ind2] & _BV(bit2)) != 0) \ - ? _SORT_CMP_TIME_NODIR() \ - : (isDir[fs > 0 ? ind1 : ind2] & (fs > 0 ? _BV(bit1) : _BV(bit2))) != 0) - #else - #define _SORT_CMP_DIR(fs) ((dir1 == filenameIsDir) ? _SORT_CMP_NODIR() : (fs > 0 ? dir1 : !dir1)) - #define _SORT_CMP_TIME_DIR(fs) ((dir1 == filenameIsDir) ? _SORT_CMP_TIME_NODIR() : (fs < 0 ? dir1 : !dir1)) - #endif - #endif + getfilename_simple(o2); + char *name2 = LONGEST_FILENAME; // use the string in-place + + // Sort the current pair according to settings. + if ( + #if HAS_FOLDER_SORTING + (sdSort == SD_SORT_TIME && _SORT_CMP_TIME_DIR(FOLDER_SORTING)) || (sdSort == SD_SORT_ALPHA && !_SORT_CMP_DIR(FOLDER_SORTING)) + #else + (sdSort == SD_SORT_TIME && _SORT_CMP_TIME_NODIR()) || (sdSort == SD_SORT_ALPHA && !_SORT_CMP_NODIR()) + #endif + ) + { + break; + } else { + #ifdef SORTING_DUMP + puts_P(PSTR("shift")); + #endif + sort_entries[j] = o2; + } + } + /// place the position + sort_entries[j] = o1; + } + +#else //Bubble Sort + +#define _SORT_CMP_NODIR() (strcasecmp(name1, name2) < 0) //true if lowercase(name1) < lowercase(name2) +#define _SORT_CMP_TIME_NODIR() (((crmod_date_bckp == crmodDate) && (crmod_time_bckp > crmodTime)) || (crmod_date_bckp > crmodDate)) + +#if HAS_FOLDER_SORTING +#define _SORT_CMP_DIR(fs) ((dir1 == filenameIsDir) ? _SORT_CMP_NODIR() : (fs < 0 ? dir1 : !dir1)) +#define _SORT_CMP_TIME_DIR(fs) ((dir1 == filenameIsDir) ? _SORT_CMP_TIME_NODIR() : (fs < 0 ? dir1 : !dir1)) +#endif + + uint16_t counter = 0; + menu_progressbar_init(0.5*(fileCnt - 1)*(fileCnt), _T(MSG_SORTING_FILES)); for (uint16_t i = fileCnt; --i;) { if (!IS_SD_INSERTED) return; bool didSwap = false; - #if !SDSORT_USES_RAM //show progresss bar only if slow sorting method is used - int8_t percent = (counter * 100) / total;//((counter * 100) / pow((fileCnt-1),2)); - for (int column = 0; column < 20; column++) { - if (column < (percent / 5)) - { - lcd_set_cursor(column, 2); - lcd_print('\x01'); //simple progress bar - } - } + menu_progressbar_update(counter); counter++; - #endif - //MYSERIAL.println(int(i)); for (uint16_t j = 0; j < i; ++j) { if (!IS_SD_INSERTED) return; + #ifdef SORTING_DUMP + for (uint16_t z = 0; z < fileCnt; z++) + { + printf_P(PSTR("%2u "), sort_entries[z]); + } + MYSERIAL.println(); + #endif manage_heater(); - const uint16_t o1 = sort_order[j], o2 = sort_order[j + 1]; + const uint16_t o1 = sort_entries[j], o2 = sort_entries[j + 1]; - // The most economical method reads names as-needed - // throughout the loop. Slow if there are many. - #if !SDSORT_USES_RAM counter++; - getfilename_simple(positions[o1]); + getfilename_simple(o1); strcpy(name1, LONGEST_FILENAME); // save (or getfilename below will trounce it) - modification_date_bckp = modificationDate; - modification_time_bckp = modificationTime; + crmod_date_bckp = crmodDate; + crmod_time_bckp = crmodTime; #if HAS_FOLDER_SORTING bool dir1 = filenameIsDir; #endif - getfilename_simple(positions[o2]); + getfilename_simple(o2); char *name2 = LONGEST_FILENAME; // use the string in-place - #endif // !SDSORT_USES_RAM - // Sort the current pair according to settings. if ( #if HAS_FOLDER_SORTING - (sdSort == SD_SORT_TIME && _SORT_CMP_TIME_DIR(FOLDER_SORTING)) || (sdSort == SD_SORT_ALPHA && _SORT_CMP_DIR(FOLDER_SORTING)) + (sdSort == SD_SORT_TIME && _SORT_CMP_TIME_DIR(FOLDER_SORTING)) || (sdSort == SD_SORT_ALPHA && !_SORT_CMP_DIR(FOLDER_SORTING)) #else - (sdSort == SD_SORT_TIME && _SORT_CMP_TIME_NODIR()) || (sdSort == SD_SORT_ALPHA && _SORT_CMP_NODIR()) + (sdSort == SD_SORT_TIME && _SORT_CMP_TIME_NODIR()) || (sdSort == SD_SORT_ALPHA && !_SORT_CMP_NODIR()) #endif ) { - sort_order[j] = o2; - sort_order[j + 1] = o1; + #ifdef SORTING_DUMP + puts_P(PSTR("swap")); + #endif + + sort_entries[j] = o2; + sort_entries[j + 1] = o1; didSwap = true; } } if (!didSwap) break; } //end of bubble sort loop #endif - // Using RAM but not keeping names around - #if (SDSORT_USES_RAM && !SDSORT_CACHE_NAMES) - #if SDSORT_DYNAMIC_RAM - for (uint16_t i = 0; i < fileCnt; ++i) free(sortnames[i]); - #if HAS_FOLDER_SORTING - free(isDir); + +#ifdef SORTING_SPEEDTEST + printf_P(PSTR("sortingSpeedtestTimer:%lu\n"), sortingSpeedtestTimer.elapsed()); +#endif //SORTING_SPEEDTEST + + #ifdef SORTING_DUMP + for (uint16_t z = 0; z < fileCnt; z++) + printf_P(PSTR("%2u "), sort_entries[z]); + SERIAL_PROTOCOLLN(); #endif - #endif - #endif - } - else { - sort_order[0] = 0; - #if (SDSORT_USES_RAM && SDSORT_CACHE_NAMES) - getfilename(0); - #if SDSORT_DYNAMIC_RAM - sortnames = new char*[1]; - sortnames[0] = strdup(LONGEST_FILENAME); // malloc - sortshort = new char*[1]; - sortshort[0] = strdup(filename); // malloc - isDir = new uint8_t[1]; - #else - strcpy(sortnames[0], LONGEST_FILENAME); - strcpy(sortshort[0], filename); - #endif - isDir[0] = filenameIsDir ? 0x01 : 0x00; - #endif - } - sort_count = fileCnt; + menu_progressbar_finish(); + } } -#if !SDSORT_USES_RAM //show progresss bar only if slow sorting method is used - for (int column = 0; column <= 19; column++) - { - lcd_set_cursor(column, 2); - lcd_print('\x01'); //simple progress bar - } - _delay(300); - lcd_set_degree(); - lcd_clear(); -#endif - lcd_update(2); + KEEPALIVE_STATE(NOT_BUSY); - lcd_timeoutToStatus.start(); } void CardReader::flush_presort() { - if (sort_count > 0) { - #if SDSORT_DYNAMIC_RAM - delete sort_order; - #if SDSORT_CACHE_NAMES - for (uint8_t i = 0; i < sort_count; ++i) { - free(sortshort[i]); // strdup - free(sortnames[i]); // strdup - } - delete sortshort; - delete sortnames; - #endif - #endif - sort_count = 0; - } + sort_count = 0; + lastSortedFilePosition = 0; } #endif // SDCARD_SORT_ALPHA @@ -978,18 +1007,17 @@ void CardReader::flush_presort() { void CardReader::printingHasFinished() { st_synchronize(); + file.close(); + if(file_subcall_ctr>0) //heading up to a parent file that called current as a procedure. { - file.close(); file_subcall_ctr--; - openFile(filenames[file_subcall_ctr],true,true); + openFileReadFilteredGcode(filenames[file_subcall_ctr],true); setIndex(filespos[file_subcall_ctr]); startFileprint(); } else { - quickStop(); - file.close(); sdprinting = false; if(SD_FINISHED_STEPPERRELEASE) { diff --git a/Firmware/cardreader.h b/Firmware/cardreader.h index 9a7d0f697..9fb3f0b4f 100644 --- a/Firmware/cardreader.h +++ b/Firmware/cardreader.h @@ -1,66 +1,81 @@ #ifndef CARDREADER_H #define CARDREADER_H +#define SDSUPPORT + #ifdef SDSUPPORT -#define MAX_DIR_DEPTH 10 +#define MAX_DIR_DEPTH 6 #include "SdFile.h" -enum LsAction {LS_SerialPrint,LS_Count,LS_GetFilename}; class CardReader { public: CardReader(); - void initsd(); + enum LsAction : uint8_t + { + LS_SerialPrint, + LS_Count, + LS_GetFilename, + }; + struct ls_param + { + bool LFN : 1; + bool timestamp : 1; + inline ls_param():LFN(0), timestamp(0) { } + inline ls_param(bool LFN, bool timestamp):LFN(LFN), timestamp(timestamp) { } + } __attribute__((packed)); + + void initsd(bool doPresort = true); void write_command(char *buf); void write_command_no_newline(char *buf); //files auto[0-9].g on the sd card are performed in a row //this is to delay autostart and hence the initialisaiton of the sd card to some seconds after the normal init, so the device is available quick after a reset void checkautostart(bool x); - void openFile(const char* name,bool read,bool replace_current=true); + void openFileWrite(const char* name); + void openFileReadFilteredGcode(const char* name, bool replace_current = false); void openLogFile(const char* name); void removeFile(const char* name); void closefile(bool store_location=false); void release(); void startFileprint(); uint32_t getFileSize(); - void getStatus(); + void getStatus(bool arg_P); void printingHasFinished(); void getfilename(uint16_t nr, const char* const match=NULL); - void getfilename_simple(uint32_t position, const char * const match = NULL); + void getfilename_simple(uint16_t entry, const char * const match = NULL); + void getfilename_next(uint32_t position, const char * const match = NULL); uint16_t getnrfilenames(); void getAbsFilename(char *t); + void printAbsFilenameFast(); void getDirName(char* name, uint8_t level); - uint16_t getWorkDirDepth(); + uint8_t getWorkDirDepth(); - void ls(); - void chdir(const char * relpath); + void ls(ls_param params); + bool chdir(const char * relpath, bool doPresort); void updir(); - void setroot(); + void setroot(bool doPresort); #ifdef SDCARD_SORT_ALPHA void presort(); - #ifdef SDSORT_QUICKSORT - void swap(uint8_t left, uint8_t right); - void quicksort(uint8_t left, uint8_t right); - #endif //SDSORT_QUICKSORT - void getfilename_sorted(const uint16_t nr); - #if SDSORT_GCODE - FORCE_INLINE void setSortOn(bool b) { sort_alpha = b; presort(); } - FORCE_INLINE void setSortFolders(int i) { sort_folders = i; presort(); } - //FORCE_INLINE void setSortReverse(bool b) { sort_reverse = b; } - #endif + void getfilename_sorted(const uint16_t nr, uint8_t sdSort); + void getfilename_afterMaxSorting(uint16_t entry, const char * const match = NULL); #endif FORCE_INLINE bool isFileOpen() { return file.isOpen(); } - FORCE_INLINE bool eof() { return sdpos>=filesize ;}; - FORCE_INLINE int16_t get() { sdpos = file.curPosition();return (int16_t)file.read();}; - FORCE_INLINE void setIndex(long index) {sdpos = index;file.seekSet(index);}; + bool eof() { return sdpos>=filesize; } + FORCE_INLINE int16_t getFilteredGcodeChar() + { + int16_t c = (int16_t)file.readFilteredGcode(); + sdpos = file.curPosition(); + return c; + }; + void setIndex(long index) {sdpos = index;file.seekSetFilteredGcode(index);}; FORCE_INLINE uint8_t percentDone(){if(!isFileOpen()) return 0; if(filesize) return sdpos/((filesize+99)/100); else return 0;}; FORCE_INLINE char* getWorkDirName(){workDir.getFilename(filename);return filename;}; FORCE_INLINE uint32_t get_sdpos() { if (!isFileOpen()) return 0; else return(sdpos); }; @@ -74,58 +89,27 @@ public: bool logging; bool sdprinting ; bool cardOK ; - char filename[13]; - uint16_t modificationTime, modificationDate; - uint32_t cluster, position; + char filename[FILENAME_LENGTH]; + // There are scenarios when simple modification time is not enough (on MS Windows) + // Therefore these timestamps hold the most recent one of creation/modification date/times + uint16_t crmodTime, crmodDate; + uint32_t /* cluster, */ position; char longFilename[LONG_FILENAME_LENGTH]; bool filenameIsDir; int lastnr; //last number of the autostart; +#ifdef SDCARD_SORT_ALPHA + bool presort_flag; +#endif // SDCARD_SORT_ALPHA + char dir_names[MAX_DIR_DEPTH][9]; private: SdFile root,*curDir,workDir,workDirParents[MAX_DIR_DEPTH]; - uint16_t workDirDepth; + uint8_t workDirDepth; // Sort files and folders alphabetically. #ifdef SDCARD_SORT_ALPHA uint16_t sort_count; // Count of sorted items in the current directory - #if SDSORT_GCODE - bool sort_alpha; // Flag to enable / disable the feature - int sort_folders; // Flag to enable / disable folder sorting - //bool sort_reverse; // Flag to enable / disable reverse sorting - #endif - - // By default the sort index is static - #if SDSORT_DYNAMIC_RAM - uint8_t *sort_order; - #else - uint8_t sort_order[SDSORT_LIMIT]; - #endif - // Cache filenames to speed up SD menus. - #if SDSORT_USES_RAM - - // If using dynamic ram for names, allocate on the heap. - #if SDSORT_CACHE_NAMES - #if SDSORT_DYNAMIC_RAM - char **sortshort, **sortnames; - #else - char sortshort[SDSORT_LIMIT][FILENAME_LENGTH]; - char sortnames[SDSORT_LIMIT][FILENAME_LENGTH]; - #endif - #elif !SDSORT_USES_STACK - char sortnames[SDSORT_LIMIT][FILENAME_LENGTH]; - uint16_t modification_time[SDSORT_LIMIT]; - uint16_t modification_date[SDSORT_LIMIT]; - #endif - - // Folder sorting uses an isDir array when caching items. - #if HAS_FOLDER_SORTING - #if SDSORT_DYNAMIC_RAM - uint8_t *isDir; - #elif (SDSORT_CACHE_NAMES) || !(SDSORT_USES_STACK) - uint8_t isDir[(SDSORT_LIMIT + 7) >> 3]; - #endif - #endif - - #endif // SDSORT_USES_RAM + uint16_t sort_entries[SDSORT_LIMIT]; + uint16_t lastSortedFilePosition; #endif // SDCARD_SORT_ALPHA @@ -144,17 +128,13 @@ private: char filenames[SD_PROCEDURE_DEPTH][MAXPATHNAMELENGTH]; uint32_t filesize; //int16_t n; - unsigned long autostart_atmillis; + ShortTimer autostart_atmillis; uint32_t sdpos ; - bool autostart_stilltocheck; //the sd start is delayed, because otherwise the serial cannot answer fast enought to make contact with the hostsoftware. - - LsAction lsAction; //stored for recursion. - int16_t nrFiles; //counter for the files in the current directory and recycled as position counter for getting the nrFiles'th name in the directory. - char* diveDirName; + uint16_t nrFiles; //counter for the files in the current directory and recycled as position counter for getting the nrFiles'th name in the directory. - void diveSubfolder (const char *fileName, SdFile& dir); - void lsDive(const char *prepend, SdFile parent, const char * const match=NULL); + bool diveSubfolder (const char *&fileName); + void lsDive(const char *prepend, SdFile parent, const char * const match=NULL, LsAction lsAction = LS_GetFilename, ls_param lsParams = ls_param()); #ifdef SDCARD_SORT_ALPHA void flush_presort(); #endif diff --git a/Firmware/cmdqueue.cpp b/Firmware/cmdqueue.cpp index c86935529..105d57465 100755 --- a/Firmware/cmdqueue.cpp +++ b/Firmware/cmdqueue.cpp @@ -1,8 +1,8 @@ +#include #include "cmdqueue.h" #include "cardreader.h" #include "ultralcd.h" - -extern bool Stopped; +#include "Prusa_farm.h" // Reserve BUFSIZE lines of length MAX_CMD_SIZE plus CMDBUFFER_RESERVE_FRONT. char cmdbuffer[BUFSIZE * (MAX_CMD_SIZE + 1) + CMDBUFFER_RESERVE_FRONT]; @@ -18,17 +18,16 @@ int buflen = 0; // Therefore don't remove the command from the queue in the loop() function. bool cmdbuffer_front_already_processed = false; +// Used for temporarely preventing accidental adding of Serial commands to the queue. +// For now only check_file and the fancheck pause use this. +bool cmdqueue_serial_disabled = false; + int serial_count = 0; //index of character read from serial line -boolean comment_mode = false; +bool comment_mode = false; char *strchr_pointer; // just a pointer to find chars in the command string like X, Y, Z, E, etc -unsigned long TimeSent = _millis(); -unsigned long TimeNow = _millis(); - -long gcode_N = 0; +ShortTimer serialTimeoutTimer; long gcode_LastN = 0; -long Stopped_gcode_LastN = 0; - uint32_t sdpos_atomic = 0; @@ -91,14 +90,19 @@ bool cmdqueue_pop_front() void cmdqueue_reset() { - bufindr = 0; - bufindw = 0; - buflen = 0; + while (buflen) + { + // printf_P(PSTR("dumping: \"%s\" of type %u\n"), cmdbuffer+bufindr+CMDHDRSIZE, CMDBUFFER_CURRENT_TYPE); + ClearToSend(); + cmdqueue_pop_front(); + } + bufindr = 0; + bufindw = 0; //commands are removed from command queue after process_command() function is finished //reseting command queue and enqueing new commands during some (usually long running) command processing would cause that new commands are immediately removed from queue (or damaged) //this will ensure that all new commands which are enqueued after cmdqueue reset, will be always executed - cmdbuffer_front_already_processed = true; + cmdbuffer_front_already_processed = true; } // How long a string could be pushed to the front of the command queue? @@ -149,7 +153,7 @@ static bool cmdqueue_could_enqueue_front(size_t len_asked) // len_asked does not contain the zero terminator size. // This function may update bufindw, therefore for the power panic to work, this function must be called // with the interrupts disabled! -static bool cmdqueue_could_enqueue_back(size_t len_asked, bool atomic_update = false) +static bool __attribute__((noinline)) cmdqueue_could_enqueue_back(size_t len_asked) { // MAX_CMD_SIZE has to accommodate the zero terminator. if (len_asked >= MAX_CMD_SIZE) @@ -159,61 +163,29 @@ static bool cmdqueue_could_enqueue_back(size_t len_asked, bool atomic_update = f // Full buffer. return false; - if (serial_count > 0) { - // If there is some data stored starting at bufindw, len_asked is certainly smaller than - // the allocated data buffer. Try to reserve a new buffer and to move the already received - // serial data. - // How much memory to reserve for the commands pushed to the front? - // End of the queue, when pushing to the end. - size_t endw = bufindw + len_asked + (1 + CMDHDRSIZE); - if (bufindw < bufindr) - // Simple case. There is a contiguous space between the write buffer and the read buffer. - return endw + CMDBUFFER_RESERVE_FRONT <= bufindr; - // Otherwise the free space is split between the start and end. - if (// Could one fit to the end, including the reserve? - endw + CMDBUFFER_RESERVE_FRONT <= sizeof(cmdbuffer) || - // Could one fit to the end, and the reserve to the start? - (endw <= sizeof(cmdbuffer) && CMDBUFFER_RESERVE_FRONT <= bufindr)) - return true; - // Could one fit both to the start? - if (len_asked + (1 + CMDHDRSIZE) + CMDBUFFER_RESERVE_FRONT <= bufindr) { - // Mark the rest of the buffer as used. - memset(cmdbuffer+bufindw, 0, sizeof(cmdbuffer)-bufindw); - // and point to the start. - // Be careful! The bufindw needs to be changed atomically for the power panic & filament panic to work. - if (atomic_update) - cli(); - bufindw = 0; - if (atomic_update) - sei(); - return true; - } - } else { - // How much memory to reserve for the commands pushed to the front? - // End of the queue, when pushing to the end. - size_t endw = bufindw + len_asked + (1 + CMDHDRSIZE); - if (bufindw < bufindr) - // Simple case. There is a contiguous space between the write buffer and the read buffer. - return endw + CMDBUFFER_RESERVE_FRONT <= bufindr; - // Otherwise the free space is split between the start and end. - if (// Could one fit to the end, including the reserve? - endw + CMDBUFFER_RESERVE_FRONT <= sizeof(cmdbuffer) || - // Could one fit to the end, and the reserve to the start? - (endw <= sizeof(cmdbuffer) && CMDBUFFER_RESERVE_FRONT <= bufindr)) - return true; - // Could one fit both to the start? - if (len_asked + (1 + CMDHDRSIZE) + CMDBUFFER_RESERVE_FRONT <= bufindr) { - // Mark the rest of the buffer as used. - memset(cmdbuffer+bufindw, 0, sizeof(cmdbuffer)-bufindw); - // and point to the start. - // Be careful! The bufindw needs to be changed atomically for the power panic & filament panic to work. - if (atomic_update) - cli(); - bufindw = 0; - if (atomic_update) - sei(); - return true; - } + // If there is some data stored starting at bufindw, len_asked is certainly smaller than + // the allocated data buffer. Try to reserve a new buffer and to move the already received + // serial data. + // How much memory to reserve for the commands pushed to the front? + // End of the queue, when pushing to the end. + size_t endw = bufindw + len_asked + (1 + CMDHDRSIZE); + if (bufindw < bufindr) + // Simple case. There is a contiguous space between the write buffer and the read buffer. + return endw + CMDBUFFER_RESERVE_FRONT <= bufindr; + // Otherwise the free space is split between the start and end. + if (// Could one fit to the end, including the reserve? + endw + CMDBUFFER_RESERVE_FRONT <= sizeof(cmdbuffer) || + // Could one fit to the end, and the reserve to the start? + (endw <= sizeof(cmdbuffer) && CMDBUFFER_RESERVE_FRONT <= bufindr)) + return true; + // Could one fit both to the start? + if (len_asked + (1 + CMDHDRSIZE) + CMDBUFFER_RESERVE_FRONT <= bufindr) { + // Mark the rest of the buffer as used. + memset(cmdbuffer+bufindw, 0, sizeof(cmdbuffer)-bufindw); + // and point to the start. + // Be careful! The bufindw needs to be changed atomically for the power panic & filament panic to work. + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { bufindw = 0; } + return true; } return false; } @@ -362,26 +334,10 @@ void repeatcommand_front() cmdbuffer_front_already_processed = true; } -bool is_buffer_empty() -{ - if (buflen == 0) return true; - else return false; -} - -void proc_commands() { - if (buflen) - { - process_commands(); - if (!cmdbuffer_front_already_processed) - cmdqueue_pop_front(); - cmdbuffer_front_already_processed = false; - } -} - void get_command() { // Test and reserve space for the new command string. - if (! cmdqueue_could_enqueue_back(MAX_CMD_SIZE - 1, true)) + if (! cmdqueue_could_enqueue_back(MAX_CMD_SIZE - 1)) return; if (MYSERIAL.available() == RX_BUFFER_SIZE - 1) { //compare number of chars buffered in rx buffer with rx buffer size @@ -390,17 +346,11 @@ void get_command() } // start of serial line processing loop - while ((MYSERIAL.available() > 0 && !saved_printing) || (MYSERIAL.available() > 0 && isPrintPaused)) { //is print is saved (crash detection or filament detection), dont process data from serial line + while (((MYSERIAL.available() > 0 && !saved_printing) || (MYSERIAL.available() > 0 && isPrintPaused)) && !cmdqueue_serial_disabled) { //is print is saved (crash detection or filament detection), dont process data from serial line char serial_char = MYSERIAL.read(); -/* if (selectedSerialPort == 1) - { - selectedSerialPort = 0; - MYSERIAL.write(serial_char); // for debuging serial line 2 in farm_mode - selectedSerialPort = 1; - } */ //RP - removed - TimeSent = _millis(); - TimeNow = _millis(); + + serialTimeoutTimer.start(); if (serial_char < 0) // Ignore extended ASCII characters. These characters have no meaning in the G-code apart from the file names @@ -416,22 +366,23 @@ void get_command() comment_mode = false; //for new command return; } - cmdbuffer[bufindw+serial_count+CMDHDRSIZE] = 0; //terminate string + cmdbuffer[bufindw+serial_count+CMDHDRSIZE] = 0; // terminate string + char* cmd_head = cmdbuffer+bufindw+CMDHDRSIZE; // current command pointer + char* cmd_start = cmd_head; // pointer past the line number (if any) + if(!comment_mode){ - - gcode_N = 0; + long gcode_N = -1; // seen line number // Line numbers must be first in buffer + if (*cmd_head == 'N') { - if ((strstr(cmdbuffer+bufindw+CMDHDRSIZE, "PRUSA") == NULL) && - (cmdbuffer[bufindw+CMDHDRSIZE] == 'N')) { + // Line number met: decode the number, then move cmd_start past all spaces. + gcode_N = (strtol(cmd_head+1, &cmd_start, 10)); + while (*cmd_start == ' ') ++cmd_start; - // Line number met. When sending a G-code over a serial line, each line may be stamped with its index, - // and Marlin tests, whether the successive lines are stamped with an increasing line number ID - gcode_N = (strtol(cmdbuffer+bufindw+CMDHDRSIZE+1, NULL, 10)); - if(gcode_N != gcode_LastN+1 && (strstr_P(cmdbuffer+bufindw+CMDHDRSIZE, PSTR("M110")) == NULL) ) { - // M110 - set current line number. - // Line numbers not sent in succession. + // Test whether the successive lines are stamped with an increasing line number ID. + if(gcode_N != gcode_LastN+1 && strncmp_P(cmd_start, PSTR("M110"), 4)) { + // Line numbers not sent in succession and M110 not seen. SERIAL_ERROR_START; SERIAL_ERRORRPGM(_n("Line Number is not Last Line Number+1, Last Line: "));////MSG_ERR_LINE_NO SERIAL_ERRORLN(gcode_LastN); @@ -441,13 +392,13 @@ void get_command() return; } - if((strchr_pointer = strchr(cmdbuffer+bufindw+CMDHDRSIZE, '*')) != NULL) + if((strchr_pointer = strchr(cmd_start, '*')) != NULL) { byte checksum = 0; - char *p = cmdbuffer+bufindw+CMDHDRSIZE; + char *p = cmd_head; while (p != strchr_pointer) checksum = checksum^(*p++); - if (int(strtol(strchr_pointer+1, NULL, 10)) != int(checksum)) { + if (code_value_short() != (int16_t)checksum) { SERIAL_ERROR_START; SERIAL_ERRORRPGM(_n("checksum mismatch, Last Line: "));////MSG_ERR_CHECKSUM_MISMATCH SERIAL_ERRORLN(gcode_LastN); @@ -467,54 +418,80 @@ void get_command() serial_count = 0; return; } - - // Don't parse N again with code_seen('N') - cmdbuffer[bufindw + CMDHDRSIZE] = '$'; - //if no errors, continue parsing - gcode_LastN = gcode_N; } - // if we don't receive 'N' but still see '*' - if ((cmdbuffer[bufindw + CMDHDRSIZE] != 'N') && (cmdbuffer[bufindw + CMDHDRSIZE] != '$') && (strchr(cmdbuffer+bufindw+CMDHDRSIZE, '*') != NULL)) + else { + // move cmd_start past all spaces + while (*cmd_start == ' ') ++cmd_start; - SERIAL_ERROR_START; - SERIAL_ERRORRPGM(_n("No Line Number with checksum, Last Line: "));////MSG_ERR_NO_LINENUMBER_WITH_CHECKSUM - SERIAL_ERRORLN(gcode_LastN); - FlushSerialRequestResend(); + // if we didn't receive 'N' but still see '*' + if (strchr(cmd_start, '*') != NULL) + { + SERIAL_ERROR_START; + SERIAL_ERRORRPGM(_n("No Line Number with checksum, Last Line: "));////MSG_ERR_NO_LINENUMBER_WITH_CHECKSUM + SERIAL_ERRORLN(gcode_LastN); + FlushSerialRequestResend(); + serial_count = 0; + return; + } + } + + // Handle KILL early, even when Stopped + if(strcmp_P(cmd_start, PSTR("M112")) == 0) + kill(MSG_M112_KILL, 2); + + // Bypass Stopped for some commands + bool allow_when_stopped = false; + if(strncmp_P(cmd_start, PSTR("M310"), 4) == 0) + allow_when_stopped = true; + + // Handle the USB timer + if ((*cmd_start == 'G') && !(IS_SD_PRINTING)) + usb_timer.start(); + + if (allow_when_stopped == false && Stopped == true) { + // Stopped can be set either during error states (thermal error: cannot continue), or + // when a printer-initiated action is processed. In such case the printer will send to + // the host an action, but cannot know if the action has been processed while new + // commands are being sent. In this situation we just drop the command while issuing + // periodic "busy" messages in the main loop. Since we're not incrementing the received + // line number, a request for resend will happen (if necessary), ensuring we don't skip + // commands whenever Stopped is cleared and processing resumes. serial_count = 0; return; } - if ((strchr_pointer = strchr(cmdbuffer+bufindw+CMDHDRSIZE, 'G')) != NULL) { - if (! IS_SD_PRINTING) { - usb_printing_counter = 10; - is_usb_printing = true; - } - if (Stopped == true) { - int gcode = strtol(strchr_pointer+1, NULL, 10); - if (gcode >= 0 && gcode <= 3) { - SERIAL_ERRORLNRPGM(MSG_ERR_STOPPED); - LCD_MESSAGERPGM(_T(MSG_STOPPED)); - } - } - } // end of 'G' command - //If command was e-stop process now - if(strcmp(cmdbuffer+bufindw+CMDHDRSIZE, "M112") == 0) - kill(MSG_M112_KILL, 2); - - // Store the current line into buffer, move to the next line. + // Command is complete: store the current line into buffer, move to the next line. + // Store type of entry - cmdbuffer[bufindw] = gcode_N ? CMDBUFFER_CURRENT_TYPE_USB_WITH_LINENR : CMDBUFFER_CURRENT_TYPE_USB; + cmdbuffer[bufindw] = gcode_N >= 0 ? CMDBUFFER_CURRENT_TYPE_USB_WITH_LINENR : CMDBUFFER_CURRENT_TYPE_USB; + #ifdef CMDBUFFER_DEBUG SERIAL_ECHO_START; SERIAL_ECHOPGM("Storing a command line to buffer: "); - SERIAL_ECHO(cmdbuffer+bufindw+CMDHDRSIZE); + SERIAL_ECHO(cmd_start); SERIAL_ECHOLNPGM(""); #endif /* CMDBUFFER_DEBUG */ - bufindw += strlen(cmdbuffer+bufindw+CMDHDRSIZE) + (1 + CMDHDRSIZE); + + // Store the command itself (without line number or checksum) + size_t cmd_len; + if (cmd_head == cmd_start) + cmd_len = strlen(cmd_start) + 1; + else { + // strip the line number + cmd_len = 0; + do { cmd_head[cmd_len] = cmd_start[cmd_len]; } + while (cmd_head[cmd_len++]); + } + bufindw += cmd_len + CMDHDRSIZE; if (bufindw == sizeof(cmdbuffer)) bufindw = 0; ++ buflen; + + // Update the processed gcode line + if (gcode_N >= 0) + gcode_LastN = gcode_N; + #ifdef CMDBUFFER_DEBUG SERIAL_ECHOPGM("Number of commands in the buffer: "); SERIAL_ECHO(buflen); @@ -524,7 +501,7 @@ void get_command() serial_count = 0; //clear buffer // Don't call cmdqueue_could_enqueue_back if there are no characters waiting // in the queue, as this function will reserve the memory. - if (MYSERIAL.available() == 0 || ! cmdqueue_could_enqueue_back(MAX_CMD_SIZE-1, true)) + if (MYSERIAL.available() == 0 || ! cmdqueue_could_enqueue_back(MAX_CMD_SIZE-1)) return; } // end of "end of line" processing else { @@ -534,26 +511,15 @@ void get_command() } } // end of serial line processing loop - if(farm_mode){ - TimeNow = _millis(); - if ( ((TimeNow - TimeSent) > 800) && (serial_count > 0) ) { - cmdbuffer[bufindw+serial_count+CMDHDRSIZE] = 0; - - bufindw += strlen(cmdbuffer+bufindw+CMDHDRSIZE) + (1 + CMDHDRSIZE); - if (bufindw == sizeof(cmdbuffer)) - bufindw = 0; - ++ buflen; - - serial_count = 0; - - SERIAL_ECHOPGM("TIMEOUT:"); - //memset(cmdbuffer, 0 , sizeof(cmdbuffer)); - return; - } + if (serial_count > 0 && serialTimeoutTimer.expired(farm_mode ? 800 : 2000)) { + comment_mode = false; + serial_count = 0; + SERIAL_ECHOLNPGM("RX timeout"); + return; } #ifdef SDSUPPORT - if(!card.sdprinting || serial_count!=0){ + if(!card.sdprinting || !card.isFileOpen() || serial_count!=0){ // If there is a half filled buffer from serial line, wait until return before // continuing with the serial line. return; @@ -575,15 +541,14 @@ void get_command() sd_count.value = 0; // Reads whole lines from the SD card. Never leaves a half-filled line in the cmdbuffer. while( !card.eof() && !stop_buffering) { - int16_t n=card.get(); + int16_t n=card.getFilteredGcodeChar(); char serial_char = (char)n; - if(serial_char == '\n' || - serial_char == '\r' || - ((serial_char == '#' || serial_char == ':') && comment_mode == false) || - serial_count >= (MAX_CMD_SIZE - 1) || n==-1) - { - if(card.eof()) break; - + if( serial_char == '\n' + || serial_char == '\r' + || serial_char == '#' + || serial_count >= (MAX_CMD_SIZE - 1) + || n==-1 + ){ if(serial_char=='#') stop_buffering=true; @@ -592,14 +557,13 @@ void get_command() // This is either an empty line, or a line with just a comment. // Continue to the following line, and continue accumulating the number of bytes // read from the sdcard into sd_count, - // so that the lenght of the already read empty lines and comments will be added + // so that the length of the already read empty lines and comments will be added // to the following non-empty line. - comment_mode = false; - continue; //if empty line + return; // prevent cycling indefinitely - let manage_heaters do their job } // The new command buffer could be updated non-atomically, because it is not yet considered // to be inside the active queue. - sd_count.value = (card.get_sdpos()+1) - sdpos_atomic; + sd_count.value = card.get_sdpos() - sdpos_atomic; cmdbuffer[bufindw] = CMDBUFFER_CURRENT_TYPE_SDCARD; cmdbuffer[bufindw+1] = sd_count.lohi.lo; cmdbuffer[bufindw+2] = sd_count.lohi.hi; @@ -611,10 +575,10 @@ void get_command() // MYSERIAL.print(sd_count.value, DEC); // SERIAL_ECHOPGM(") "); // SERIAL_ECHOLN(cmdbuffer+bufindw+CMDHDRSIZE); -// SERIAL_ECHOPGM("cmdbuffer:"); -// MYSERIAL.print(cmdbuffer); -// SERIAL_ECHOPGM("buflen:"); -// MYSERIAL.print(buflen+1); +// SERIAL_ECHOPGM("cmdbuffer:"); +// MYSERIAL.print(cmdbuffer); +// SERIAL_ECHOPGM("buflen:"); +// MYSERIAL.print(buflen+1); sd_count.value = 0; cli(); @@ -624,21 +588,24 @@ void get_command() // or a 115200 Bd serial line receive interrupt, which will not trigger faster than 12kHz. ++ buflen; bufindw += len; - sdpos_atomic = card.get_sdpos()+1; + sdpos_atomic = card.get_sdpos(); if (bufindw == sizeof(cmdbuffer)) bufindw = 0; sei(); comment_mode = false; //for new command serial_count = 0; //clear buffer + + if(card.eof()) break; + // The following line will reserve buffer space if available. - if (! cmdqueue_could_enqueue_back(MAX_CMD_SIZE-1, true)) + if (! cmdqueue_could_enqueue_back(MAX_CMD_SIZE-1)) return; } else { - if(serial_char == ';') comment_mode = true; - else if(!comment_mode) cmdbuffer[bufindw+CMDHDRSIZE+serial_count++] = serial_char; + // there are no comments coming from the filtered file + cmdbuffer[bufindw+CMDHDRSIZE+serial_count++] = serial_char; } } if(card.eof()) @@ -649,6 +616,10 @@ void get_command() // cleared by printingHasFinished after peforming all remaining moves. if(!cmdqueue_calc_sd_length()) { + // queue is complete, but before we process EOF commands prevent + // re-entry by disabling SD processing from any st_synchronize call + card.closefile(); + SERIAL_PROTOCOLLNRPGM(_n("Done printing file"));////MSG_FILE_PRINTED stoptime=_millis(); char time[30]; @@ -666,10 +637,7 @@ void get_command() card.checkautostart(true); if (farm_mode) - { - prusa_statistics(6); - lcd_commands_type = LcdCommands::FarmModeConfirm; - } + prusa_statistics(6); } } diff --git a/Firmware/cmdqueue.h b/Firmware/cmdqueue.h index 13185f179..2ea684529 100644 --- a/Firmware/cmdqueue.h +++ b/Firmware/cmdqueue.h @@ -35,6 +35,7 @@ extern char cmdbuffer[BUFSIZE * (MAX_CMD_SIZE + 1) + CMDBUFFER_RESERVE_FRONT]; extern size_t bufindr; extern int buflen; extern bool cmdbuffer_front_already_processed; +extern bool cmdqueue_serial_disabled; // Type of a command, which is to be executed right now. #define CMDBUFFER_CURRENT_TYPE (cmdbuffer[bufindr]) @@ -48,15 +49,10 @@ extern bool cmdbuffer_front_already_processed; extern uint32_t sdpos_atomic; extern int serial_count; -extern boolean comment_mode; +extern bool comment_mode; extern char *strchr_pointer; -extern unsigned long TimeSent; -extern unsigned long TimeNow; - -extern long gcode_N; extern long gcode_LastN; -extern long Stopped_gcode_LastN; extern bool cmdqueue_pop_front(); extern void cmdqueue_reset(); @@ -65,30 +61,18 @@ extern void cmdqueue_dump_to_serial_single_line(int nr, const char *p); extern void cmdqueue_dump_to_serial(); #endif /* CMDBUFFER_DEBUG */ extern bool cmd_buffer_empty(); -extern void enquecommand(const char *cmd, bool from_progmem); -extern void enquecommand_front(const char *cmd, bool from_progmem); +extern void enquecommand(const char *cmd, bool from_progmem = false); +extern void enquecommand_front(const char *cmd, bool from_progmem = false); extern void repeatcommand_front(); -extern bool is_buffer_empty(); extern void get_command(); extern uint16_t cmdqueue_calc_sd_length(); // Return True if a character was found static inline bool code_seen(char code) { return (strchr_pointer = strchr(CMDBUFFER_CURRENT_STRING, code)) != NULL; } -static inline bool code_seen(const char *code) { return (strchr_pointer = strstr(CMDBUFFER_CURRENT_STRING, code)) != NULL; } +static inline bool code_seen_P(const char *code_PROGMEM) { return (strchr_pointer = strstr_P(CMDBUFFER_CURRENT_STRING, code_PROGMEM)) != NULL; } static inline float code_value() { return strtod(strchr_pointer+1, NULL);} static inline long code_value_long() { return strtol(strchr_pointer+1, NULL, 10); } static inline int16_t code_value_short() { return int16_t(strtol(strchr_pointer+1, NULL, 10)); }; static inline uint8_t code_value_uint8() { return uint8_t(strtol(strchr_pointer+1, NULL, 10)); }; -static inline float code_value_float() -{ - char* e = strchr(strchr_pointer, 'E'); - if (!e) return strtod(strchr_pointer + 1, NULL); - *e = 0; - float ret = strtod(strchr_pointer + 1, NULL); - *e = 'E'; - return ret; -} - - #endif //CMDQUEUE_H diff --git a/Firmware/config.h b/Firmware/config.h index 241a2f20b..c560b13ec 100644 --- a/Firmware/config.h +++ b/Firmware/config.h @@ -2,13 +2,16 @@ #define _CONFIG_H -#include "Configuration_prusa.h" +#include "Configuration_var.h" #include "pins.h" -#define IR_SENSOR_ANALOG (defined(VOLT_IR_PIN) && defined(IR_SENSOR)) +#if (defined(VOLT_IR_PIN) && defined(IR_SENSOR)) +// TODO: IR_SENSOR_ANALOG currently disabled as being incompatible with the new thermal regulation +// # define IR_SENSOR_ANALOG +#endif //ADC configuration -#if !IR_SENSOR_ANALOG +#ifndef IR_SENSOR_ANALOG #define ADC_CHAN_MSK 0b0000001001011111 //used AD channels bit mask (0,1,2,3,4,6,9) #define ADC_DIDR_MSK 0b0000001001011111 //AD channels DIDR mask (1 ~ disabled digital input) #define ADC_CHAN_CNT 7 //number of used channels) @@ -18,10 +21,9 @@ #define ADC_CHAN_CNT 8 //number of used channels) #endif //!IR_SENSOR_ANALOG #define ADC_OVRSAMPL 16 //oversampling multiplier -#define ADC_CALLBACK adc_ready //callback function () +#define ADC_CALLBACK adc_callback //callback function () //SWI2C configuration -#define SWI2C //#define SWI2C_SDA 20 //SDA on P3 //#define SWI2C_SCL 21 //SCL on P3 #define SWI2C_A8 @@ -29,12 +31,23 @@ #define SWI2C_TMO 2048 //2048 cycles timeout //PAT9125 configuration -#define PAT9125_SWI2C +#ifdef SWI2C_SCL +#define PAT9125_SWI2C // software I2C mode +#else +#define PAT9125_I2C // hardware I2C mode +#endif + #define PAT9125_I2C_ADDR 0x75 //ID=LO //#define PAT9125_I2C_ADDR 0x79 //ID=HI //#define PAT9125_I2C_ADDR 0x73 //ID=NC #define PAT9125_XRES 0 -#define PAT9125_YRES 240 +#define PAT9125_YRES 240 // maximum resolution (5*X cpi) +#define PAT9125_YRES_MM (5*PAT9125_YRES/25.4) // counts per mm +#define PAT9125_INVERT_X 0 //1 means flipped +#define PAT9125_INVERT_Y 1 //1 means flipped +#define PAT9125_SWAP_XY 0 //X is Y and Y is X +#define PAT9125_12B_RES 1 //8bit or 12bit signed motion data +#define PAT9125_NEW_INIT 1 //set to 1 to use the magic sequence provided by pixart. //SM4 configuration #define SM4_DEFDELAY 500 //default step delay [us] @@ -46,19 +59,73 @@ #define TMC2130_SPCR SPI_SPCR(TMC2130_SPI_RATE, 1, 1, 1, 0) #define TMC2130_SPSR SPI_SPSR(TMC2130_SPI_RATE) -//W25X20CL configuration -//pinout: -#define W25X20CL_PIN_CS 32 -//spi: -#define W25X20CL_SPI_RATE 0 // fosc/4 = 4MHz -#define W25X20CL_SPCR SPI_SPCR(W25X20CL_SPI_RATE, 1, 1, 1, 0) -#define W25X20CL_SPSR SPI_SPSR(W25X20CL_SPI_RATE) - +// This is set by the cmake build to be able to take control of +// the language flag, without breaking existing build mechanisms. +#ifndef CMAKE_CONTROL //LANG - Multi-language support -//define LANG_MODE 0 // primary language only +//#define LANG_MODE 0 // primary language only #define LANG_MODE 1 // sec. language support +#endif -#define LANG_SIZE_RESERVED 0x3000 // reserved space for secondary language (12288 bytes) +#define LANG_SIZE_RESERVED 0x3500 // reserved space for secondary language (13568 bytes). + // 0x3D00 Maximum 15616 bytes as it depends on xflash_layout.h + // 16 Languages max. per group including stock +#if (LANG_SIZE_RESERVED % 256) + #error "LANG_SIZE_RESERVED should be a multiple of a page size" +#endif +//Community language support +#define COMMUNITY_LANG_GROUP 1 + +#if (COMMUNITY_LANG_GROUP == 1) +#define COMMUNITY_LANG_GROUP1_NL // Community Dutch language +#define COMMUNITY_LANG_GROUP1_RO // Community Romanian language +#define COMMUNITY_LANG_GROUP1_HU // Community Hungarian language +#define COMMUNITY_LANG_GROUP1_HR // Community Croatian language +#define COMMUNITY_LANG_GROUP1_SK // Community Slovak language +#define COMMUNITY_LANG_GROUP1_SV // Community Swedish language +#define COMMUNITY_LANG_GROUP1_NO // Community Norwegian language +//#define COMMUNITY_LANG_GROUP1_DA // Community Danish language +//#define COMMUNITY_LANG_GROUP1_SL // Community Slovanian language +//#define COMMUNITY_LANG_GROUP1_LB // Community Luxembourgish language +#endif //COMMUNITY_LANG_GROUP 1 + +#if (COMMUNITY_LANG_GROUP == 2) +#define COMMUNITY_LANG_GROUP2_LT // Community Lithuanian language +//#define COMMUNITY_LANG_GROUP1_QR // Community new language //..use this as a template and replace 'QR' +#endif //COMMUNITY_LANG_GROUP 2 + +#if (COMMUNITY_LANG_GROUP >=1 ) +#define COMMUNITY_LANGUAGE_SUPPORT +#endif + +// Sanity checks for correct configuration of XFLASH_DUMP options +#if defined(XFLASH_DUMP) && !defined(XFLASH) +#error "XFLASH_DUMP requires XFLASH support" +#endif +#if (defined(MENU_DUMP) || defined(EMERGENCY_DUMP)) && !defined(XFLASH_DUMP) +#error "MENU_DUMP and EMERGENCY_DUMP require XFLASH_DUMP" +#endif + +// Support for serial dumps is mutually exclusive with XFLASH_DUMP features +#if defined(EMERGENCY_DUMP) && defined(EMERGENCY_SERIAL_DUMP) +#error "EMERGENCY_DUMP and EMERGENCY_SERIAL_DUMP are mutually exclusive" +#endif +#if defined(MENU_DUMP) && defined(MENU_SERIAL_DUMP) +#error "MENU_DUMP and MENU_SERIAL_DUMP are mutually exclusive" +#endif + +// Reduce internal duplication +#if defined(EMERGENCY_DUMP) || defined(EMERGENCY_SERIAL_DUMP) +#define EMERGENCY_HANDLERS +#endif + +//FARM_MODE +#if ( LANG_MODE == 0 ) && defined(XFLASH) //Save resources on EINSY and disable FARM_MODE on multi-language version +#define PRUSA_FARM +#endif //PRUSA_FARM only in english on EINSYs +#ifndef XFLASH //enable FARM_MODE on miniRAMBo boards +#define PRUSA_FARM +#endif #endif //_CONFIG_H diff --git a/Firmware/conv2str.cpp b/Firmware/conv2str.cpp deleted file mode 100644 index 10aa711e4..000000000 --- a/Firmware/conv2str.cpp +++ /dev/null @@ -1,292 +0,0 @@ -//conv2str.cpp - Float conversion utilities - -#include "conv2str.h" -#include - - -// convert float to string with +123.4 format -char conv[8]; - -char *ftostr3(const float &x) -{ - return itostr3((int)x); -} - -char *itostr2(const uint8_t &x) -{ - //sprintf(conv,"%5.1f",x); - int xx = x; - conv[0] = (xx / 10) % 10 + '0'; - conv[1] = (xx) % 10 + '0'; - conv[2] = 0; - return conv; -} - -// Convert float to string with 123.4 format, dropping sign -char *ftostr31(const float &x) -{ - int xx = x * 10; - conv[0] = (xx >= 0) ? '+' : '-'; - xx = abs(xx); - conv[1] = (xx / 1000) % 10 + '0'; - conv[2] = (xx / 100) % 10 + '0'; - conv[3] = (xx / 10) % 10 + '0'; - conv[4] = '.'; - conv[5] = (xx) % 10 + '0'; - conv[6] = 0; - return conv; -} - -// Convert float to string with 123.4 format -char *ftostr31ns(const float &x) -{ - int xx = x * 10; - //conv[0]=(xx>=0)?'+':'-'; - xx = abs(xx); - conv[0] = (xx / 1000) % 10 + '0'; - conv[1] = (xx / 100) % 10 + '0'; - conv[2] = (xx / 10) % 10 + '0'; - conv[3] = '.'; - conv[4] = (xx) % 10 + '0'; - conv[5] = 0; - return conv; -} - -char *ftostr32(const float &x) -{ - long xx = x * 100; - if (xx >= 0) - conv[0] = (xx / 10000) % 10 + '0'; - else - conv[0] = '-'; - xx = abs(xx); - conv[1] = (xx / 1000) % 10 + '0'; - conv[2] = (xx / 100) % 10 + '0'; - conv[3] = '.'; - conv[4] = (xx / 10) % 10 + '0'; - conv[5] = (xx) % 10 + '0'; - conv[6] = 0; - return conv; -} - -//// Convert float to rj string with 123.45 format -char *ftostr32ns(const float &x) { - long xx = abs(x); - conv[0] = xx >= 10000 ? (xx / 10000) % 10 + '0' : ' '; - conv[1] = xx >= 1000 ? (xx / 1000) % 10 + '0' : ' '; - conv[2] = xx >= 100 ? (xx / 100) % 10 + '0' : '0'; - conv[3] = '.'; - conv[4] = (xx / 10) % 10 + '0'; - conv[5] = xx % 10 + '0'; - return conv; -} - - -// Convert float to string with 1.234 format -char *ftostr43(const float &x, uint8_t offset) -{ - const size_t maxOffset = sizeof(conv)/sizeof(conv[0]) - 6; - if (offset>maxOffset) offset = maxOffset; - long xx = x * 1000; - if (xx >= 0) - conv[offset] = (xx / 1000) % 10 + '0'; - else - conv[offset] = '-'; - xx = abs(xx); - conv[offset + 1] = '.'; - conv[offset + 2] = (xx / 100) % 10 + '0'; - conv[offset + 3] = (xx / 10) % 10 + '0'; - conv[offset + 4] = (xx) % 10 + '0'; - conv[offset + 5] = 0; - return conv; -} - -//Float to string with 1.23 format -char *ftostr12ns(const float &x) -{ - long xx = x * 100; - - xx = abs(xx); - conv[0] = (xx / 100) % 10 + '0'; - conv[1] = '.'; - conv[2] = (xx / 10) % 10 + '0'; - conv[3] = (xx) % 10 + '0'; - conv[4] = 0; - return conv; -} - -//Float to string with 1.234 format -char *ftostr13ns(const float &x) -{ - long xx = x * 1000; - if (xx >= 0) - conv[0] = ' '; - else - conv[0] = '-'; - xx = abs(xx); - conv[1] = (xx / 1000) % 10 + '0'; - conv[2] = '.'; - conv[3] = (xx / 100) % 10 + '0'; - conv[4] = (xx / 10) % 10 + '0'; - conv[5] = (xx) % 10 + '0'; - conv[6] = 0; - return conv; -} - -// convert float to space-padded string with -_23.4_ format -char *ftostr32sp(const float &x) { - long xx = abs(x * 100); - uint8_t dig; - - if (x < 0) { // negative val = -_0 - conv[0] = '-'; - dig = (xx / 1000) % 10; - conv[1] = dig ? '0' + dig : ' '; - } - else { // positive val = __0 - dig = (xx / 10000) % 10; - if (dig) { - conv[0] = '0' + dig; - conv[1] = '0' + (xx / 1000) % 10; - } - else { - conv[0] = ' '; - dig = (xx / 1000) % 10; - conv[1] = dig ? '0' + dig : ' '; - } - } - - conv[2] = '0' + (xx / 100) % 10; // lsd always - - dig = xx % 10; - if (dig) { // 2 decimal places - conv[5] = '0' + dig; - conv[4] = '0' + (xx / 10) % 10; - conv[3] = '.'; - } - else { // 1 or 0 decimal place - dig = (xx / 10) % 10; - if (dig) { - conv[4] = '0' + dig; - conv[3] = '.'; - } - else { - conv[3] = conv[4] = ' '; - } - conv[5] = ' '; - } - conv[6] = '\0'; - return conv; -} - -char *itostr31(const int &xx) -{ - conv[0] = (xx >= 0) ? '+' : '-'; - conv[1] = (xx / 1000) % 10 + '0'; - conv[2] = (xx / 100) % 10 + '0'; - conv[3] = (xx / 10) % 10 + '0'; - conv[4] = '.'; - conv[5] = (xx) % 10 + '0'; - conv[6] = 0; - return conv; -} - -// Convert int to rj string with 123 or -12 format -char *itostr3(const int &x) -{ - int xx = x; - if (xx < 0) { - conv[0] = '-'; - xx = -xx; - } else if (xx >= 100) - conv[0] = (xx / 100) % 10 + '0'; - else - conv[0] = ' '; - if (xx >= 10) - conv[1] = (xx / 10) % 10 + '0'; - else - conv[1] = ' '; - conv[2] = (xx) % 10 + '0'; - conv[3] = 0; - return conv; -} - -// Convert int to lj string with 123 format -char *itostr3left(const int &xx) -{ - if (xx >= 100) - { - conv[0] = (xx / 100) % 10 + '0'; - conv[1] = (xx / 10) % 10 + '0'; - conv[2] = (xx) % 10 + '0'; - conv[3] = 0; - } - else if (xx >= 10) - { - conv[0] = (xx / 10) % 10 + '0'; - conv[1] = (xx) % 10 + '0'; - conv[2] = 0; - } - else - { - conv[0] = (xx) % 10 + '0'; - conv[1] = 0; - } - return conv; -} - -// Convert int to rj string with 1234 format -char *itostr4(const int &xx) { - conv[0] = xx >= 1000 ? (xx / 1000) % 10 + '0' : ' '; - conv[1] = xx >= 100 ? (xx / 100) % 10 + '0' : ' '; - conv[2] = xx >= 10 ? (xx / 10) % 10 + '0' : ' '; - conv[3] = xx % 10 + '0'; - conv[4] = 0; - return conv; -} - -// Convert float to rj string with 12345 format -char *ftostr5(const float &x) { - long xx = abs(x); - conv[0] = xx >= 10000 ? (xx / 10000) % 10 + '0' : ' '; - conv[1] = xx >= 1000 ? (xx / 1000) % 10 + '0' : ' '; - conv[2] = xx >= 100 ? (xx / 100) % 10 + '0' : ' '; - conv[3] = xx >= 10 ? (xx / 10) % 10 + '0' : ' '; - conv[4] = xx % 10 + '0'; - conv[5] = 0; - return conv; -} - -// Convert float to string with +1234.5 format -char *ftostr51(const float &x) -{ - long xx = x * 10; - conv[0] = (xx >= 0) ? '+' : '-'; - xx = abs(xx); - conv[1] = (xx / 10000) % 10 + '0'; - conv[2] = (xx / 1000) % 10 + '0'; - conv[3] = (xx / 100) % 10 + '0'; - conv[4] = (xx / 10) % 10 + '0'; - conv[5] = '.'; - conv[6] = (xx) % 10 + '0'; - conv[7] = 0; - return conv; -} - -// Convert float to string with +123.45 format -char *ftostr52(const float &x) -{ - long xx = x * 100; - conv[0] = (xx >= 0) ? '+' : '-'; - xx = abs(xx); - conv[1] = (xx / 10000) % 10 + '0'; - conv[2] = (xx / 1000) % 10 + '0'; - conv[3] = (xx / 100) % 10 + '0'; - conv[4] = '.'; - conv[5] = (xx / 10) % 10 + '0'; - conv[6] = (xx) % 10 + '0'; - conv[7] = 0; - return conv; -} - - diff --git a/Firmware/conv2str.h b/Firmware/conv2str.h deleted file mode 100644 index c6cf176d2..000000000 --- a/Firmware/conv2str.h +++ /dev/null @@ -1,28 +0,0 @@ -//conv2str.h - Float conversion utilities -#ifndef _CONV2STR_H -#define _CONV2STR_H - -#include - - -char *itostr2(const uint8_t &x); -char *itostr31(const int &xx); -char *itostr3(const int &xx); -char *itostr3left(const int &xx); -char *itostr4(const int &xx); - -char *ftostr3(const float &x); -char *ftostr31ns(const float &x); // float to string without sign character -char *ftostr31(const float &x); -char *ftostr32(const float &x); -char *ftostr32ns(const float &x); -char *ftostr43(const float &x, uint8_t offset = 0); -char *ftostr12ns(const float &x); -char *ftostr13ns(const float &x); -char *ftostr32sp(const float &x); // remove zero-padding from ftostr32 -char *ftostr5(const float &x); -char *ftostr51(const float &x); -char *ftostr52(const float &x); - - -#endif //_CONV2STR_H diff --git a/Firmware/eeprom.cpp b/Firmware/eeprom.cpp index 93098e93b..520dcf377 100644 --- a/Firmware/eeprom.cpp +++ b/Firmware/eeprom.cpp @@ -11,57 +11,22 @@ #include "language.h" -#if 0 -template -static T eeprom_read(T *address); - -template<> -char eeprom_read(char *address) -{ - return eeprom_read_byte(reinterpret_cast(address)); -} -#endif - -template -static void eeprom_write(T *address, T value); - -template<> -void eeprom_write(char *addres, char value) -{ - eeprom_write_byte(reinterpret_cast(addres), static_cast(value)); -} - - -template -static bool eeprom_is_uninitialized(T *address); - -template <> -bool eeprom_is_uninitialized(char *address) -{ - return (0xff == eeprom_read_byte(reinterpret_cast(address))); -} - -bool eeprom_is_sheet_initialized(uint8_t sheet_num) -{ - return (0xffff != eeprom_read_word(reinterpret_cast(&(EEPROM_Sheets_base-> - s[sheet_num].z_offset)))); -} - void eeprom_init() { - if (eeprom_read_byte((uint8_t*)EEPROM_POWER_COUNT) == 0xff) eeprom_write_byte((uint8_t*)EEPROM_POWER_COUNT, 0); - if (eeprom_read_byte((uint8_t*)EEPROM_CRASH_COUNT_X) == 0xff) eeprom_write_byte((uint8_t*)EEPROM_CRASH_COUNT_X, 0); - if (eeprom_read_byte((uint8_t*)EEPROM_CRASH_COUNT_Y) == 0xff) eeprom_write_byte((uint8_t*)EEPROM_CRASH_COUNT_Y, 0); - if (eeprom_read_byte((uint8_t*)EEPROM_FERROR_COUNT) == 0xff) eeprom_write_byte((uint8_t*)EEPROM_FERROR_COUNT, 0); - if (eeprom_read_word((uint16_t*)EEPROM_POWER_COUNT_TOT) == 0xffff) eeprom_write_word((uint16_t*)EEPROM_POWER_COUNT_TOT, 0); - if (eeprom_read_word((uint16_t*)EEPROM_CRASH_COUNT_X_TOT) == 0xffff) eeprom_write_word((uint16_t*)EEPROM_CRASH_COUNT_X_TOT, 0); - if (eeprom_read_word((uint16_t*)EEPROM_CRASH_COUNT_Y_TOT) == 0xffff) eeprom_write_word((uint16_t*)EEPROM_CRASH_COUNT_Y_TOT, 0); - if (eeprom_read_word((uint16_t*)EEPROM_FERROR_COUNT_TOT) == 0xffff) eeprom_write_word((uint16_t*)EEPROM_FERROR_COUNT_TOT, 0); + eeprom_init_default_byte((uint8_t*)EEPROM_POWER_COUNT, 0); + eeprom_init_default_byte((uint8_t*)EEPROM_CRASH_COUNT_X, 0); + eeprom_init_default_byte((uint8_t*)EEPROM_CRASH_COUNT_Y, 0); + eeprom_init_default_byte((uint8_t*)EEPROM_FERROR_COUNT, 0); + eeprom_init_default_word((uint16_t*)EEPROM_POWER_COUNT_TOT, 0); + eeprom_init_default_word((uint16_t*)EEPROM_CRASH_COUNT_X_TOT, 0); + eeprom_init_default_word((uint16_t*)EEPROM_CRASH_COUNT_Y_TOT, 0); + eeprom_init_default_word((uint16_t*)EEPROM_FERROR_COUNT_TOT, 0); - if (eeprom_read_word((uint16_t*)EEPROM_MMU_FAIL_TOT) == 0xffff) eeprom_update_word((uint16_t *)EEPROM_MMU_FAIL_TOT, 0); - if (eeprom_read_word((uint16_t*)EEPROM_MMU_LOAD_FAIL_TOT) == 0xffff) eeprom_update_word((uint16_t *)EEPROM_MMU_LOAD_FAIL_TOT, 0); - if (eeprom_read_byte((uint8_t*)EEPROM_MMU_FAIL) == 0xff) eeprom_update_byte((uint8_t *)EEPROM_MMU_FAIL, 0); - if (eeprom_read_byte((uint8_t*)EEPROM_MMU_LOAD_FAIL) == 0xff) eeprom_update_byte((uint8_t *)EEPROM_MMU_LOAD_FAIL, 0); + eeprom_init_default_word((uint16_t*)EEPROM_MMU_FAIL_TOT, 0); + eeprom_init_default_word((uint16_t*)EEPROM_MMU_LOAD_FAIL_TOT, 0); + eeprom_init_default_byte((uint8_t*)EEPROM_MMU_FAIL, 0); + eeprom_init_default_byte((uint8_t*)EEPROM_MMU_LOAD_FAIL, 0); + eeprom_init_default_dword((uint32_t*)EEPROM_MMU_MATERIAL_CHANGES, 0); if (eeprom_read_byte(&(EEPROM_Sheets_base->active_sheet)) == EEPROM_EMPTY_VALUE) { eeprom_update_byte(&(EEPROM_Sheets_base->active_sheet), 0); @@ -71,28 +36,38 @@ void eeprom_init() eeprom_update_word(reinterpret_cast(&(EEPROM_Sheets_base->s[0].z_offset)), last_babystep); } - for (uint_least8_t i = 0; i < (sizeof(Sheets::s)/sizeof(Sheets::s[0])); ++i) - { - bool is_uninitialized = true; - for (uint_least8_t j = 0; j < (sizeof(Sheet::name)/sizeof(Sheet::name[0])); ++j) - { - if (!eeprom_is_uninitialized(&(EEPROM_Sheets_base->s[i].name[j]))) is_uninitialized = false; - } - if(is_uninitialized) - { - SheetName sheetName; - eeprom_default_sheet_name(i,sheetName); - - for (uint_least8_t a = 0; a < sizeof(Sheet::name); ++a){ - eeprom_write(&(EEPROM_Sheets_base->s[i].name[a]), sheetName.c[a]); - } - } + // initialize the sheet names in eeprom + for (uint_least8_t i = 0; i < (sizeof(Sheets::s)/sizeof(Sheets::s[0])); i++) { + SheetName sheetName; + eeprom_default_sheet_name(i, sheetName); + eeprom_init_default_block(EEPROM_Sheets_base->s[i].name, (sizeof(Sheet::name)/sizeof(Sheet::name[0])), sheetName.c); } + if(!eeprom_is_sheet_initialized(eeprom_read_byte(&(EEPROM_Sheets_base->active_sheet)))) { eeprom_switch_to_next_sheet(); } check_babystep(); + +#ifdef PINDA_TEMP_COMP + eeprom_init_default_byte((uint8_t*)EEPROM_PINDA_TEMP_COMPENSATION, 0); +#endif //PINDA_TEMP_COMP + + eeprom_init_default_dword((uint32_t*)EEPROM_JOB_ID, 0); + eeprom_init_default_dword((uint32_t*)EEPROM_TOTALTIME, 0); + eeprom_init_default_dword((uint32_t*)EEPROM_FILAMENTUSED, 0); + eeprom_init_default_byte((uint8_t*)EEPROM_MMU_CUTTER_ENABLED, 0); + + eeprom_init_default_byte((uint8_t*)EEPROM_HEAT_BED_ON_LOAD_FILAMENT, 1); + +} + +void eeprom_adjust_bed_reset() { + eeprom_update_byte((uint8_t*)EEPROM_BED_CORRECTION_VALID, 1); + eeprom_update_byte((uint8_t*)EEPROM_BED_CORRECTION_LEFT, 0); + eeprom_update_byte((uint8_t*)EEPROM_BED_CORRECTION_RIGHT, 0); + eeprom_update_byte((uint8_t*)EEPROM_BED_CORRECTION_FRONT, 0); + eeprom_update_byte((uint8_t*)EEPROM_BED_CORRECTION_REAR, 0); } //! @brief Get default sheet name for index @@ -103,10 +78,10 @@ void eeprom_init() //! | 1 | Smooth2 | //! | 2 | Textur1 | //! | 3 | Textur2 | -//! | 4 | Custom1 | -//! | 5 | Custom2 | -//! | 6 | Custom3 | -//! | 7 | Custom4 | +//! | 4 | Satin | +//! | 5 | NylonPA | +//! | 6 | Custom1 | +//! | 7 | Custom2 | //! //! @param[in] index //! @param[out] sheetName @@ -122,42 +97,23 @@ void eeprom_default_sheet_name(uint8_t index, SheetName &sheetName) { strcpy_P(sheetName.c, PSTR("Textur")); } + else if (index < 5) + { + strcpy_P(sheetName.c, PSTR("Satin ")); + } + else if (index < 6) + { + strcpy_P(sheetName.c, PSTR("NylonPA")); + } else { strcpy_P(sheetName.c, PSTR("Custom")); } - - switch (index) + if (index <4 || index >5) { - case 0: - sheetName.c[6] = '1'; - break; - case 1: - sheetName.c[6] = '2'; - break; - case 2: - sheetName.c[6] = '1'; - break; - case 3: - sheetName.c[6] = '2'; - break; - case 4: - sheetName.c[6] = '1'; - break; - case 5: - sheetName.c[6] = '2'; - break; - case 6: - sheetName.c[6] = '3'; - break; - case 7: - sheetName.c[6] = '4'; - break; - default: - break; + sheetName.c[6] = '0' + ((index % 2)+1); + sheetName.c[7] = '\0'; } - - sheetName.c[7] = '\0'; } //! @brief Get next initialized sheet @@ -185,3 +141,97 @@ void eeprom_switch_to_next_sheet() sheet = eeprom_next_initialized_sheet(sheet); if (sheet >= 0) eeprom_update_byte(&(EEPROM_Sheets_base->active_sheet), sheet); } + +bool __attribute__((noinline)) eeprom_is_sheet_initialized(uint8_t sheet_num) { + return (eeprom_read_word(reinterpret_cast(&(EEPROM_Sheets_base->s[sheet_num].z_offset))) != EEPROM_EMPTY_VALUE16); +} + + +bool __attribute__((noinline)) eeprom_is_initialized_block(const void *__p, size_t __n) { + const uint8_t *p = (const uint8_t*)__p; + while (__n--) { + if (eeprom_read_byte(p++) != EEPROM_EMPTY_VALUE) + return true; + } + return false; +} + +void eeprom_update_block_P(const void *__src, void *__dst, size_t __n) { + const uint8_t *src = (const uint8_t*)__src; + uint8_t *dst = (uint8_t*)__dst; + while (__n--) { + eeprom_update_byte(dst++, pgm_read_byte(src++)); + } +} + +void eeprom_toggle(uint8_t *__p) { + eeprom_write_byte(__p, !eeprom_read_byte(__p)); +} + +void __attribute__((noinline)) eeprom_increment_byte(uint8_t *__p) { + eeprom_write_byte(__p, eeprom_read_byte(__p) + 1); +} + +void __attribute__((noinline)) eeprom_increment_word(uint16_t *__p) { + eeprom_write_word(__p, eeprom_read_word(__p) + 1); +} + +void __attribute__((noinline)) eeprom_increment_dword(uint32_t *__p) { + eeprom_write_dword(__p, eeprom_read_dword(__p) + 1); +} + + +void __attribute__((noinline)) eeprom_add_byte(uint8_t *__p, uint8_t add) { + eeprom_write_byte(__p, eeprom_read_byte(__p) + add); +} + +void __attribute__((noinline)) eeprom_add_word(uint16_t *__p, uint16_t add) { + eeprom_write_word(__p, eeprom_read_word(__p) + add); +} + +void __attribute__((noinline)) eeprom_add_dword(uint32_t *__p, uint32_t add) { + eeprom_write_dword(__p, eeprom_read_dword(__p) + add); +} + + +uint8_t __attribute__((noinline)) eeprom_init_default_byte(uint8_t *__p, uint8_t def) { + uint8_t val = eeprom_read_byte(__p); + if (val == EEPROM_EMPTY_VALUE) { + eeprom_write_byte(__p, def); + return def; + } + return val; +} + +uint16_t __attribute__((noinline)) eeprom_init_default_word(uint16_t *__p, uint16_t def) { + uint16_t val = eeprom_read_word(__p); + if (val == EEPROM_EMPTY_VALUE16) { + eeprom_write_word(__p, def); + return def; + } + return val; +} + +uint32_t __attribute__((noinline)) eeprom_init_default_dword(uint32_t *__p, uint32_t def) { + uint32_t val = eeprom_read_dword(__p); + if (val == EEPROM_EMPTY_VALUE32) { + eeprom_write_dword(__p, def); + return def; + } + return val; +} + +void __attribute__((noinline)) eeprom_init_default_float(float *__p, float def) { + if (eeprom_read_dword((uint32_t*)__p) == EEPROM_EMPTY_VALUE32) + eeprom_write_float(__p, def); +} + +void __attribute__((noinline)) eeprom_init_default_block(void *__p, size_t __n, const void *def) { + if (!eeprom_is_initialized_block(__p, __n)) + eeprom_update_block(def, __p, __n); +} + +void __attribute__((noinline)) eeprom_init_default_block_P(void *__p, size_t __n, const void *def) { + if (!eeprom_is_initialized_block(__p, __n)) + eeprom_update_block_P(def, __p, __n); +} diff --git a/Firmware/eeprom.h b/Firmware/eeprom.h index 5a9d7d813..008ced0bc 100644 --- a/Firmware/eeprom.h +++ b/Firmware/eeprom.h @@ -1,3 +1,13 @@ + +/** + * @file + * @author 3d-gussner + */ + /** \ingroup eeprom_table */ + + //! _This is a EEPROM table of currently implemented in Prusa firmware (dynamically generated from doxygen)._ + + #ifndef EEPROM_H #define EEPROM_H @@ -10,8 +20,8 @@ typedef struct { char name[MAX_SHEET_NAME_LENGTH]; //!< Can be null terminated, doesn't need to be null terminated int16_t z_offset; //!< Z_BABYSTEP_MIN .. Z_BABYSTEP_MAX = Z_BABYSTEP_MIN*2/1000 [mm] .. Z_BABYSTEP_MAX*2/1000 [mm] - uint8_t bed_temp; //!< 0 .. 254 [°C] - uint8_t pinda_temp; //!< 0 .. 254 [°C] + uint8_t bed_temp; //!< 0 .. 254 [°C] NOTE: currently only written-to and never used + uint8_t pinda_temp; //!< 0 .. 254 [°C] NOTE: currently only written-to and never used } Sheet; typedef struct @@ -26,9 +36,341 @@ typedef struct #ifdef __cplusplus static_assert(sizeof(Sheets) == EEPROM_SHEETS_SIZEOF, "Sizeof(Sheets) is not EEPROM_SHEETS_SIZEOF."); #endif +/** @defgroup eeprom_table EEPROM Table + * + + --------------------------------------------------------------------------------- + EEPROM 8-bit Empty value = 0xFFh 255 + + EEPROM 16-bit Empty value = 0xFFFFh 65535 + + _Italic = unused or default_ + + __Bold = Status__ + + In Default/FactoryReset column the + + - __L__ Language + - __S__ Statistics + - __P__ Shipping prep + - __M__ Service/Maintenance prep + - __S/P__ Statistics and Shipping prep + + will overwrite existing values to 0 or default. + A FactoryReset All Data will overwrite the whole EEPROM with ffh and some values will be initialized automatically, + others need a reset / reboot. + + --------------------------------------------------------------------------------- + How can you use the debug codes? + - Serial terminal like Putty. + - Octoprint does support D-codes + - _Pronterface_ does __not__ support D-codes + + ### !!! D-codes are case sensitive so please don't use upper case A,C or X in the address you want to read !!! + + #### Useful tools/links: + To convert hex to ascii https://www.rapidtables.com/convert/number/hex-to-ascii.html + + To convert hex to dec https://www.rapidtables.com/convert/number/hex-to-decimal.html + + Version: 1.0.2 + + --------------------------------------------------------------------------------- + + +|Address begin|Bit/Type | Name | Valid values | Default/FactoryReset | Description |Gcode/Function| Debug code +| :-- | :-- | :-- | :--: | :--: | :-- | :--: | :--: +| 0x0FFF 4095 | uchar | EEPROM_SILENT | 00h 0 | ffh 255 | TMC Stealth mode: __off__ / miniRambo Power mode | LCD menu | D3 Ax0fff C1 +| ^ | ^ | ^ | 01h 1 | ^ | TMC Stealth mode: __on__ / miniRambo Silent mode | ^ | ^ +| ^ | ^ | ^ | 02h 2 | ^ | miniRambo Auto mode | ^ | ^ +| 0x0FFE 4094 | uchar | EEPROM_LANG | 00h 0 | ffh 255 __L__ | English / LANG_ID_PRI | LCD menu | D3 Ax0ffe C1 +| ^ | ^ | ^ | 01h 1 | ^ | Other language LANG_ID_SEC | ^ | ^ +| 0x0FFC 4092 | uint16 | EEPROM_BABYSTEP_X | ??? | ff ffh 65535 | Babystep for X axis _unsued_ | ??? | D3 Ax0ffc C2 +| 0x0FFA 4090 | uint16 | EEPROM_BABYSTEP_Y | ??? | ff ffh 65535 | Babystep for Y axis _unsued_ | ^ | D3 Ax0ffa C2 +| 0x0FF8 4088 | uint16 | EEPROM_BABYSTEP_Z | ??? | ff ffh 65535 | Babystep for Z axis _lagacy_ | ^ | D3 Ax0ff8 C2 +| ^ | ^ | ^ | ^ | ^ | multiple values stored now in EEPROM_Sheets_base | ^ | ^ +| 0x0FF7 4087 | uint8 | EEPROM_CALIBRATION_STATUS_V1 | ffh 255 | ffh 255 | Calibration status (