Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/Duet3D/RepRapFirmware.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cproject116
-rw-r--r--.settings/org.eclipse.cdt.core.prefs8
-rw-r--r--Release/Duet-0.6-0.8.5/Edge/RepRapFirmware-1.17dev7.binbin0 -> 320124 bytes
-rw-r--r--Release/Duet-WiFi/Edge/DuetWiFiFirmware-1.17dev7.binbin0 -> 269708 bytes
-rw-r--r--src/Configuration.h7
-rw-r--r--src/GCodes/GCodeBuffer.h7
-rw-r--r--src/GCodes/GCodeMachineState.cpp2
-rw-r--r--src/GCodes/GCodeMachineState.h20
-rw-r--r--src/GCodes/GCodes.cpp6605
-rw-r--r--src/GCodes/GCodes.h28
-rw-r--r--src/GCodes/GCodes1.cpp3286
-rw-r--r--src/GCodes/GCodes2.cpp3481
-rw-r--r--src/Heating/Heat.h12
-rw-r--r--src/Movement/DDA.cpp4
-rw-r--r--src/Movement/Grid.cpp222
-rw-r--r--src/Movement/Grid.h63
-rw-r--r--src/Movement/Move.cpp143
-rw-r--r--src/Movement/Move.h15
-rw-r--r--src/Pins.h6
-rw-r--r--src/Platform.cpp154
-rw-r--r--src/Platform.h92
-rw-r--r--src/RADDS/Network.cpp8
-rw-r--r--src/RADDS/Network.h34
-rw-r--r--src/RADDS/Pins_radds.h248
-rw-r--r--src/RADDS/Webserver.h38
-rw-r--r--src/Reprap.cpp44
-rw-r--r--src/Reprap.h4
-rw-r--r--src/Storage/FileStore.cpp34
-rw-r--r--src/Storage/FileStore.h1
29 files changed, 7683 insertions, 6999 deletions
diff --git a/.cproject b/.cproject
index b2516706..18519f03 100644
--- a/.cproject
+++ b/.cproject
@@ -81,7 +81,6 @@
<option id="gnu.cpp.compiler.option.include.paths.1285689288" name="Include paths (-I)" superClass="gnu.cpp.compiler.option.include.paths" useByScannerDiscovery="false" valueType="includePath">
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/cores/arduino}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/Flash}&quot;"/>
- <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/RTCDue}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/SharedSpi}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/Storage}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/Wire}&quot;"/>
@@ -114,7 +113,7 @@
</toolChain>
</folderInfo>
<sourceEntries>
- <entry excluding="src/Duet/Lwip/lwip/src/core/ipv6|src/DuetNG|src/Duet/Lwip/lwip/test" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/>
+ <entry excluding="src/RADDS|src/Duet/Lwip/lwip/src/core/ipv6|src/DuetNG|src/Duet/Lwip/lwip/test" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/>
</sourceEntries>
</configuration>
</storageModule>
@@ -199,7 +198,6 @@
<option id="gnu.cpp.compiler.option.include.paths.251815634" name="Include paths (-I)" superClass="gnu.cpp.compiler.option.include.paths" useByScannerDiscovery="false" valueType="includePath">
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/cores/arduino}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/Flash}&quot;"/>
- <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/RTCDue}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/SharedSpi}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/Storage}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/Wire}&quot;"/>
@@ -235,14 +233,14 @@
</toolChain>
</folderInfo>
<sourceEntries>
- <entry excluding="src/Libraries/MCP4461|src/Duet" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/>
+ <entry excluding="src/RADDS|src/Libraries/MCP4461|src/Duet" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/>
</sourceEntries>
</configuration>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
</cconfiguration>
- <cconfiguration id="cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080">
- <storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080" moduleId="org.eclipse.cdt.core.settings" name="SAM4E_PROTO1">
+ <cconfiguration id="cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.1027429289">
+ <storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.1027429289" moduleId="org.eclipse.cdt.core.settings" name="RADDS">
<macros>
<stringMacro name="CoreName" type="VALUE_TEXT" value="CoreNG"/>
</macros>
@@ -257,105 +255,105 @@
</extensions>
</storageModule>
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
- <configuration artifactExtension="elf" artifactName="${ProjName}" buildArtefactType="org.eclipse.cdt.build.core.buildArtefactType.exe" buildProperties="org.eclipse.cdt.build.core.buildType=org.eclipse.cdt.build.core.buildType.release,org.eclipse.cdt.build.core.buildArtefactType=org.eclipse.cdt.build.core.buildArtefactType.exe" cleanCommand="rm -rf" description="" id="cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080" name="SAM4E_PROTO1" parent="cdt.managedbuild.config.gnu.cross.exe.release" postannouncebuildStep="Generating binary file" postbuildStep="arm-none-eabi-objcopy -O binary ${workspace_loc:/${ProjName}/${ConfigName}}/${ProjName}.elf ${workspace_loc:/${ProjName}/${ConfigName}}/${ProjName}.bin">
- <folderInfo id="cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080." name="/" resourcePath="">
- <toolChain id="cdt.managedbuild.toolchain.gnu.cross.exe.release.1721367471" name="Cross GCC" superClass="cdt.managedbuild.toolchain.gnu.cross.exe.release">
- <option id="cdt.managedbuild.option.gnu.cross.path.775185210" name="Path" superClass="cdt.managedbuild.option.gnu.cross.path" value="C:\Arduino-1.5.8\hardware\tools\gcc-arm-none-eabi-4.8.3-2014q1\bin" valueType="string"/>
- <option id="cdt.managedbuild.option.gnu.cross.prefix.662072815" name="Prefix" superClass="cdt.managedbuild.option.gnu.cross.prefix" value="arm-none-eabi-" valueType="string"/>
- <targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="cdt.managedbuild.targetPlatform.gnu.cross.1984540835" isAbstract="false" osList="all" superClass="cdt.managedbuild.targetPlatform.gnu.cross"/>
- <builder buildPath="${workspace_loc:/RepRapFirmware}/Release" id="cdt.managedbuild.builder.gnu.cross.614311341" keepEnvironmentInBuildfile="false" managedBuildOn="true" name="Gnu Make Builder" superClass="cdt.managedbuild.builder.gnu.cross"/>
- <tool id="cdt.managedbuild.tool.gnu.cross.assembler.1598889104" name="Cross GCC Assembler" superClass="cdt.managedbuild.tool.gnu.cross.assembler">
- <inputType id="cdt.managedbuild.tool.gnu.assembler.input.2008444256" superClass="cdt.managedbuild.tool.gnu.assembler.input"/>
+ <configuration artifactExtension="elf" artifactName="${ProjName}" buildArtefactType="org.eclipse.cdt.build.core.buildArtefactType.exe" buildProperties="org.eclipse.cdt.build.core.buildType=org.eclipse.cdt.build.core.buildType.release,org.eclipse.cdt.build.core.buildArtefactType=org.eclipse.cdt.build.core.buildArtefactType.exe" cleanCommand="rm -rf" description="" id="cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.1027429289" name="RADDS" parent="cdt.managedbuild.config.gnu.cross.exe.release" postannouncebuildStep="Generating binary file" postbuildStep="arm-none-eabi-objcopy -O binary ${workspace_loc:/${ProjName}/${ConfigName}}/${ProjName}.elf ${workspace_loc:/${ProjName}/${ConfigName}}/${ProjName}.bin">
+ <folderInfo id="cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.1027429289." name="/" resourcePath="">
+ <toolChain id="cdt.managedbuild.toolchain.gnu.cross.exe.release.1973208555" name="Cross GCC" superClass="cdt.managedbuild.toolchain.gnu.cross.exe.release">
+ <option id="cdt.managedbuild.option.gnu.cross.path.2092504710" name="Path" superClass="cdt.managedbuild.option.gnu.cross.path" value="C:\Arduino-1.5.8\hardware\tools\gcc-arm-none-eabi-4.8.3-2014q1\bin" valueType="string"/>
+ <option id="cdt.managedbuild.option.gnu.cross.prefix.1606498191" name="Prefix" superClass="cdt.managedbuild.option.gnu.cross.prefix" value="arm-none-eabi-" valueType="string"/>
+ <targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="cdt.managedbuild.targetPlatform.gnu.cross.342355349" isAbstract="false" osList="all" superClass="cdt.managedbuild.targetPlatform.gnu.cross"/>
+ <builder buildPath="${workspace_loc:/RepRapFirmware}/Release" id="cdt.managedbuild.builder.gnu.cross.1336978387" keepEnvironmentInBuildfile="false" managedBuildOn="true" name="Gnu Make Builder" superClass="cdt.managedbuild.builder.gnu.cross"/>
+ <tool id="cdt.managedbuild.tool.gnu.cross.assembler.863511428" name="Cross GCC Assembler" superClass="cdt.managedbuild.tool.gnu.cross.assembler">
+ <inputType id="cdt.managedbuild.tool.gnu.assembler.input.664007272" superClass="cdt.managedbuild.tool.gnu.assembler.input"/>
</tool>
- <tool commandLinePattern="${COMMAND} ${FLAGS} ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${INPUTS}" id="cdt.managedbuild.tool.gnu.cross.c.compiler.669564787" name="Cross GCC Compiler" superClass="cdt.managedbuild.tool.gnu.cross.c.compiler">
- <option defaultValue="gnu.c.optimization.level.most" id="gnu.c.compiler.option.optimization.level.925369674" name="Optimization Level" superClass="gnu.c.compiler.option.optimization.level" useByScannerDiscovery="false" valueType="enumerated"/>
- <option id="gnu.c.compiler.option.debugging.level.1437680267" name="Debug Level" superClass="gnu.c.compiler.option.debugging.level" useByScannerDiscovery="false" value="gnu.c.debugging.level.none" valueType="enumerated"/>
- <option id="gnu.c.compiler.option.misc.verbose.454077281" name="Verbose (-v)" superClass="gnu.c.compiler.option.misc.verbose" useByScannerDiscovery="false" value="false" valueType="boolean"/>
- <option id="gnu.c.compiler.option.misc.other.1262081974" name="Other flags" superClass="gnu.c.compiler.option.misc.other" useByScannerDiscovery="false" value="-c -std=gnu99 -mcpu=cortex-m4 -mthumb -ffunction-sections -fdata-sections -nostdlib --param max-inline-insns-single=500" valueType="string"/>
- <option id="gnu.c.compiler.option.include.paths.1815693236" name="Include paths (-I)" superClass="gnu.c.compiler.option.include.paths" useByScannerDiscovery="false" valueType="includePath">
+ <tool commandLinePattern="${COMMAND} ${FLAGS} ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${INPUTS}" id="cdt.managedbuild.tool.gnu.cross.c.compiler.764246283" name="Cross GCC Compiler" superClass="cdt.managedbuild.tool.gnu.cross.c.compiler">
+ <option defaultValue="gnu.c.optimization.level.most" id="gnu.c.compiler.option.optimization.level.1125289372" name="Optimization Level" superClass="gnu.c.compiler.option.optimization.level" useByScannerDiscovery="false" valueType="enumerated"/>
+ <option id="gnu.c.compiler.option.debugging.level.807229803" name="Debug Level" superClass="gnu.c.compiler.option.debugging.level" useByScannerDiscovery="false" value="gnu.c.debugging.level.none" valueType="enumerated"/>
+ <option id="gnu.c.compiler.option.misc.verbose.1747279976" name="Verbose (-v)" superClass="gnu.c.compiler.option.misc.verbose" useByScannerDiscovery="false" value="false" valueType="boolean"/>
+ <option id="gnu.c.compiler.option.misc.other.1771169870" name="Other flags" superClass="gnu.c.compiler.option.misc.other" useByScannerDiscovery="false" value="-c -std=gnu99 -mcpu=cortex-m3 -mthumb -ffunction-sections -fdata-sections -nostdlib --param max-inline-insns-single=500" valueType="string"/>
+ <option id="gnu.c.compiler.option.include.paths.425316569" name="Include paths (-I)" superClass="gnu.c.compiler.option.include.paths" useByScannerDiscovery="false" valueType="includePath">
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/cores/arduino}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/Storage}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/common/utils}&quot;"/>
- <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/common/services/ioport}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/drivers/emac}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/drivers/hsmci}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/drivers/rstc}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/drivers/rtc}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/utils}&quot;"/>
- <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/utils/cmsis/sam4e/include}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/utils/cmsis/sam3x/include}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/utils/header_files}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/utils/preprocessor}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/thirdparty/CMSIS/Include}&quot;"/>
- <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/variants/duetNG}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/variants/duet}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/src/Duet/Lwip}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/src/Duet/Lwip/lwip/src/include}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/src/Duet/EMAC}&quot;"/>
</option>
- <option id="gnu.c.compiler.option.preprocessor.def.symbols.1739570632" name="Defined symbols (-D)" superClass="gnu.c.compiler.option.preprocessor.def.symbols" useByScannerDiscovery="false" valueType="definedSymbols">
- <listOptionValue builtIn="false" value="__SAM4E8E__"/>
- <listOptionValue builtIn="false" value="CORE_NG"/>
- <listOptionValue builtIn="false" value="DUET_NG"/>
+ <option id="gnu.c.compiler.option.preprocessor.def.symbols.2017188375" name="Defined symbols (-D)" superClass="gnu.c.compiler.option.preprocessor.def.symbols" useByScannerDiscovery="false" valueType="definedSymbols">
+ <listOptionValue builtIn="false" value="__SAM3X8E__"/>
+ <listOptionValue builtIn="false" value="__RADDS__"/>
<listOptionValue builtIn="false" value="printf=iprintf"/>
- <listOptionValue builtIn="false" value="PROTOTYPE_1"/>
</option>
- <inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.1283635516" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
+ <inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.110609707" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
</tool>
- <tool id="cdt.managedbuild.tool.gnu.cross.c.linker.316456764" name="Cross GCC Linker" superClass="cdt.managedbuild.tool.gnu.cross.c.linker"/>
- <tool id="cdt.managedbuild.tool.gnu.cross.archiver.81082459" name="Cross GCC Archiver" superClass="cdt.managedbuild.tool.gnu.cross.archiver"/>
- <tool command="gcc" commandLinePattern="${COMMAND} ${FLAGS} ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${LINK_FLAGS_1} ${workspace_loc:/${CoreName}/SAM4E8E/cores/arduino/syscalls.o} ${INPUTS} ${LINK_FLAGS_2}" id="cdt.managedbuild.tool.gnu.cross.cpp.linker.1781803617" name="Cross G++ Linker" superClass="cdt.managedbuild.tool.gnu.cross.cpp.linker">
- <option id="gnu.cpp.link.option.nostdlibs.1188962244" name="No startup or default libs (-nostdlib)" superClass="gnu.cpp.link.option.nostdlibs" value="false" valueType="boolean"/>
- <option id="gnu.cpp.link.option.paths.2059496497" name="Library search path (-L)" superClass="gnu.cpp.link.option.paths" valueType="libPaths">
- <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/SAM4E_PROTO1/}&quot;"/>
+ <tool id="cdt.managedbuild.tool.gnu.cross.c.linker.1692168928" name="Cross GCC Linker" superClass="cdt.managedbuild.tool.gnu.cross.c.linker"/>
+ <tool id="cdt.managedbuild.tool.gnu.cross.archiver.1755453550" name="Cross GCC Archiver" superClass="cdt.managedbuild.tool.gnu.cross.archiver"/>
+ <tool command="gcc" commandLinePattern="${COMMAND} ${FLAGS} ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${LINK_FLAGS_1} ${workspace_loc:/${CoreName}/SAM3X8E/cores/arduino/syscalls.o} ${INPUTS} ${LINK_FLAGS_2}" id="cdt.managedbuild.tool.gnu.cross.cpp.linker.1176271302" name="Cross G++ Linker" superClass="cdt.managedbuild.tool.gnu.cross.cpp.linker">
+ <option id="gnu.cpp.link.option.nostdlibs.706270025" name="No startup or default libs (-nostdlib)" superClass="gnu.cpp.link.option.nostdlibs" value="false" valueType="boolean"/>
+ <option id="gnu.cpp.link.option.paths.1160723414" name="Library search path (-L)" superClass="gnu.cpp.link.option.paths" valueType="libPaths">
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/SAM3X8E/}&quot;"/>
</option>
- <option id="gnu.cpp.link.option.libs.1868917950" name="Libraries (-l)" superClass="gnu.cpp.link.option.libs" valueType="libs">
+ <option id="gnu.cpp.link.option.libs.1006761104" name="Libraries (-l)" superClass="gnu.cpp.link.option.libs" valueType="libs">
<listOptionValue builtIn="false" value="${CoreName}"/>
</option>
- <option id="gnu.cpp.link.option.flags.748151728" name="Linker flags" superClass="gnu.cpp.link.option.flags" value="-Os -Wl,--gc-sections -Wl,--fatal-warnings -mcpu=cortex-m3 -T${workspace_loc:/${CoreName}/variants/duetNG/linker_scripts/gcc/flash.ld} -Wl,-Map,${workspace_loc:/${ProjName}/${ConfigName}}/${ProjName}.map" valueType="string"/>
- <inputType id="cdt.managedbuild.tool.gnu.cpp.linker.input.1649038990" superClass="cdt.managedbuild.tool.gnu.cpp.linker.input">
+ <option id="gnu.cpp.link.option.flags.827167716" name="Linker flags" superClass="gnu.cpp.link.option.flags" value="-Os -Wl,--gc-sections -Wl,--fatal-warnings -mcpu=cortex-m3 -T${workspace_loc:/${CoreName}/variants/duet/linker_scripts/gcc/flash.ld} -Wl,-Map,${workspace_loc:/${ProjName}/${ConfigName}}/${ProjName}.map" valueType="string"/>
+ <inputType id="cdt.managedbuild.tool.gnu.cpp.linker.input.99895855" superClass="cdt.managedbuild.tool.gnu.cpp.linker.input">
<additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/>
<additionalInput kind="additionalinput" paths="$(LIBS)"/>
</inputType>
</tool>
- <tool command="g++" id="cdt.managedbuild.tool.gnu.cross.cpp.compiler.2138612171" name="Cross G++ Compiler" superClass="cdt.managedbuild.tool.gnu.cross.cpp.compiler">
- <option id="gnu.cpp.compiler.option.optimization.level.1445246033" name="Optimization Level" superClass="gnu.cpp.compiler.option.optimization.level" useByScannerDiscovery="false" value="gnu.cpp.compiler.optimization.level.more" valueType="enumerated"/>
- <option id="gnu.cpp.compiler.option.debugging.level.213346939" name="Debug Level" superClass="gnu.cpp.compiler.option.debugging.level" useByScannerDiscovery="false" value="gnu.cpp.compiler.debugging.level.none" valueType="enumerated"/>
- <option id="gnu.cpp.compiler.option.other.verbose.1378702640" name="Verbose (-v)" superClass="gnu.cpp.compiler.option.other.verbose" useByScannerDiscovery="false" value="false" valueType="boolean"/>
- <option id="gnu.cpp.compiler.option.other.other.1875098822" name="Other flags" superClass="gnu.cpp.compiler.option.other.other" useByScannerDiscovery="false" value="-c -std=gnu++11 -mcpu=cortex-m4 -mthumb -ffunction-sections -fdata-sections -fno-threadsafe-statics -fno-rtti -fno-exceptions -nostdlib --param max-inline-insns-single=500" valueType="string"/>
- <option id="gnu.cpp.compiler.option.include.paths.254420427" name="Include paths (-I)" superClass="gnu.cpp.compiler.option.include.paths" useByScannerDiscovery="false" valueType="includePath">
+ <tool command="g++" id="cdt.managedbuild.tool.gnu.cross.cpp.compiler.2077096750" name="Cross G++ Compiler" superClass="cdt.managedbuild.tool.gnu.cross.cpp.compiler">
+ <option id="gnu.cpp.compiler.option.optimization.level.25432549" name="Optimization Level" superClass="gnu.cpp.compiler.option.optimization.level" useByScannerDiscovery="false" value="gnu.cpp.compiler.optimization.level.more" valueType="enumerated"/>
+ <option id="gnu.cpp.compiler.option.debugging.level.1920128035" name="Debug Level" superClass="gnu.cpp.compiler.option.debugging.level" useByScannerDiscovery="false" value="gnu.cpp.compiler.debugging.level.none" valueType="enumerated"/>
+ <option id="gnu.cpp.compiler.option.other.verbose.1825237573" name="Verbose (-v)" superClass="gnu.cpp.compiler.option.other.verbose" useByScannerDiscovery="false" value="false" valueType="boolean"/>
+ <option id="gnu.cpp.compiler.option.other.other.165959132" name="Other flags" superClass="gnu.cpp.compiler.option.other.other" useByScannerDiscovery="false" value="-c -std=gnu++11 -mcpu=cortex-m3 -mthumb -ffunction-sections -fdata-sections -fno-threadsafe-statics -fno-rtti -fno-exceptions -nostdlib --param max-inline-insns-single=500" valueType="string"/>
+ <option id="gnu.cpp.compiler.option.include.paths.603435221" name="Include paths (-I)" superClass="gnu.cpp.compiler.option.include.paths" useByScannerDiscovery="false" valueType="includePath">
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/cores/arduino}&quot;"/>
- <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/Wire}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/Flash}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/SharedSpi}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/Storage}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/libraries/Wire}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/common/utils}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/common/services/clock}&quot;"/>
- <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/common/services/ioport}&quot;"/>
- <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/drivers/dmac}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/drivers/efc}&quot;"/>
- <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/drivers/pdc}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/drivers/emac}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/drivers/pmc}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/drivers/spi}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/drivers/twi}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/services/flash_efc}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/utils}&quot;"/>
- <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/utils/cmsis/sam4e/include}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/utils/cmsis/sam3x/include}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/utils/header_files}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/sam/utils/preprocessor}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/asf/thirdparty/CMSIS/Include}&quot;"/>
- <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/variants/duetNG}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${CoreName}/variants/duet}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/src}&quot;"/>
- <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/src/DuetNG}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/src/Duet}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/src/Duet/Lwip}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/src/Duet/EMAC}&quot;"/>
</option>
- <option id="gnu.cpp.compiler.option.preprocessor.def.486383061" name="Defined symbols (-D)" superClass="gnu.cpp.compiler.option.preprocessor.def" useByScannerDiscovery="false" valueType="definedSymbols">
- <listOptionValue builtIn="false" value="__SAM4E8E__"/>
- <listOptionValue builtIn="false" value="CORE_NG"/>
- <listOptionValue builtIn="false" value="DUET_NG"/>
+ <option id="gnu.cpp.compiler.option.preprocessor.def.1179781669" name="Defined symbols (-D)" superClass="gnu.cpp.compiler.option.preprocessor.def" useByScannerDiscovery="false" valueType="definedSymbols">
+ <listOptionValue builtIn="false" value="__SAM3X8E__"/>
+ <listOptionValue builtIn="false" value="__RADDS__"/>
<listOptionValue builtIn="false" value="printf=iprintf"/>
- <listOptionValue builtIn="false" value="PROTOTYPE_1"/>
</option>
- <inputType id="cdt.managedbuild.tool.gnu.cpp.compiler.input.2012484327" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.input"/>
+ <inputType id="cdt.managedbuild.tool.gnu.cpp.compiler.input.1578939493" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.input"/>
</tool>
</toolChain>
</folderInfo>
<sourceEntries>
- <entry excluding="src/Duet" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/>
+ <entry excluding="src/Duet|src/Duet/Lwip/lwip/src/core/ipv6|src/DuetNG|src/Duet/Lwip/lwip/test" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/>
</sourceEntries>
</configuration>
</storageModule>
diff --git a/.settings/org.eclipse.cdt.core.prefs b/.settings/org.eclipse.cdt.core.prefs
index 0d147d57..c941b38c 100644
--- a/.settings/org.eclipse.cdt.core.prefs
+++ b/.settings/org.eclipse.cdt.core.prefs
@@ -1,4 +1,12 @@
eclipse.preferences.version=1
+environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.1027429289/LINK_FLAGS_1/delimiter=;
+environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.1027429289/LINK_FLAGS_1/operation=append
+environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.1027429289/LINK_FLAGS_1/value=-mthumb -Wl,--cref -Wl,--check-sections -Wl,--gc-sections -Wl,--entry\=Reset_Handler -Wl,--unresolved-symbols\=report-all -Wl,--warn-common -Wl,--warn-section-align -Wl,--warn-unresolved-symbols -Wl,--start-group
+environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.1027429289/LINK_FLAGS_2/delimiter=;
+environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.1027429289/LINK_FLAGS_2/operation=append
+environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.1027429289/LINK_FLAGS_2/value=-Wl,--end-group -lm -gcc
+environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.1027429289/append=true
+environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.1027429289/appendContributed=true
environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080/LINK_FLAGS_1/delimiter=;
environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080/LINK_FLAGS_1/operation=append
environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080/LINK_FLAGS_1/value=-mthumb -Wl,--cref -Wl,--check-sections -Wl,--gc-sections -Wl,--entry\=Reset_Handler -Wl,--unresolved-symbols\=report-all -Wl,--warn-common -Wl,--warn-section-align -Wl,--warn-unresolved-symbols -Wl,--start-group
diff --git a/Release/Duet-0.6-0.8.5/Edge/RepRapFirmware-1.17dev7.bin b/Release/Duet-0.6-0.8.5/Edge/RepRapFirmware-1.17dev7.bin
new file mode 100644
index 00000000..73e5a4f8
--- /dev/null
+++ b/Release/Duet-0.6-0.8.5/Edge/RepRapFirmware-1.17dev7.bin
Binary files differ
diff --git a/Release/Duet-WiFi/Edge/DuetWiFiFirmware-1.17dev7.bin b/Release/Duet-WiFi/Edge/DuetWiFiFirmware-1.17dev7.bin
new file mode 100644
index 00000000..e9388c78
--- /dev/null
+++ b/Release/Duet-WiFi/Edge/DuetWiFiFirmware-1.17dev7.bin
Binary files differ
diff --git a/src/Configuration.h b/src/Configuration.h
index 62d7c58c..924e1634 100644
--- a/src/Configuration.h
+++ b/src/Configuration.h
@@ -28,11 +28,11 @@ Licence: GPL
// Firmware name is now defined in the Pins file
#ifndef VERSION
-# define VERSION "1.17dev6"
+# define VERSION "1.17dev7"
#endif
#ifndef DATE
-# define DATE "2016-11-22"
+# define DATE "2016-12-07"
#endif
#define AUTHORS "reprappro, dc42, zpl, t3p3, dnewman"
@@ -105,7 +105,7 @@ const float DefaultMaxHeatingFaultTime = 5.0; // How many seconds we allow a he
const float AllowedTemperatureDerivativeNoise = 0.25; // How much fluctuation in the averaged temperature derivative we allow
const float MaxAmbientTemperature = 45.0; // We expect heaters to cool to this temperature or lower when switched off
const float NormalAmbientTemperature = 25.0; // The ambient temperature we assume - allow for the printer heating its surroundings a little
-const float DefaultMaxTempExcursion = 10.0; // How much error we tolerate when maintaining temperature before deciding that a heater fault has occurred
+const float DefaultMaxTempExcursion = 15.0; // How much error we tolerate when maintaining temperature before deciding that a heater fault has occurred
const float MinimumConnectedTemperature = -5.0; // Temperatures below this we treat as a disconnected thermistor
static_assert(DefaultMaxTempExcursion > TEMPERATURE_CLOSE_ENOUGH, "DefaultMaxTempExcursion is too low");
@@ -173,7 +173,6 @@ const size_t OUTPUT_BUFFER_COUNT = 32; // How many OutputBuffer instances do
const size_t RESERVED_OUTPUT_BUFFERS = 2; // Number of reserved output buffers after long responses. Must be enough for an HTTP header
#endif
-
// Move system
const float DEFAULT_FEEDRATE = 3000.0; // The initial requested feed rate after resetting the printer
diff --git a/src/GCodes/GCodeBuffer.h b/src/GCodes/GCodeBuffer.h
index aa8723c9..b1540871 100644
--- a/src/GCodes/GCodeBuffer.h
+++ b/src/GCodes/GCodeBuffer.h
@@ -48,6 +48,8 @@ public:
bool IsDoingFileMacro() const; // Return true if this source is executing a file macro
GCodeState GetState() const;
void SetState(GCodeState newState);
+ void AdvanceState();
+ const char *GetIdentity() const { return identity; }
private:
@@ -118,4 +120,9 @@ inline void GCodeBuffer::SetState(GCodeState newState)
machineState->state = newState;
}
+inline void GCodeBuffer::AdvanceState()
+{
+ machineState->state = static_cast<GCodeState>(static_cast<uint8_t>(machineState->state) + 1);
+}
+
#endif /* GCODEBUFFER_H_ */
diff --git a/src/GCodes/GCodeMachineState.cpp b/src/GCodes/GCodeMachineState.cpp
index 3cfa1204..98dac7e6 100644
--- a/src/GCodes/GCodeMachineState.cpp
+++ b/src/GCodes/GCodeMachineState.cpp
@@ -12,7 +12,7 @@ unsigned int GCodeMachineState::numAllocated = 0;
// Create a default initialised GCodeMachineState
GCodeMachineState::GCodeMachineState()
- : previous(nullptr), feedrate(DEFAULT_FEEDRATE), fileState(), lockedResources(0), state(GCodeState::normal),
+ : previous(nullptr), feedrate(DEFAULT_FEEDRATE/minutesToSeconds), fileState(), lockedResources(0), state(GCodeState::normal),
drivesRelative(false), axesRelative(false), doingFileMacro(false)
{
}
diff --git a/src/GCodes/GCodeMachineState.h b/src/GCodes/GCodeMachineState.h
index 8424738a..1d05d2fa 100644
--- a/src/GCodes/GCodeMachineState.h
+++ b/src/GCodes/GCodeMachineState.h
@@ -12,6 +12,9 @@
#include "Configuration.h"
#include "Storage/FileData.h"
+const float minutesToSeconds = 60.0;
+const float secondsToMinutes = 1.0/minutesToSeconds;
+
// Enumeration to list all the possible states that the Gcode processing machine may be in
enum class GCodeState : uint8_t
{
@@ -21,9 +24,16 @@ enum class GCodeState : uint8_t
setBed1,
setBed2,
setBed3,
+
+ // These next 3 must be contiguous
toolChange1,
toolChange2,
- toolChange3,
+ toolChangeComplete,
+ // These next 3 must be contiguous
+ m109ToolChange1,
+ m109ToolChange2,
+ m109ToolChangeComplete,
+
pausing1,
pausing2,
resuming1,
@@ -50,9 +60,11 @@ public:
FileData fileState;
uint32_t lockedResources;
GCodeState state;
- bool drivesRelative;
- bool axesRelative;
- bool doingFileMacro;
+ unsigned int
+ drivesRelative : 1,
+ axesRelative : 1,
+ doingFileMacro : 1,
+ waitWhileCooling : 1;
static GCodeMachineState *Allocate()
post(!result.IsLive(); result.state == GCodeState::normal);
diff --git a/src/GCodes/GCodes.cpp b/src/GCodes/GCodes.cpp
deleted file mode 100644
index 37602f94..00000000
--- a/src/GCodes/GCodes.cpp
+++ /dev/null
@@ -1,6605 +0,0 @@
-/****************************************************************************************************
-
- RepRapFirmware - G Codes
-
- This class interprets G Codes from one or more sources, and calls the functions in Move, Heat etc
- that drive the machine to do what the G Codes command.
-
- Most of the functions in here are designed not to wait, and they return a boolean. When you want them to do
- something, you call them. If they return false, the machine can't do what you want yet. So you go away
- and do something else. Then you try again. If they return true, the thing you wanted done has been done.
-
- -----------------------------------------------------------------------------------------------------
-
- Version 0.1
-
- 13 February 2013
-
- Adrian Bowyer
- RepRap Professional Ltd
- http://reprappro.com
-
- Licence: GPL
-
- ****************************************************************************************************/
-
-#include "RepRapFirmware.h"
-
-#ifdef DUET_NG
-#include "FirmwareUpdater.h"
-#endif
-
-#define DEGREE_SYMBOL "\xC2\xB0" // degree-symbol encoding in UTF8
-
-const char GCodes::axisLetters[MAX_AXES] = { 'X', 'Y', 'Z', 'U', 'V', 'W' };
-
-const char* BED_EQUATION_G = "bed.g";
-const char* PAUSE_G = "pause.g";
-const char* RESUME_G = "resume.g";
-const char* CANCEL_G = "cancel.g";
-const char* STOP_G = "stop.g";
-const char* SLEEP_G = "sleep.g";
-const char* homingFileNames[MAX_AXES] = { "homex.g", "homey.g", "homez.g", "homeu.g", "homev.g", "homew.g" };
-const char* HOME_ALL_G = "homeall.g";
-const char* HOME_DELTA_G = "homedelta.g";
-const char* DefaultHeightMapFile = "heightmap.csv";
-
-const size_t gcodeReplyLength = 2048; // long enough to pass back a reasonable number of files in response to M20
-
-const float MinServoPulseWidth = 544.0, MaxServoPulseWidth = 2400.0;
-const uint16_t ServoRefreshFrequency = 50;
-
-void GCodes::RestorePoint::Init()
-{
- for (size_t i = 0; i < DRIVES; ++i)
- {
- moveCoords[i] = 0.0;
- }
- feedRate = DEFAULT_FEEDRATE/minutesToSeconds;
-}
-
-GCodes::GCodes(Platform* p, Webserver* w) :
- platform(p), webserver(w), active(false), isFlashing(false),
- fileBeingHashed(nullptr)
-{
- httpGCode = new GCodeBuffer("http", HTTP_MESSAGE);
- telnetGCode = new GCodeBuffer("telnet", TELNET_MESSAGE);
- fileGCode = new GCodeBuffer("file", GENERIC_MESSAGE);
- serialGCode = new GCodeBuffer("serial", HOST_MESSAGE);
- auxGCode = new GCodeBuffer("aux", AUX_MESSAGE);
- daemonGCode = new GCodeBuffer("daemon", GENERIC_MESSAGE);
-}
-
-void GCodes::Exit()
-{
- platform->Message(HOST_MESSAGE, "GCodes class exited.\n");
- active = false;
-}
-
-void GCodes::Init()
-{
- Reset();
- numAxes = MIN_AXES;
- numExtruders = MaxExtruders;
- distanceScale = 1.0;
- rawExtruderTotal = 0.0;
- for (size_t extruder = 0; extruder < MaxExtruders; extruder++)
- {
- lastRawExtruderPosition[extruder] = 0.0;
- rawExtruderTotalByDrive[extruder] = 0.0;
- }
- eofString = EOF_STRING;
- eofStringCounter = 0;
- eofStringLength = strlen(eofString);
- offSetSet = false;
- zProbesSet = false;
- active = true;
- longWait = platform->Time();
- dwellTime = longWait;
- limitAxes = true;
- for(size_t axis = 0; axis < MAX_AXES; axis++)
- {
- axisScaleFactors[axis] = 1.0;
- }
- SetAllAxesNotHomed();
- for (size_t i = 0; i < NUM_FANS; ++i)
- {
- pausedFanValues[i] = 0.0;
- }
- lastDefaultFanSpeed = 0.0;
-
- retractLength = retractExtra = retractHop = 0.0;
- retractSpeed = unRetractSpeed = 600.0;
-}
-
-// This is called from Init and when doing an emergency stop
-void GCodes::Reset()
-{
- httpGCode->Init();
- telnetGCode->Init();
- fileGCode->Init();
- serialGCode->Init();
- auxGCode->Init();
- auxGCode->SetCommsProperties(1); // by default, we require a checksum on the aux port
- daemonGCode->Init();
-
- nextGcodeSource = 0;
-
- fileToPrint.Close();
- fileBeingWritten = NULL;
- dwellWaiting = false;
- probeCount = 0;
- cannedCycleMoveCount = 0;
- cannedCycleMoveQueued = false;
- speedFactor = 1.0 / minutesToSeconds; // default is just to convert from mm/minute to mm/second
- for (size_t i = 0; i < MaxExtruders; ++i)
- {
- extrusionFactors[i] = 1.0;
- }
- for (size_t i = 0; i < DRIVES; ++i)
- {
- moveBuffer.coords[i] = 0.0;
- }
- moveBuffer.xAxes = DefaultXAxisMapping;
-
- feedRate = DEFAULT_FEEDRATE/minutesToSeconds;
- pauseRestorePoint.Init();
- toolChangeRestorePoint.Init();
-
- ClearMove();
-
- for (size_t i = 0; i < MaxTriggers; ++i)
- {
- triggers[i].Init();
- }
- triggersPending = 0;
-
- simulationMode = 0;
- simulationTime = 0.0;
- isPaused = false;
- filePos = moveBuffer.filePos = noFilePosition;
- lastEndstopStates = platform->GetAllEndstopStates();
- firmwareUpdateModuleMap = 0;
-
- cancelWait = isWaiting = false;
-
- for (size_t i = 0; i < NumResources; ++i)
- {
- resourceOwners[i] = nullptr;
- }
-}
-
-float GCodes::FractionOfFilePrinted() const
-{
- const FileData& fileBeingPrinted = fileGCode->OriginalMachineState().fileState;
- return (fileBeingPrinted.IsLive()) ? fileBeingPrinted.FractionRead() : -1.0;
-}
-
-// Start running the config file
-// We use triggerCGode as the source to prevent any triggers being executed until we have finished
-bool GCodes::RunConfigFile(const char* fileName)
-{
- return DoFileMacro(*daemonGCode, fileName, false);
-}
-
-// Are we still running the config file?
-bool GCodes::IsRunningConfigFile() const
-{
- return daemonGCode->MachineState().fileState.IsLive();
-}
-
-void GCodes::Spin()
-{
- if (!active)
- {
- return;
- }
-
- CheckTriggers();
-
- // Get the GCodeBuffer that we want to work from
- GCodeBuffer& gb = *(gcodeSources[nextGcodeSource]);
-
- // Set up a buffer for the reply
- char replyBuffer[gcodeReplyLength];
- StringRef reply(replyBuffer, ARRAY_SIZE(replyBuffer));
- reply.Clear();
-
- if (gb.GetState() == GCodeState::normal)
- {
- StartNextGCode(gb, reply);
- }
- else
- {
- // Perform the next operation of the state machine for this gcode source
- bool error = false;
-
- switch (gb.GetState())
- {
- case GCodeState::waitingForMoveToComplete:
- if (AllMovesAreFinishedAndMoveBufferIsLoaded())
- {
- gb.SetState(GCodeState::normal);
- }
- break;
-
- case GCodeState::homing:
- if (toBeHomed == 0)
- {
- gb.SetState(GCodeState::normal);
- }
- else
- {
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- // Leave the Z axis until all other axes are done
- if ((toBeHomed & (1u << axis)) != 0 && (axis != Z_AXIS || toBeHomed == (1u << Z_AXIS)))
- {
- toBeHomed &= ~(1u << axis);
- DoFileMacro(gb, homingFileNames[axis]);
- break;
- }
- }
- }
- break;
-
- case GCodeState::setBed1:
- reprap.GetMove()->SetIdentityTransform();
- probeCount = 0;
- gb.SetState(GCodeState::setBed2);
- // no break
-
- case GCodeState::setBed2:
- {
- int numProbePoints = reprap.GetMove()->NumberOfXYProbePoints();
- if (DoSingleZProbeAtPoint(gb, probeCount, 0.0))
- {
- probeCount++;
- if (probeCount >= numProbePoints)
- {
- zProbesSet = true;
- reprap.GetMove()->FinishedBedProbing(0, reply);
- gb.SetState(GCodeState::normal);
- }
- }
- }
- break;
-
- case GCodeState::toolChange1: // Release the old tool (if any)
- {
- const Tool *oldTool = reprap.GetCurrentTool();
- if (oldTool != NULL)
- {
- reprap.StandbyTool(oldTool->Number());
- }
- }
- gb.SetState(GCodeState::toolChange2);
- if (reprap.GetTool(newToolNumber) != nullptr && AllAxesAreHomed())
- {
- scratchString.printf("tpre%d.g", newToolNumber);
- DoFileMacro(gb, scratchString.Pointer(), false);
- }
- break;
-
- case GCodeState::toolChange2: // Select the new tool (even if it doesn't exist - that just deselects all tools)
- reprap.SelectTool(newToolNumber);
- gb.SetState(GCodeState::toolChange3);
- if (reprap.GetTool(newToolNumber) != nullptr && AllAxesAreHomed())
- {
- scratchString.printf("tpost%d.g", newToolNumber);
- DoFileMacro(gb, scratchString.Pointer(), false);
- }
- break;
-
- case GCodeState::toolChange3:
- gb.SetState(GCodeState::normal);
- break;
-
- case GCodeState::pausing1:
- if (AllMovesAreFinishedAndMoveBufferIsLoaded())
- {
- gb.SetState(GCodeState::pausing2);
- DoFileMacro(gb, PAUSE_G);
- }
- break;
-
- case GCodeState::pausing2:
- if (AllMovesAreFinishedAndMoveBufferIsLoaded())
- {
- reply.copy("Printing paused");
- gb.SetState(GCodeState::normal);
- }
- break;
-
- case GCodeState::resuming1:
- case GCodeState::resuming2:
- // Here when we have just finished running the resume macro file.
- // Move the head back to the paused location
- if (AllMovesAreFinishedAndMoveBufferIsLoaded())
- {
- float currentZ = moveBuffer.coords[Z_AXIS];
- for (size_t drive = 0; drive < numAxes; ++drive)
- {
- moveBuffer.coords[drive] = pauseRestorePoint.moveCoords[drive];
- }
- for (size_t drive = numAxes; drive < DRIVES; ++drive)
- {
- moveBuffer.coords[drive] = 0.0;
- }
- moveBuffer.feedRate = DEFAULT_FEEDRATE/minutesToSeconds; // ask for a good feed rate, we may have paused during a slow move
- moveBuffer.moveType = 0;
- moveBuffer.endStopsToCheck = 0;
- moveBuffer.usePressureAdvance = false;
- moveBuffer.filePos = noFilePosition;
- if (gb.GetState() == GCodeState::resuming1 && currentZ > pauseRestorePoint.moveCoords[Z_AXIS])
- {
- // First move the head to the correct XY point, then move it down in a separate move
- moveBuffer.coords[Z_AXIS] = currentZ;
- gb.SetState(GCodeState::resuming2);
- }
- else
- {
- // Just move to the saved position in one go
- gb.SetState(GCodeState::resuming3);
- }
- moveAvailable = true;
- }
- break;
-
- case GCodeState::resuming3:
- if (AllMovesAreFinishedAndMoveBufferIsLoaded())
- {
- for (size_t i = 0; i < NUM_FANS; ++i)
- {
- platform->SetFanValue(i, pausedFanValues[i]);
- }
- for (size_t drive = numAxes; drive < DRIVES; ++drive)
- {
- lastRawExtruderPosition[drive - numAxes] = pauseRestorePoint.moveCoords[drive]; // reset the extruder position in case we are receiving absolute extruder moves
- }
- feedRate = pauseRestorePoint.feedRate;
- isPaused = false;
- reply.copy("Printing resumed");
- gb.SetState(GCodeState::normal);
- }
- break;
-
- case GCodeState::flashing1:
-#ifdef DUET_NG
- // Update additional modules before the main firmware
- if (FirmwareUpdater::IsReady())
- {
- bool updating = false;
- for (unsigned int module = 1; module < NumFirmwareUpdateModules; ++module)
- {
- if ((firmwareUpdateModuleMap & (1u << module)) != 0)
- {
- firmwareUpdateModuleMap &= ~(1u << module);
- FirmwareUpdater::UpdateModule(module);
- updating = true;
- break;
- }
- }
- if (!updating)
- {
- gb.SetState(GCodeState::flashing2);
- }
- }
-#else
- gb.SetState(GCodeState::flashing2);
-#endif
- break;
-
- case GCodeState::flashing2:
- if ((firmwareUpdateModuleMap & 1) != 0)
- {
- // Update main firmware
- firmwareUpdateModuleMap = 0;
- platform->UpdateFirmware();
- // The above call does not return unless an error occurred
- }
- isFlashing = false;
- gb.SetState(GCodeState::normal);
- break;
-
- case GCodeState::stopping: // MO after executing stop.g if present
- case GCodeState::sleeping: // M1 after executing sleep.g if present
- // Deselect the active tool and turn off all heaters, unless parameter Hn was used with n > 0
- if (!gb.Seen('H') || gb.GetIValue() <= 0)
- {
- Tool* tool = reprap.GetCurrentTool();
- if (tool != nullptr)
- {
- reprap.StandbyTool(tool->Number());
- }
- reprap.GetHeat()->SwitchOffAll();
- }
-
- // chrishamm 2014-18-10: Although RRP says M0 is supposed to turn off all drives and heaters,
- // I think M1 is sufficient for this purpose. Leave M0 for a normal reset.
- if (gb.GetState() == GCodeState::sleeping)
- {
- DisableDrives();
- }
- else
- {
- platform->SetDriversIdle();
- }
- gb.SetState(GCodeState::normal);
- break;
-
- case GCodeState::gridProbing1: // ready to move to next grid probe point
- {
- // Move to the current probe point
- const GridDefinition grid = reprap.GetMove()->GetBedProbeGrid();
- const float x = grid.GetXCoordinate(gridXindex);
- const float y = grid.GetYCoordinate(gridYindex);
- if (grid.IsInRadius(x, y) && platform->IsAccessibleProbePoint(x, y))
- {
- moveBuffer.moveType = 0;
- moveBuffer.endStopsToCheck = 0;
- moveBuffer.usePressureAdvance = false;
- moveBuffer.filePos = noFilePosition;
- moveBuffer.coords[X_AXIS] = x - platform->GetZProbeParameters().xOffset;
- moveBuffer.coords[Y_AXIS] = y - platform->GetZProbeParameters().yOffset;
- moveBuffer.coords[Z_AXIS] = platform->GetZProbeDiveHeight();
- moveBuffer.feedRate = platform->GetZProbeTravelSpeed();
- moveBuffer.xAxes = 0;
- moveAvailable = true;
- gb.SetState(GCodeState::gridProbing2);
- }
- else
- {
- gb.SetState(GCodeState::gridProbing4);
- }
- }
- break;
-
- case GCodeState::gridProbing2: // ready to probe the current grid probe point
- if (AllMovesAreFinishedAndMoveBufferIsLoaded())
- {
- // Probe the bed at the current XY coordinates
- // Check for probe already triggered at start
- if (reprap.GetPlatform()->GetZProbeResult() == EndStopHit::lowHit)
- {
- reply.copy("Z probe already triggered before probing move started");
- error = true;
- gb.SetState(GCodeState::normal);
- break;
- }
-
- zProbeTriggered = false;
- moveBuffer.moveType = 0;
- moveBuffer.endStopsToCheck = ZProbeActive;
- moveBuffer.usePressureAdvance = false;
- moveBuffer.filePos = noFilePosition;
- moveBuffer.coords[Z_AXIS] = -platform->GetZProbeDiveHeight();
- moveBuffer.feedRate = platform->GetZProbeParameters().probeSpeed;
- moveBuffer.xAxes = 0;
- moveAvailable = true;
- gb.SetState(GCodeState::gridProbing3);
- }
- break;
-
- case GCodeState::gridProbing3: // ready to lift the probe after probing the current grid probe point
- if (AllMovesAreFinishedAndMoveBufferIsLoaded())
- {
- if (!zProbeTriggered)
- {
- reply.copy("Z probe was not triggered during probing move");
- error = true;
- gb.SetState(GCodeState::normal);
- break;
- }
-
- const float heightError = moveBuffer.coords[Z_AXIS] - platform->ZProbeStopHeight();
- reprap.GetMove()->SetGridHeight(gridXindex, gridYindex, heightError);
- ++numPointsProbed;
- heightSum += (double)heightError;
- heightSquaredSum += (double)heightError * (double)heightError;
-
- // Move back up to the dive height
- moveBuffer.moveType = 0;
- moveBuffer.endStopsToCheck = 0;
- moveBuffer.usePressureAdvance = false;
- moveBuffer.filePos = noFilePosition;
- moveBuffer.coords[Z_AXIS] = platform->GetZProbeDiveHeight();
- moveBuffer.feedRate = platform->GetZProbeTravelSpeed();
- moveBuffer.xAxes = 0;
- moveAvailable = true;
- gb.SetState(GCodeState::gridProbing4);
- }
- break;
-
- case GCodeState::gridProbing4: // ready to compute the next probe point
- if (AllMovesAreFinishedAndMoveBufferIsLoaded())
- {
- const GridDefinition grid = reprap.GetMove()->GetBedProbeGrid();
- if (gridYindex & 1)
- {
- // Odd row, so decreasing X
- if (gridXindex == 0)
- {
- ++gridYindex;
- }
- else
- {
- --gridXindex;
- }
- }
- else
- {
- // Even row, so increasing X
- if (gridXindex + 1 == grid.NumXpoints())
- {
- ++gridYindex;
- }
- else
- {
- ++gridXindex;
- }
- }
- if (gridYindex == grid.NumYpoints())
- {
- // Finished probing the grid
- if (numPointsProbed >= 4)
- {
- error = reprap.GetMove()->SaveHeightMapToFile(heightMapFile, reply);
- const double mean = heightSum/numPointsProbed;
- const double deviation = sqrt(((heightSquaredSum * numPointsProbed) - (heightSum * heightSum)))/numPointsProbed;
- reply.catf(" - %u points probed, mean error %.2f, deviation %.2f", numPointsProbed, mean, deviation);
- reprap.GetMove()->UseHeightMap();
- }
- else
- {
- reply.copy("Too few points probed");
- error = true;
- }
- gb.SetState(GCodeState::normal);
- }
- else
- {
- gb.SetState(GCodeState::gridProbing1);
- }
- }
- break;
-
- default: // should not happen
- platform->Message(GENERIC_MESSAGE, "Error: undefined GCodeState\n");
- gb.SetState(GCodeState::normal);
- break;
- }
-
- if (gb.GetState() == GCodeState::normal)
- {
- // We completed a command, so unlock resources and tell the host about it
- UnlockAll(gb);
- HandleReply(gb, error, reply.Pointer());
- }
- }
-
- // Move on to the next gcode source ready for next time
- ++nextGcodeSource;
- if (nextGcodeSource == ARRAY_SIZE(gcodeSources))
- {
- nextGcodeSource = 0;
- }
-
- platform->ClassReport(longWait);
-}
-
-// Start a new gcode, or continue to execute one that has already been started:
-void GCodes::StartNextGCode(GCodeBuffer& gb, StringRef& reply)
-{
- if (isPaused && &gb == fileGCode)
- {
- // We are paused, so don't process any more gcodes from the file being printed.
- // There is a potential issue here if fileGCode holds any locks, so unlock everything.
- UnlockAll(gb);
- }
- else if (gb.IsReady() || gb.IsExecuting())
- {
- gb.SetFinished(ActOnCode(gb, reply));
- }
- else if (gb.MachineState().fileState.IsLive())
- {
- DoFilePrint(gb, reply);
- }
- else if (&gb == httpGCode)
- {
- // Webserver
- for (unsigned int i = 0; i < 16 && webserver->GCodeAvailable(WebSource::HTTP); ++i)
- {
- const char b = webserver->ReadGCode(WebSource::HTTP);
- if (gb.Put(b))
- {
- // We have a complete gcode
- if (gb.WritingFileDirectory() != nullptr)
- {
- WriteGCodeToFile(gb);
- gb.SetFinished(true);
- }
- else
- {
- gb.SetFinished(ActOnCode(gb, reply));
- }
- break;
- }
- }
- }
- else if (&gb == telnetGCode)
- {
- // Telnet
- for (unsigned int i = 0; i < GCODE_LENGTH && webserver->GCodeAvailable(WebSource::Telnet); ++i)
- {
- char b = webserver->ReadGCode(WebSource::Telnet);
- if (gb.Put(b))
- {
- gb.SetFinished(ActOnCode(gb, reply));
- break;
- }
- }
- }
- else if (&gb == serialGCode)
- {
- // USB interface
- for (unsigned int i = 0; i < 16 && platform->GCodeAvailable(SerialSource::USB); ++i)
- {
- const char b = platform->ReadFromSource(SerialSource::USB);
- // Check the special case of uploading the reprap.htm file
- if (gb.WritingFileDirectory() == platform->GetWebDir())
- {
- WriteHTMLToFile(gb, b);
- }
- else if (gb.Put(b)) // add char to buffer and test whether the gcode is complete
- {
- // We have a complete gcode
- if (gb.WritingFileDirectory() != nullptr)
- {
- WriteGCodeToFile(gb);
- gb.SetFinished(true);
- }
- else
- {
- gb.SetFinished(ActOnCode(gb, reply));
- }
- break;
- }
- }
- }
- else if (&gb == auxGCode)
- {
- // Aux serial port (typically PanelDue)
- for (unsigned int i = 0; i < 16 && platform->GCodeAvailable(SerialSource::AUX); ++i)
- {
- char b = platform->ReadFromSource(SerialSource::AUX);
- if (gb.Put(b)) // add char to buffer and test whether the gcode is complete
- {
- platform->SetAuxDetected();
- gb.SetFinished(ActOnCode(gb, reply));
- break;
- }
- }
- }
-}
-
-void GCodes::DoFilePrint(GCodeBuffer& gb, StringRef& reply)
-{
- FileData& fd = gb.MachineState().fileState;
- for (int i = 0; i < 50 && fd.IsLive(); ++i)
- {
- char b;
- if (fd.Read(b))
- {
- if (gb.StartingNewCode() && &gb == fileGCode && gb.MachineState().previous == nullptr)
- {
- filePos = fd.GetPosition() - 1;
- //debugPrintf("Set file pos %u\n", filePos);
- }
- if (gb.Put(b))
- {
- gb.SetFinished(ActOnCode(gb, reply));
- return;
- }
- }
- else
- {
- // We have reached the end of the file. Check for the last line of gcode not ending in newline.
- if (!gb.StartingNewCode()) // if there is something in the buffer
- {
- if (gb.Put('\n')) // in case there wasn't a newline ending the file
- {
- gb.SetFinished(ActOnCode(gb, reply));
- return;
- }
- }
-
- gb.Init(); // mark buffer as empty
-
- // Don't close the file until all moves have been completed, in case the print gets paused.
- // Also, this keeps the state as 'Printing' until the print really has finished.
- if (AllMovesAreFinishedAndMoveBufferIsLoaded())
- {
- fd.Close();
- if (gb.MachineState().previous == nullptr)
- {
- // Finished printing SD card file
- reprap.GetPrintMonitor()->StoppedPrint();
- if (platform->Emulating() == marlin)
- {
- // Pronterface expects a "Done printing" message
- HandleReply(gb, false, "Done printing file");
- }
- }
- else
- {
- // Finished a macro
- Pop(gb);
- gb.Init();
- if (gb.GetState() == GCodeState::normal)
- {
- UnlockAll(gb);
- HandleReply(gb, false, "");
- }
- }
- }
- return;
- }
- }
-}
-
-// Check for and execute triggers
-void GCodes::CheckTriggers()
-{
- // Check for endstop state changes that activate new triggers
- const TriggerMask oldEndstopStates = lastEndstopStates;
- lastEndstopStates = platform->GetAllEndstopStates();
- const TriggerMask risen = lastEndstopStates & ~oldEndstopStates,
- fallen = ~lastEndstopStates & oldEndstopStates;
- unsigned int lowestTriggerPending = MaxTriggers;
- for (unsigned int triggerNumber = 0; triggerNumber < MaxTriggers; ++triggerNumber)
- {
- const Trigger& ct = triggers[triggerNumber];
- if ( ((ct.rising & risen) != 0 || (ct.falling & fallen) != 0)
- && (ct.condition == 0 || (ct.condition == 1 && reprap.GetPrintMonitor()->IsPrinting()))
- )
- {
- triggersPending |= (1u << triggerNumber);
- }
- if (triggerNumber < lowestTriggerPending && (triggersPending & (1u << triggerNumber)) != 0)
- {
- lowestTriggerPending = triggerNumber;
- }
- }
-
- // If any triggers are pending, activate the one with the lowest number
- if (lowestTriggerPending == 0)
- {
- triggersPending &= ~(1u << lowestTriggerPending); // clear the trigger
- DoEmergencyStop();
- }
- else if (lowestTriggerPending < MaxTriggers // if a trigger is pending
- && !daemonGCode->MachineState().fileState.IsLive()
- && daemonGCode->GetState() == GCodeState::normal // and we are not already executing a trigger or config.g
- )
- {
- if (lowestTriggerPending == 1)
- {
- if (isPaused || !reprap.GetPrintMonitor()->IsPrinting())
- {
- triggersPending &= ~(1u << lowestTriggerPending); // ignore a pause trigger if we are already paused
- }
- else if (LockMovement(*daemonGCode)) // need to lock movement before executing the pause macro
- {
- triggersPending &= ~(1u << lowestTriggerPending); // clear the trigger
- DoPause(*daemonGCode);
- }
- }
- else
- {
- triggersPending &= ~(1u << lowestTriggerPending); // clear the trigger
- char buffer[25];
- StringRef filename(buffer, ARRAY_SIZE(buffer));
- filename.printf(SYS_DIR "trigger%u.g", lowestTriggerPending);
- DoFileMacro(*daemonGCode, filename.Pointer(), true);
- }
- }
-}
-
-// Execute an emergency stop
-void GCodes::DoEmergencyStop()
-{
- reprap.EmergencyStop();
- Reset();
- platform->Message(GENERIC_MESSAGE, "Emergency Stop! Reset the controller to continue.");
-}
-
-// Pause the print. Before calling this, check that we are doing a file print that isn't already paused and get the movement lock.
-void GCodes::DoPause(GCodeBuffer& gb)
-{
- if (&gb == fileGCode)
- {
- // Pausing a file print because of a command in the file itself
- for (size_t drive = 0; drive < numAxes; ++drive)
- {
- pauseRestorePoint.moveCoords[drive] = moveBuffer.coords[drive];
- }
- for (size_t drive = numAxes; drive < DRIVES; ++drive)
- {
- pauseRestorePoint.moveCoords[drive] = lastRawExtruderPosition[drive - numAxes]; // get current extruder positions into pausedMoveBuffer
- }
- pauseRestorePoint.feedRate = feedRate;
- }
- else
- {
- // Pausing a file print via another input source
- pauseRestorePoint.feedRate = feedRate; // the call to PausePrint may or may not change this
- FilePosition fPos = reprap.GetMove()->PausePrint(pauseRestorePoint.moveCoords, pauseRestorePoint.feedRate, reprap.GetCurrentXAxes());
- // tell Move we wish to pause the current print
- FileData& fdata = fileGCode->MachineState().fileState;
- if (fPos != noFilePosition && fdata.IsLive())
- {
- fdata.Seek(fPos); // replay the abandoned instructions if/when we resume
- }
- if (moveAvailable)
- {
- for (size_t drive = numAxes; drive < DRIVES; ++drive)
- {
- pauseRestorePoint.moveCoords[drive] += moveBuffer.coords[drive]; // add on the extrusion in the move not yet taken
- }
- ClearMove();
- }
-
- for (size_t drive = numAxes; drive < DRIVES; ++drive)
- {
- pauseRestorePoint.moveCoords[drive] = lastRawExtruderPosition[drive - numAxes] - pauseRestorePoint.moveCoords[drive];
- }
-
- //TODO record the virtual extruder positions of mixing tools too. But that's very hard to do unless we store it in the move.
-
- if (reprap.Debug(moduleGcodes))
- {
- platform->MessageF(GENERIC_MESSAGE, "Paused print, file offset=%u\n", fPos);
- }
- }
-
- for (size_t i = 0; i < NUM_FANS; ++i)
- {
- pausedFanValues[i] = platform->GetFanValue(i);
- }
- gb.SetState(GCodeState::pausing1);
- isPaused = true;
-}
-
-void GCodes::Diagnostics(MessageType mtype)
-{
- platform->Message(mtype, "=== GCodes ===\n");
- platform->MessageF(mtype, "Move available? %s\n", moveAvailable ? "yes" : "no");
- platform->MessageF(mtype, "Stack records: %u allocated, %u in use\n", GCodeMachineState::GetNumAllocated(), GCodeMachineState::GetNumInUse());
-
- for (size_t i = 0; i < ARRAY_SIZE(gcodeSources); ++i)
- {
- gcodeSources[i]->Diagnostics(mtype);
- }
-}
-
-// The wait till everything's done function. If you need the machine to
-// be idle before you do something (for example homing an axis, or shutting down) call this
-// until it returns true. As a side-effect it loads moveBuffer with the last position and feedrate for you.
-bool GCodes::AllMovesAreFinishedAndMoveBufferIsLoaded()
-{
- // Last one gone?
- if (moveAvailable)
- {
- return false;
- }
-
- // Wait for all the queued moves to stop so we get the actual last position
- if (!reprap.GetMove()->AllMovesAreFinished())
- {
- return false;
- }
-
- reprap.GetMove()->ResumeMoving();
- reprap.GetMove()->GetCurrentUserPosition(moveBuffer.coords, 0, reprap.GetCurrentXAxes());
- return true;
-}
-
-// Save (some of) the state of the machine for recovery in the future.
-bool GCodes::Push(GCodeBuffer& gb)
-{
- bool ok = gb.PushState();
- if (!ok)
- {
- platform->Message(GENERIC_MESSAGE, "Push(): stack overflow!\n");
- }
- return ok;
-}
-
-// Recover a saved state
-void GCodes::Pop(GCodeBuffer& gb)
-{
- if (!gb.PopState())
- {
- platform->Message(GENERIC_MESSAGE, "Pop(): stack underflow!\n");
- }
-}
-
-// Move expects all axis movements to be absolute, and all extruder drive moves to be relative. This function serves that.
-// 'moveType' is the S parameter in the G0 or G1 command, or -1 if we are doing G92.
-// For regular (type 0) moves, we apply limits and do X axis mapping.
-// Returns true if we have a legal move (or G92 argument), false if this gcode should be discarded
-bool GCodes::LoadMoveBufferFromGCode(GCodeBuffer& gb, int moveType)
-{
- // Zero every extruder drive as some drives may not be changed
- for (size_t drive = numAxes; drive < DRIVES; drive++)
- {
- moveBuffer.coords[drive] = 0.0;
- }
-
- // Deal with feed rate
- if (gb.Seen(feedrateLetter))
- {
- feedRate = gb.GetFValue() * distanceScale * speedFactor;
- }
- moveBuffer.feedRate = feedRate;
-
- // First do extrusion, and check, if we are extruding, that we have a tool to extrude with
- Tool* tool = reprap.GetCurrentTool();
- if (gb.Seen(extrudeLetter))
- {
- if (tool == nullptr)
- {
- platform->Message(GENERIC_MESSAGE, "Attempting to extrude with no tool selected.\n");
- return false;
- }
- size_t eMoveCount = tool->DriveCount();
- if (eMoveCount > 0)
- {
- // Set the drive values for this tool.
- // chrishamm-2014-10-03: Do NOT check extruder temperatures here, because we may be executing queued codes like M116
- if (tool->GetMixing())
- {
- const float moveArg = gb.GetFValue() * distanceScale;
- if (moveType == -1) // if doing G92
- {
- tool->virtualExtruderPosition = moveArg;
- }
- else
- {
- const float requestedExtrusionAmount = (gb.MachineState().drivesRelative)
- ? moveArg
- : moveArg - tool->virtualExtruderPosition;
- for (size_t eDrive = 0; eDrive < eMoveCount; eDrive++)
- {
- const int drive = tool->Drive(eDrive);
- const float extrusionAmount = requestedExtrusionAmount * tool->GetMix()[eDrive];
- lastRawExtruderPosition[drive] += extrusionAmount;
- rawExtruderTotalByDrive[drive] += extrusionAmount;
- rawExtruderTotal += extrusionAmount;
- moveBuffer.coords[drive + numAxes] = extrusionAmount * extrusionFactors[drive];
- }
- }
- }
- else
- {
- float eMovement[MaxExtruders];
- size_t mc = eMoveCount;
- gb.GetFloatArray(eMovement, mc, false);
- if (eMoveCount != mc)
- {
- platform->MessageF(GENERIC_MESSAGE, "Wrong number of extruder drives for the selected tool: %s\n", gb.Buffer());
- return false;
- }
-
- for (size_t eDrive = 0; eDrive < eMoveCount; eDrive++)
- {
- const int drive = tool->Drive(eDrive);
- const float moveArg = eMovement[eDrive] * distanceScale;
- if (moveType == -1)
- {
- moveBuffer.coords[drive + numAxes] = moveArg;
- lastRawExtruderPosition[drive] = moveArg;
- }
- else
- {
- const float extrusionAmount = (gb.MachineState().drivesRelative)
- ? moveArg
- : moveArg - lastRawExtruderPosition[drive];
- lastRawExtruderPosition[drive] += extrusionAmount;
- rawExtruderTotalByDrive[drive] += extrusionAmount;
- rawExtruderTotal += extrusionAmount;
- moveBuffer.coords[drive + numAxes] = extrusionAmount * extrusionFactors[drive];
- }
- }
- }
- }
- }
-
- // Now the movement axes
- const Tool *currentTool = reprap.GetCurrentTool();
- for (size_t axis = 0; axis < numAxes; axis++)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- float moveArg = gb.GetFValue() * distanceScale * axisScaleFactors[axis];
- if (moveType == -1) // if doing G92
- {
- SetAxisIsHomed(axis); // doing a G92 defines the absolute axis position
- moveBuffer.coords[axis] = moveArg;
- }
- else if (axis == X_AXIS && moveType == 0 && currentTool != nullptr)
- {
- // Perform X axis mapping
- const uint32_t xMap = currentTool->GetXAxisMap();
- for (size_t mappedAxis = 0; mappedAxis < numAxes; ++mappedAxis)
- {
- if ((xMap & (1u << mappedAxis)) != 0)
- {
- float mappedMoveArg = moveArg;
- if (gb.MachineState().axesRelative)
- {
- mappedMoveArg += moveBuffer.coords[mappedAxis];
- }
- else
- {
- mappedMoveArg -= currentTool->GetOffset()[mappedAxis]; // adjust requested position to compensate for tool offset
- }
- moveBuffer.coords[mappedAxis] = mappedMoveArg;
- }
- }
- }
- else
- {
- if (gb.MachineState().axesRelative)
- {
- moveArg += moveBuffer.coords[axis];
- }
- else if (currentTool != nullptr && moveType == 0)
- {
- moveArg -= currentTool->GetOffset()[axis]; // adjust requested position to compensate for tool offset
- }
- moveBuffer.coords[axis] = moveArg;
- }
- }
- }
-
- // If doing a regular move and applying limits, limit all axes
- if (moveType == 0 && limitAxes
-#if SUPPORT_ROLAND
- && !reprap.GetRoland()->Active()
-#endif
- )
- {
- if (!reprap.GetMove()->IsDeltaMode())
- {
- // Cartesian or CoreXY printer, so limit those axes that have been homed
- for (size_t axis = 0; axis < numAxes; axis++)
- {
- if (GetAxisIsHomed(axis))
- {
- float& f = moveBuffer.coords[axis];
- if (f < platform->AxisMinimum(axis))
- {
- f = platform->AxisMinimum(axis);
- }
- else if (f > platform->AxisMaximum(axis))
- {
- f = platform->AxisMaximum(axis);
- }
- }
- }
- }
- else if (AllAxesAreHomed()) // this is to allow extruder-only moves before homing
- {
- // If axes have been homed on a delta printer and this isn't a homing move, check for movements outside limits.
- // Skip this check if axes have not been homed, so that extruder-only moved are allowed before homing
- // Constrain the move to be within the build radius
- const float diagonalSquared = fsquare(moveBuffer.coords[X_AXIS]) + fsquare(moveBuffer.coords[Y_AXIS]);
- if (diagonalSquared > reprap.GetMove()->GetDeltaParams().GetPrintRadiusSquared())
- {
- const float factor = sqrtf(reprap.GetMove()->GetDeltaParams().GetPrintRadiusSquared() / diagonalSquared);
- moveBuffer.coords[X_AXIS] *= factor;
- moveBuffer.coords[Y_AXIS] *= factor;
- }
-
- // Constrain the end height of the move to be no greater than the homed height and no lower than -0.2mm
- moveBuffer.coords[Z_AXIS] = max<float>(platform->AxisMinimum(Z_AXIS),
- min<float>(moveBuffer.coords[Z_AXIS], reprap.GetMove()->GetDeltaParams().GetHomedHeight()));
- }
- }
-
- return true;
-}
-
-// This function is called for a G Code that makes a move.
-// If the Move class can't receive the move (i.e. things have to wait), return 0.
-// If we have queued the move and the caller doesn't need to wait for it to complete, return 1.
-// If we need to wait for the move to complete before doing another one (e.g. because endstops are checked in this move), return 2.
-int GCodes::SetUpMove(GCodeBuffer& gb, StringRef& reply)
-{
- // Last one gone yet?
- if (moveAvailable)
- {
- return 0;
- }
-
- // Check to see if the move is a 'homing' move that endstops are checked on.
- moveBuffer.endStopsToCheck = 0;
- moveBuffer.moveType = 0;
- moveBuffer.xAxes = reprap.GetCurrentXAxes();
- if (gb.Seen('S'))
- {
- int ival = gb.GetIValue();
- if (ival == 1 || ival == 2)
- {
- moveBuffer.moveType = ival;
- moveBuffer.xAxes = 0; // don't do bed compensation
- }
-
- if (ival == 1)
- {
- for (size_t i = 0; i < numAxes; ++i)
- {
- if (gb.Seen(axisLetters[i]))
- {
- moveBuffer.endStopsToCheck |= (1u << i);
- }
- }
- }
- else if (ival == 99) // temporary code to log Z probe change positions
- {
- moveBuffer.endStopsToCheck |= LogProbeChanges;
- }
- }
-
- if (reprap.GetMove()->IsDeltaMode())
- {
- // Extra checks to avoid damaging delta printers
- if (moveBuffer.moveType != 0 && !gb.MachineState().axesRelative)
- {
- // We have been asked to do a move without delta mapping on a delta machine, but the move is not relative.
- // This may be damaging and is almost certainly a user mistake, so ignore the move.
- reply.copy("Attempt to move the motors of a delta printer to absolute positions");
- return 1;
- }
-
- if (moveBuffer.moveType == 0 && !AllAxesAreHomed())
- {
- // The user may be attempting to move a delta printer to an XYZ position before homing the axes
- // This may be damaging and is almost certainly a user mistake, so ignore the move. But allow extruder-only moves.
- if (gb.Seen(axisLetters[X_AXIS]) || gb.Seen(axisLetters[Y_AXIS]) || gb.Seen(axisLetters[Z_AXIS]))
- {
- reply.copy("Attempt to move the head of a delta printer before homing the towers");
- return 1;
- }
- }
- }
-
- // Load the last position and feed rate into moveBuffer
-#if SUPPORT_ROLAND
- if (reprap.GetRoland()->Active())
- {
- reprap.GetRoland()->GetCurrentRolandPosition(moveBuffer);
- }
- else
-#endif
- {
- reprap.GetMove()->GetCurrentUserPosition(moveBuffer.coords, moveBuffer.moveType, reprap.GetCurrentXAxes());
- }
-
- // Load the move buffer with either the absolute movement required or the relative movement required
- float oldCoords[MAX_AXES];
- memcpy(oldCoords, moveBuffer.coords, sizeof(oldCoords));
- moveAvailable = LoadMoveBufferFromGCode(gb, moveBuffer.moveType);
- if (moveAvailable)
- {
- // Flag whether we should use pressure advance, if there is any extrusion in this move.
- // We assume it is a normal printing move needing pressure advance if there is forward extrusion and XY movement.
- // The movement code will only apply pressure advance if there is forward extrusion, so we only need to check for XY movement here.
- moveBuffer.usePressureAdvance = false;
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- if (axis != Z_AXIS && moveBuffer.coords[axis] != oldCoords[axis])
- {
- moveBuffer.usePressureAdvance = true;
- break;
- }
- }
- moveBuffer.filePos = (&gb == fileGCode) ? filePos : noFilePosition;
- //debugPrintf("Queue move pos %u\n", moveFilePos);
- }
- return (moveBuffer.moveType != 0 || moveBuffer.endStopsToCheck != 0) ? 2 : 1;
-}
-
-// The Move class calls this function to find what to do next.
-
-bool GCodes::ReadMove(RawMove& m)
-{
- if (!moveAvailable)
- {
- return false;
- }
-
- m = moveBuffer;
- ClearMove();
- return true;
-}
-
-void GCodes::ClearMove()
-{
- moveAvailable = false;
- moveBuffer.endStopsToCheck = 0;
- moveBuffer.moveType = 0;
- moveBuffer.isFirmwareRetraction = false;
-}
-
-// Run a file macro. Prior to calling this, 'state' must be set to the state we want to enter when the macro has been completed.
-// Return true if the file was found or it wasn't and we were asked to report that fact.
-bool GCodes::DoFileMacro(GCodeBuffer& gb, const char* fileName, bool reportMissing)
-{
- FileStore * const f = platform->GetFileStore(platform->GetSysDir(), fileName, false);
- if (f == nullptr)
- {
- if (reportMissing)
- {
- // Don't use snprintf into scratchString here, because fileName may be aliased to scratchString
- platform->MessageF(GENERIC_MESSAGE, "Macro file %s not found.\n", fileName);
- return true;
- }
- return false;
- }
-
- if (!Push(gb))
- {
- return true;
- }
- gb.MachineState().fileState.Set(f);
- gb.MachineState().doingFileMacro = true;
- gb.SetState(GCodeState::normal);
- gb.Init();
- return true;
-}
-
-void GCodes::FileMacroCyclesReturn(GCodeBuffer& gb)
-{
- if (gb.MachineState().doingFileMacro)
- {
- gb.PopState();
- gb.Init();
- }
-}
-
-// To execute any move, call this until it returns true.
-// There is only one copy of the canned cycle variable so you must acquire the move lock before calling this.
-bool GCodes::DoCannedCycleMove(GCodeBuffer& gb, EndstopChecks ce)
-{
- if (LockMovementAndWaitForStandstill(gb))
- {
- if (cannedCycleMoveQueued) // if the move has already been queued, it must have finished
- {
- Pop(gb);
- cannedCycleMoveQueued = false;
- return true;
- }
-
- // Otherwise, the move has not been queued yet
- if (!Push(gb))
- {
- return true; // stack overflow
- }
-
- for (size_t drive = 0; drive < DRIVES; drive++)
- {
- switch(cannedMoveType[drive])
- {
- case CannedMoveType::none:
- break;
- case CannedMoveType::relative:
- moveBuffer.coords[drive] += cannedMoveCoords[drive];
- break;
- case CannedMoveType::absolute:
- moveBuffer.coords[drive] = cannedMoveCoords[drive];
- break;
- }
- }
- moveBuffer.feedRate = cannedFeedRate;
- moveBuffer.xAxes = 0;
- moveBuffer.endStopsToCheck = ce;
- moveBuffer.filePos = noFilePosition;
- moveBuffer.usePressureAdvance = false;
- moveAvailable = true;
- cannedCycleMoveQueued = true;
- }
- return false;
-}
-
-// This handles G92
-bool GCodes::SetPositions(GCodeBuffer& gb)
-{
- // Don't pause the machine if only extruder drives are being reset (DC, 2015-09-06).
- // This avoids blobs and seams when the gcode uses absolute E coordinates and periodically includes G92 E0.
- bool includingAxes = false;
- for (size_t drive = 0; drive < numAxes; ++drive)
- {
- if (gb.Seen(axisLetters[drive]))
- {
- includingAxes = true;
- break;
- }
- }
-
- if (includingAxes)
- {
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- }
- else if (moveAvailable) // wait for previous move to be taken so that GetCurrentUserPosition returns the correct value
- {
- return false;
- }
-
- reprap.GetMove()->GetCurrentUserPosition(moveBuffer.coords, 0, reprap.GetCurrentXAxes()); // make sure move buffer is up to date
- bool ok = LoadMoveBufferFromGCode(gb, -1);
- if (ok && includingAxes)
- {
-#if SUPPORT_ROLAND
- if (reprap.GetRoland()->Active())
- {
- for(size_t axis = 0; axis < AXES; axis++)
- {
- if (!reprap.GetRoland()->ProcessG92(moveBuffer[axis], axis))
- {
- return false;
- }
- }
- }
-#endif
- SetPositions(moveBuffer.coords);
- }
-
- return true;
-}
-
-// Offset the axes by the X, Y, and Z amounts in the M code in gb. Say the machine is at [10, 20, 30] and
-// the offsets specified are [8, 2, -5]. The machine will move to [18, 22, 25] and henceforth consider that point
-// to be [10, 20, 30].
-bool GCodes::OffsetAxes(GCodeBuffer& gb)
-{
- if (!offSetSet)
- {
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- for (size_t drive = 0; drive < DRIVES; drive++)
- {
- if (drive < numAxes)
- {
- record[drive] = moveBuffer.coords[drive];
- if (gb.Seen(axisLetters[drive]))
- {
- cannedMoveCoords[drive] = gb.GetFValue();
- cannedMoveType[drive] = CannedMoveType::relative;
- }
- }
- else
- {
- record[drive] = 0.0;
- }
- cannedMoveType[drive] = CannedMoveType::none;
- }
-
- if (gb.Seen(feedrateLetter)) // Has the user specified a feedrate?
- {
- cannedFeedRate = gb.GetFValue() * distanceScale * SECONDS_TO_MINUTES;
- }
- else
- {
- cannedFeedRate = DEFAULT_FEEDRATE;
- }
-
- offSetSet = true;
- }
-
- if (DoCannedCycleMove(gb, 0))
- {
- // Restore positions
- for (size_t drive = 0; drive < DRIVES; drive++)
- {
- moveBuffer.coords[drive] = record[drive];
- }
- reprap.GetMove()->SetLiveCoordinates(record); // This doesn't transform record
- reprap.GetMove()->SetPositions(record); // This does
- offSetSet = false;
- return true;
- }
-
- return false;
-}
-
-// Home one or more of the axes. Which ones are decided by the
-// booleans homeX, homeY and homeZ.
-// Returns true if completed, false if needs to be called again.
-// 'reply' is only written if there is an error.
-// 'error' is false on entry, gets changed to true if there is an error.
-bool GCodes::DoHome(GCodeBuffer& gb, StringRef& reply, bool& error)
-{
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
-
-#if SUPPORT_ROLAND
- // Deal with a Roland configuration
- if (reprap.GetRoland()->Active())
- {
- bool rolHome = reprap.GetRoland()->ProcessHome();
- if (rolHome)
- {
- for(size_t axis = 0; axis < AXES; axis++)
- {
- axisIsHomed[axis] = true;
- }
- }
- return rolHome;
- }
-#endif
-
- if (reprap.GetMove()->IsDeltaMode())
- {
- SetAllAxesNotHomed();
- DoFileMacro(gb, HOME_DELTA_G);
- }
- else
- {
- toBeHomed = 0;
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- toBeHomed |= (1u << axis);
- SetAxisNotHomed(axis);
- }
- }
-
- if (toBeHomed == 0 || toBeHomed == ((1u << numAxes) - 1))
- {
- // Homing everything
- SetAllAxesNotHomed();
- DoFileMacro(gb, HOME_ALL_G);
- }
- else if ( platform->MustHomeXYBeforeZ()
- && ((toBeHomed & (1u << Z_AXIS)) != 0)
- && ((toBeHomed | axesHomed | (1u << Z_AXIS)) != ((1u << numAxes) - 1))
- )
- {
- // We can only home Z if both X and Y have already been homed or are being homed
- reply.copy("Must home all other axes before homing Z");
- error = true;
- }
- else
- {
- gb.SetState(GCodeState::homing);
- }
- }
- return true;
-}
-
-// This lifts Z a bit, moves to the probe XY coordinates (obtained by a call to GetProbeCoordinates() ),
-// probes the bed height, and records the Z coordinate probed. If you want to program any general
-// internal canned cycle, this shows how to do it.
-// On entry, probePointIndex specifies which of the points this is.
-bool GCodes::DoSingleZProbeAtPoint(GCodeBuffer& gb, int probePointIndex, float heightAdjust)
-{
- reprap.GetMove()->SetIdentityTransform(); // It doesn't matter if these are called repeatedly
-
- for (size_t drive = 0; drive <= DRIVES; drive++)
- {
- cannedMoveType[drive] = CannedMoveType::none;
- }
-
- switch (cannedCycleMoveCount)
- {
- case 0: // Move Z to the dive height. This only does anything on the first move; on all the others Z is already there
- cannedMoveCoords[Z_AXIS] = platform->GetZProbeDiveHeight() + max<float>(platform->ZProbeStopHeight(), 0.0);
- cannedMoveType[Z_AXIS] = CannedMoveType::absolute;
- cannedFeedRate = platform->GetZProbeTravelSpeed();
- if (DoCannedCycleMove(gb, 0))
- {
- cannedCycleMoveCount++;
- }
- return false;
-
- case 1: // Move to the correct XY coordinates
- GetProbeCoordinates(probePointIndex, cannedMoveCoords[X_AXIS], cannedMoveCoords[Y_AXIS], cannedMoveCoords[Z_AXIS]);
- cannedMoveType[X_AXIS] = CannedMoveType::absolute;
- cannedMoveType[Y_AXIS] = CannedMoveType::absolute;
- // NB - we don't use the Z value
- cannedFeedRate = platform->GetZProbeTravelSpeed();
- if (DoCannedCycleMove(gb, 0))
- {
- cannedCycleMoveCount++;
- }
- return false;
-
- case 2: // Probe the bed
- {
- const float height = (GetAxisIsHomed(Z_AXIS))
- ? 2 * platform->GetZProbeDiveHeight() // Z axis has been homed, so no point in going very far
- : 1.1 * platform->AxisTotalLength(Z_AXIS); // Z axis not homed yet, so treat this as a homing move
- switch(DoZProbe(gb, height))
- {
- case 0:
- // Z probe is already triggered at the start of the move, so abandon the probe and record an error
- platform->Message(GENERIC_MESSAGE, "Error: Z probe already triggered at start of probing move\n");
- cannedCycleMoveCount++;
- reprap.GetMove()->SetZBedProbePoint(probePointIndex, platform->GetZProbeDiveHeight(), true, true);
- break;
-
- case 1:
- // Z probe did not trigger
- platform->Message(GENERIC_MESSAGE, "Error: Z probe was not triggered during probing move\n");
- cannedCycleMoveCount++;
- reprap.GetMove()->SetZBedProbePoint(probePointIndex, -(platform->GetZProbeDiveHeight()), true, true);
- break;
-
- case 2:
- // Successful probing
- if (GetAxisIsHomed(Z_AXIS))
- {
- lastProbedZ = moveBuffer.coords[Z_AXIS] - (platform->ZProbeStopHeight() + heightAdjust);
- }
- else
- {
- // The Z axis has not yet been homed, so treat this probe as a homing move.
- moveBuffer.coords[Z_AXIS] = platform->ZProbeStopHeight() + heightAdjust;
- SetPositions(moveBuffer.coords);
- SetAxisIsHomed(Z_AXIS);
- lastProbedZ = 0.0;
- }
- reprap.GetMove()->SetZBedProbePoint(probePointIndex, lastProbedZ, true, false);
- cannedCycleMoveCount++;
- break;
-
- default:
- break;
- }
- }
- return false;
-
- case 3: // Raise the head back up to the dive height
- cannedMoveCoords[Z_AXIS] = platform->GetZProbeDiveHeight() + max<float>(platform->ZProbeStopHeight(), 0.0);
- cannedMoveType[Z_AXIS] = CannedMoveType::absolute;
- cannedFeedRate = platform->GetZProbeTravelSpeed();
- if (DoCannedCycleMove(gb, 0))
- {
- cannedCycleMoveCount = 0;
- return true;
- }
- return false;
-
- default: // should not happen
- cannedCycleMoveCount = 0;
- return true;
- }
-}
-
-// This simply moves down till the Z probe/switch is triggered. Call it repeatedly until it returns true.
-// Called when we do a G30 with no P parameter.
-bool GCodes::DoSingleZProbe(GCodeBuffer& gb, bool reportOnly, float heightAdjust)
-{
- switch (DoZProbe(gb, 1.1 * platform->AxisTotalLength(Z_AXIS)))
- {
- case 0: // failed
- platform->Message(GENERIC_MESSAGE, "Error: Z probe already triggered at start of probing move\n");
- return true;
-
- case 1:
- platform->Message(GENERIC_MESSAGE, "Error: Z probe was not triggered during probing move\n");
- return true;
-
- case 2: // success
- if (!reportOnly)
- {
- moveBuffer.coords[Z_AXIS] = platform->ZProbeStopHeight() + heightAdjust;
- SetPositions(moveBuffer.coords);
- SetAxisIsHomed(Z_AXIS);
- lastProbedZ = 0.0;
- }
- return true;
-
- default: // not finished yet
- return false;
- }
-}
-
-// Do a Z probe cycle up to the maximum specified distance.
-// Returns -1 if not complete yet
-// Returns 0 if Z probe already triggered at start of probing
-// Returns 1 if Z probe didn't trigger
-// Returns 2 if success, with the current position in moveBuffer
-int GCodes::DoZProbe(GCodeBuffer& gb, float distance)
-{
- if (platform->GetZProbeType() == ZProbeTypeDelta)
- {
- const ZProbeParameters& params = platform->GetZProbeParameters();
- return reprap.GetMove()->DoDeltaProbe(params.param1, params.param2, params.probeSpeed, distance);
- }
- else
- {
- // Check for probe already triggered at start
- if (!cannedCycleMoveQueued)
- {
- if (reprap.GetPlatform()->GetZProbeResult() == EndStopHit::lowHit)
- {
- return 0;
- }
- zProbeTriggered = false;
- }
-
- // Do a normal canned cycle Z movement with Z probe enabled
- for (size_t drive = 0; drive <= DRIVES; drive++)
- {
- cannedMoveType[drive] = CannedMoveType::none;
- }
-
- cannedMoveCoords[Z_AXIS] = -distance;
- cannedMoveType[Z_AXIS] = CannedMoveType::relative;
- cannedFeedRate = platform->GetZProbeParameters().probeSpeed;
-
- if (DoCannedCycleMove(gb, ZProbeActive))
- {
- return (zProbeTriggered) ? 2 : 1;
- }
- return -1;
- }
-}
-
-// This is called to execute a G30.
-// It sets wherever we are as the probe point P (probePointIndex)
-// then probes the bed, or gets all its parameters from the arguments.
-// If X or Y are specified, use those; otherwise use the machine's
-// coordinates. If no Z is specified use the machine's coordinates. If it
-// is specified and is greater than SILLY_Z_VALUE (i.e. greater than -9999.0)
-// then that value is used. If it's less than SILLY_Z_VALUE the bed is
-// probed and that value is used.
-// Call this repeatedly until it returns true.
-bool GCodes::SetSingleZProbeAtAPosition(GCodeBuffer& gb, StringRef& reply)
-{
- if (reprap.GetMove()->IsDeltaMode() && !AllAxesAreHomed())
- {
- reply.copy("Must home before bed probing");
- return true;
- }
-
- float heightAdjust = 0.0;
- bool dummy;
- gb.TryGetFValue('H', heightAdjust, dummy);
-
- if (!gb.Seen('P'))
- {
- bool reportOnly = false;
- if (gb.Seen('S') && gb.GetIValue() < 0)
- {
- reportOnly = true;
- }
- return DoSingleZProbe(gb, reportOnly, heightAdjust);
- }
-
- int probePointIndex = gb.GetIValue();
- if (probePointIndex < 0 || (unsigned int)probePointIndex >= MaxProbePoints)
- {
- reprap.GetPlatform()->Message(GENERIC_MESSAGE, "Z probe point index out of range.\n");
- return true;
- }
-
- float x = (gb.Seen(axisLetters[X_AXIS])) ? gb.GetFValue() : moveBuffer.coords[X_AXIS];
- float y = (gb.Seen(axisLetters[Y_AXIS])) ? gb.GetFValue() : moveBuffer.coords[Y_AXIS];
- float z = (gb.Seen(axisLetters[Z_AXIS])) ? gb.GetFValue() : moveBuffer.coords[Z_AXIS];
-
- reprap.GetMove()->SetXBedProbePoint(probePointIndex, x);
- reprap.GetMove()->SetYBedProbePoint(probePointIndex, y);
-
- if (z > SILLY_Z_VALUE)
- {
- reprap.GetMove()->SetZBedProbePoint(probePointIndex, z, false, false);
- if (gb.Seen('S'))
- {
- zProbesSet = true;
- reprap.GetMove()->FinishedBedProbing(gb.GetIValue(), reply);
- }
- return true;
- }
- else
- {
- if (DoSingleZProbeAtPoint(gb, probePointIndex, heightAdjust))
- {
- if (gb.Seen('S'))
- {
- zProbesSet = true;
- int sParam = gb.GetIValue();
- if (sParam == 1)
- {
- // G30 with a silly Z value and S=1 is equivalent to G30 with no parameters in that it sets the current Z height
- // This is useful because it adjusts the XY position to account for the probe offset.
- moveBuffer.coords[Z_AXIS] += lastProbedZ;
- SetPositions(moveBuffer.coords);
- lastProbedZ = 0.0;
- }
- else
- {
- reprap.GetMove()->FinishedBedProbing(sParam, reply);
- }
- }
- return true;
- }
- }
-
- return false;
-}
-
-// This returns the (X, Y) points to probe the bed at probe point count. When probing, it returns false.
-// If called after probing has ended it returns true, and the Z coordinate probed is also returned.
-bool GCodes::GetProbeCoordinates(int count, float& x, float& y, float& z) const
-{
- const ZProbeParameters& rp = platform->GetZProbeParameters();
- x = reprap.GetMove()->XBedProbePoint(count) - rp.xOffset;
- y = reprap.GetMove()->YBedProbePoint(count) - rp.yOffset;
- z = reprap.GetMove()->ZBedProbePoint(count);
- return zProbesSet;
-}
-
-bool GCodes::SetPrintZProbe(GCodeBuffer& gb, StringRef& reply)
-{
- ZProbeParameters params = platform->GetZProbeParameters();
- bool seen = false;
- gb.TryGetFValue(axisLetters[X_AXIS], params.xOffset, seen);
- gb.TryGetFValue(axisLetters[Y_AXIS], params.yOffset, seen);
- gb.TryGetFValue(axisLetters[Z_AXIS], params.height, seen);
- gb.TryGetIValue('P', params.adcValue, seen);
-
- if (gb.Seen('C'))
- {
- params.temperatureCoefficient = gb.GetFValue();
- seen = true;
- if (gb.Seen('S'))
- {
- params.calibTemperature = gb.GetFValue();
- }
- else
- {
- // Use the current bed temperature as the calibration temperature if no value was provided
- params.calibTemperature = platform->GetZProbeTemperature();
- }
- }
-
- if (seen)
- {
- platform->SetZProbeParameters(params);
- }
- else
- {
- const int v0 = platform->ZProbe();
- int v1, v2;
- switch (platform->GetZProbeSecondaryValues(v1, v2))
- {
- case 1:
- reply.printf("%d (%d)", v0, v1);
- break;
- case 2:
- reply.printf("%d (%d, %d)", v0, v1, v2);
- break;
- default:
- reply.printf("%d", v0);
- break;
- }
- }
- return true;
-}
-
-// Define the probing grid, returning true if error
-// Called when we see an M557 command with no P parameter
-bool GCodes::DefineGrid(GCodeBuffer& gb, StringRef &reply)
-{
- bool seenX = false, seenY = false, seenR = false, seenS = false;
- float xValues[2];
- float yValues[2];
-
- if (gb.Seen('X'))
- {
- size_t count = 2;
- gb.GetFloatArray(xValues, count, false);
- if (count == 2)
- {
- seenX = true;
- }
- else
- {
- reply.copy("ERROR: Wrong number of X values in M577, need 2");
- return true;
- }
- }
- if (gb.Seen('Y'))
- {
- size_t count = 2;
- gb.GetFloatArray(yValues, count, false);
- if (count == 2)
- {
- seenY = true;
- }
- else
- {
- reply.copy("ERROR: Wrong number of Y values in M577, need 2");
- return true;
- }
- }
-
- float radius = -1.0;
- gb.TryGetFValue('R', radius, seenR);
- float spacing = DefaultGridSpacing;
- gb.TryGetFValue('S', spacing, seenS);
-
- if (!seenX && !seenY && !seenR && !seenS)
- {
- // Just print the existing grid parameters
- const GridDefinition& grid = reprap.GetMove()->GetBedProbeGrid();
- if (grid.IsValid())
- {
- reply.copy("Grid: ");
- grid.PrintParameters(reply);
- }
- else
- {
- reply.copy("Grid is not defined");
- }
- return false;
- }
-
- if (seenX != seenY)
- {
- reply.copy("ERROR: specify both or neither of X and Y in M577");
- return true;
- }
-
- if (!seenX && !seenR)
- {
- // Must have given just the S parameter
- reply.copy("ERROR: specify at least radius or X,Y ranges in M577");
- return true;
-
- }
-
- if (!seenX)
- {
- if (radius > 0)
- {
- const float effectiveRadius = floor((radius - 0.1)/spacing) * spacing;
- xValues[0] = yValues[0] = -effectiveRadius;
- xValues[1] = yValues[1] = effectiveRadius + 0.1;
- }
- else
- {
- reply.copy("ERROR: M577 radius must be positive unless X and Y are specified");
- return true;
- }
- }
- GridDefinition newGrid(xValues, yValues, radius, spacing); // create a new grid
- if (newGrid.IsValid())
- {
- reprap.GetMove()->SetBedProbeGrid(newGrid);
- return false;
- }
- else
- {
- reply.copy("ERROR: bad grid definition: ");
- newGrid.PrintError(reply);
- return true;
- }
-}
-
-// Start probing the grid, returning true if we didn't because of an error.
-// Prior to calling this the movement system must be locked.
-bool GCodes::ProbeGrid(GCodeBuffer& gb, StringRef& reply)
-{
- long sParam = 0;
- bool dummy;
- gb.TryGetIValue('S', sParam, dummy);
-
- if (gb.Seen('P'))
- {
- heightMapFile = gb.GetString();
- }
- else
- {
- heightMapFile = DefaultHeightMapFile;
- }
-
- switch(sParam)
- {
- case 0: // Probe the bed and save to file
- if (!reprap.GetMove()->GetBedProbeGrid().IsValid())
- {
- reply.copy("No valid grid defined for G29 bed probing");
- return true;
- }
-
- if (!AllAxesAreHomed())
- {
- reply.copy("Must home printer before G29 bed probing");
- return true;
- }
-
- gridXindex = gridYindex = 0;
- numPointsProbed = 0;
- heightSum = heightSquaredSum = 0.0;
-
- reprap.GetMove()->ClearGridHeights();
- reprap.GetMove()->SetIdentityTransform();
- gb.SetState(GCodeState::gridProbing1);
- return false;
-
- case 1: // Load height map from file
- return reprap.GetMove()->LoadHeightMapFromFile(heightMapFile, reply);
-
- case 2: // Clear height map
- reprap.GetMove()->ClearGridHeights();
- return false;
-
- default:
- reply.copy("Invalid S parameter in G29 command");
- return true;
- }
-
-
-}
-
-// Return the current coordinates as a printable string.
-// Coordinates are updated at the end of each movement, so this won't tell you where you are mid-movement.
-void GCodes::GetCurrentCoordinates(StringRef& s) const
-{
- float liveCoordinates[DRIVES];
- reprap.GetMove()->LiveCoordinates(liveCoordinates, reprap.GetCurrentXAxes());
- const Tool *currentTool = reprap.GetCurrentTool();
- if (currentTool != nullptr)
- {
- const float *offset = currentTool->GetOffset();
- for (size_t i = 0; i < numAxes; ++i)
- {
- liveCoordinates[i] += offset[i];
- }
- }
-
- s.Clear();
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- s.catf("%c: %.2f ", axisLetters[axis], liveCoordinates[axis]);
- }
- for (size_t i = numAxes; i < DRIVES; i++)
- {
- s.catf("E%u: %.1f ", i - numAxes, liveCoordinates[i]);
- }
-
- // Print the axis stepper motor positions as Marlin does, as an aid to debugging.
- // Don't bother with the extruder endpoints, they are zero after any non-extruding move.
- s.cat(" Count");
- for (size_t i = 0; i < numAxes; ++i)
- {
- s.catf(" %d", reprap.GetMove()->GetEndPoint(i));
- }
-}
-
-bool GCodes::OpenFileToWrite(GCodeBuffer& gb, const char* directory, const char* fileName)
-{
- fileBeingWritten = platform->GetFileStore(directory, fileName, true);
- eofStringCounter = 0;
- if (fileBeingWritten == NULL)
- {
- platform->MessageF(GENERIC_MESSAGE, "Can't open GCode file \"%s\" for writing.\n", fileName);
- return false;
- }
- else
- {
- gb.SetWritingFileDirectory(directory);
- return true;
- }
-}
-
-void GCodes::WriteHTMLToFile(GCodeBuffer& gb, char b)
-{
- if (fileBeingWritten == NULL)
- {
- platform->Message(GENERIC_MESSAGE, "Attempt to write to a null file.\n");
- return;
- }
-
- if (eofStringCounter != 0 && b != eofString[eofStringCounter])
- {
- fileBeingWritten->Write(eofString);
- eofStringCounter = 0;
- }
-
- if (b == eofString[eofStringCounter])
- {
- eofStringCounter++;
- if (eofStringCounter >= eofStringLength)
- {
- fileBeingWritten->Close();
- fileBeingWritten = NULL;
- gb.SetWritingFileDirectory(NULL);
- const char* r = (platform->Emulating() == marlin) ? "Done saving file." : "";
- HandleReply(gb, false, r);
- return;
- }
- }
- else
- {
- fileBeingWritten->Write(b);
- }
-}
-
-void GCodes::WriteGCodeToFile(GCodeBuffer& gb)
-{
- if (fileBeingWritten == NULL)
- {
- platform->Message(GENERIC_MESSAGE, "Attempt to write to a null file.\n");
- return;
- }
-
- // End of file?
- if (gb.Seen('M'))
- {
- if (gb.GetIValue() == 29)
- {
- fileBeingWritten->Close();
- fileBeingWritten = NULL;
- gb.SetWritingFileDirectory(NULL);
- const char* r = (platform->Emulating() == marlin) ? "Done saving file." : "";
- HandleReply(gb, false, r);
- return;
- }
- }
-
- // Resend request?
- if (gb.Seen('G'))
- {
- if (gb.GetIValue() == 998)
- {
- if (gb.Seen('P'))
- {
- scratchString.printf("%d\n", gb.GetIValue());
- HandleReply(gb, false, scratchString.Pointer());
- return;
- }
- }
- }
-
- fileBeingWritten->Write(gb.Buffer());
- fileBeingWritten->Write('\n');
- HandleReply(gb, false, "");
-}
-
-// Set up a file to print, but don't print it yet.
-void GCodes::QueueFileToPrint(const char* fileName)
-{
- FileStore * const f = platform->GetFileStore(platform->GetGCodeDir(), fileName, false);
- if (f != nullptr)
- {
- // Cancel current print if there is any
- if (!reprap.GetPrintMonitor()->IsPrinting())
- {
- CancelPrint();
- }
-
- fileGCode->SetToolNumberAdjust(0); // clear tool number adjustment
-
- // Reset all extruder positions when starting a new print
- for (size_t extruder = 0; extruder < MaxExtruders; extruder++)
- {
- lastRawExtruderPosition[extruder] = 0.0;
- rawExtruderTotalByDrive[extruder] = 0.0;
- }
- rawExtruderTotal = 0.0;
- reprap.GetMove()->ResetExtruderPositions();
-
- fileToPrint.Set(f);
- }
- else
- {
- platform->MessageF(GENERIC_MESSAGE, "GCode file \"%s\" not found\n", fileName);
- }
-}
-
-void GCodes::DeleteFile(const char* fileName)
-{
- if (!platform->GetMassStorage()->Delete(platform->GetGCodeDir(), fileName))
- {
- platform->MessageF(GENERIC_MESSAGE, "Could not delete file \"%s\"\n", fileName);
- }
-}
-
-// Function to handle dwell delays. Return true for dwell finished, false otherwise.
-bool GCodes::DoDwell(GCodeBuffer& gb)
-{
- float dwell;
- if (gb.Seen('S'))
- {
- dwell = gb.GetFValue();
- }
- else if (gb.Seen('P'))
- {
- dwell = 0.001 * (float) gb.GetIValue(); // P values are in milliseconds; we need seconds
- }
- else
- {
- return true; // No time given - throw it away
- }
-
-#if SUPPORT_ROLAND
- // Deal with a Roland configuration
- if (reprap.GetRoland()->Active())
- {
- return reprap.GetRoland()->ProcessDwell(gb.GetLValue());
- }
-#endif
-
- // Wait for all the queued moves to stop
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
-
- if (simulationMode != 0)
- {
- simulationTime += dwell;
- return true;
- }
- else
- {
- return DoDwellTime(dwell);
- }
-}
-
-bool GCodes::DoDwellTime(float dwell)
-{
- // Are we already in the dwell?
- if (dwellWaiting)
- {
- if (platform->Time() - dwellTime >= 0.0)
- {
- dwellWaiting = false;
- reprap.GetMove()->ResumeMoving();
- return true;
- }
- return false;
- }
-
- // New dwell - set it up
- dwellWaiting = true;
- dwellTime = platform->Time() + dwell;
- return false;
-}
-
-// Set offset, working and standby temperatures for a tool. I.e. handle a G10.
-bool GCodes::SetOrReportOffsets(GCodeBuffer &gb, StringRef& reply)
-{
- if (gb.Seen('P'))
- {
- int8_t toolNumber = gb.GetIValue();
- toolNumber += gb.GetToolNumberAdjust();
- Tool* tool = reprap.GetTool(toolNumber);
- if (tool == NULL)
- {
- reply.printf("Attempt to set/report offsets and temperatures for non-existent tool: %d", toolNumber);
- return true;
- }
-
- // Deal with setting offsets
- float offset[MAX_AXES];
- for (size_t i = 0; i < MAX_AXES; ++i)
- {
- offset[i] = tool->GetOffset()[i];
- }
-
- bool settingOffset = false;
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- gb.TryGetFValue(axisLetters[axis], offset[axis], settingOffset);
- }
- if (settingOffset)
- {
- if (!LockMovement(gb))
- {
- return false;
- }
- tool->SetOffset(offset);
- }
-
- // Deal with setting temperatures
- bool settingTemps = false;
- size_t hCount = tool->HeaterCount();
- float standby[HEATERS];
- float active[HEATERS];
- if (hCount > 0)
- {
- tool->GetVariables(standby, active);
- if (gb.Seen('R'))
- {
- gb.GetFloatArray(standby, hCount, true);
- settingTemps = true;
- }
- if (gb.Seen('S'))
- {
- gb.GetFloatArray(active, hCount, true);
- settingTemps = true;
- }
-
- if (settingTemps && simulationMode == 0)
- {
- tool->SetVariables(standby, active);
- }
- }
-
- if (!settingOffset && !settingTemps)
- {
- // Print offsets and temperatures
- reply.printf("Tool %d offsets:", toolNumber);
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- reply.catf(" %c%.2f", axisLetters[axis], offset[axis]);
- }
- if (hCount != 0)
- {
- reply.cat(", active/standby temperature(s):");
- for (size_t heater = 0; heater < hCount; heater++)
- {
- reply.catf(" %.1f/%.1f", active[heater], standby[heater]);
- }
- }
- }
- }
- return true;
-}
-
-void GCodes::ManageTool(GCodeBuffer& gb, StringRef& reply)
-{
- if (!gb.Seen('P'))
- {
- // DC temporary code to allow tool numbers to be adjusted so that we don't need to edit multi-media files generated by slic3r
- if (gb.Seen('S'))
- {
- int adjust = gb.GetIValue();
- gb.SetToolNumberAdjust(adjust);
- }
- return;
- }
-
- // Check tool number
- bool seen = false;
- const int toolNumber = gb.GetIValue();
- if (toolNumber < 0)
- {
- platform->Message(GENERIC_MESSAGE, "Tool number must be positive!\n");
- return;
- }
-
- // Check drives
- long drives[MaxExtruders]; // There can never be more than we have...
- size_t dCount = numExtruders; // Sets the limit and returns the count
- if (gb.Seen('D'))
- {
- gb.GetLongArray(drives, dCount);
- seen = true;
- }
- else
- {
- dCount = 0;
- }
-
- // Check heaters
- long heaters[HEATERS];
- size_t hCount = HEATERS;
- if (gb.Seen('H'))
- {
- gb.GetLongArray(heaters, hCount);
- seen = true;
- }
- else
- {
- hCount = 0;
- }
-
- // Check X axis mapping
- uint32_t xMap;
- if (gb.Seen('X'))
- {
- long xMapping[MAX_AXES];
- size_t xCount = numAxes;
- gb.GetLongArray(xMapping, xCount);
- xMap = LongArrayToBitMap(xMapping, xCount) & ((1u << numAxes) - 1);
- seen = true;
- }
- else
- {
- xMap = 1; // by default map X axis straight through
- }
-
- // Check for fan mapping
- uint32_t fanMap;
- if (gb.Seen('F'))
- {
- long fanMapping[NUM_FANS];
- size_t fanCount = NUM_FANS;
- gb.GetLongArray(fanMapping, fanCount);
- fanMap = LongArrayToBitMap(fanMapping, fanCount) & ((1u << NUM_FANS) - 1);
- seen = true;
- }
- else
- {
- fanMap = 1; // by default map fan 0 to fan 0
- }
-
- if (seen)
- {
- // Add or delete tool, so start by deleting the old one with this number, if any
- reprap.DeleteTool(reprap.GetTool(toolNumber));
-
- // M563 P# D-1 H-1 removes an existing tool
- if (dCount == 1 && hCount == 1 && drives[0] == -1 && heaters[0] == -1)
- {
- // nothing more to do
- }
- else
- {
- Tool* tool = Tool::Create(toolNumber, drives, dCount, heaters, hCount, xMap, fanMap);
- if (tool != nullptr)
- {
- reprap.AddTool(tool);
- }
- }
- }
- else
- {
- reprap.PrintTool(toolNumber, reply);
- }
-}
-
-// Does what it says.
-void GCodes::DisableDrives()
-{
- for (size_t drive = 0; drive < DRIVES; drive++)
- {
- platform->DisableDrive(drive);
- }
- SetAllAxesNotHomed();
-}
-
-// Does what it says.
-void GCodes::SetEthernetAddress(GCodeBuffer& gb, int mCode)
-{
- byte eth[4];
- const char* ipString = gb.GetString();
- uint8_t sp = 0;
- uint8_t spp = 0;
- uint8_t ipp = 0;
- while (ipString[sp])
- {
- if (ipString[sp] == '.')
- {
- eth[ipp] = atoi(&ipString[spp]);
- ipp++;
- if (ipp > 3)
- {
- platform->MessageF(GENERIC_MESSAGE, "Dud IP address: %s\n", gb.Buffer());
- return;
- }
- sp++;
- spp = sp;
- }
- else
- {
- sp++;
- }
- }
- eth[ipp] = atoi(&ipString[spp]);
- if (ipp == 3)
- {
- switch (mCode)
- {
- case 552:
- platform->SetIPAddress(eth);
- break;
- case 553:
- platform->SetNetMask(eth);
- break;
- case 554:
- platform->SetGateWay(eth);
- break;
-
- default:
- platform->Message(GENERIC_MESSAGE, "Setting ether parameter - dud code.\n");
- }
- }
- else
- {
- platform->MessageF(GENERIC_MESSAGE, "Dud IP address: %s\n", gb.Buffer());
- }
-}
-
-void GCodes::SetMACAddress(GCodeBuffer& gb)
-{
- uint8_t mac[6];
- const char* ipString = gb.GetString();
- uint8_t sp = 0;
- uint8_t spp = 0;
- uint8_t ipp = 0;
- while (ipString[sp])
- {
- if (ipString[sp] == ':')
- {
- mac[ipp] = strtoul(&ipString[spp], NULL, 16);
- ipp++;
- if (ipp > 5)
- {
- platform->MessageF(GENERIC_MESSAGE, "Dud MAC address: %s\n", gb.Buffer());
- return;
- }
- sp++;
- spp = sp;
- }
- else
- {
- sp++;
- }
- }
- mac[ipp] = strtoul(&ipString[spp], NULL, 16);
- if (ipp == 5)
- {
- platform->SetMACAddress(mac);
- }
- else
- {
- platform->MessageF(GENERIC_MESSAGE, "Dud MAC address: %s\n", gb.Buffer());
- }
-}
-
-bool GCodes::ChangeMicrostepping(size_t drive, int microsteps, int mode) const
-{
- bool dummy;
- unsigned int oldSteps = platform->GetMicrostepping(drive, dummy);
- bool success = platform->SetMicrostepping(drive, microsteps, mode);
- if (success && mode <= 1) // modes higher than 1 are used for special functions
- {
- // We changed the microstepping, so adjust the steps/mm to compensate
- float stepsPerMm = platform->DriveStepsPerUnit(drive);
- if (stepsPerMm > 0)
- {
- platform->SetDriveStepsPerUnit(drive, stepsPerMm * (float)microsteps / (float)oldSteps);
- }
- }
- return success;
-}
-
-// Set the speeds of fans mapped for the current tool to lastDefaultFanSpeed
-void GCodes::SetMappedFanSpeed()
-{
- if (reprap.GetCurrentTool() == nullptr)
- {
- platform->SetFanValue(0, lastDefaultFanSpeed);
- }
- else
- {
- const uint32_t fanMap = reprap.GetCurrentTool()->GetFanMapping();
- for (size_t i = 0; i < NUM_FANS; ++i)
- {
- if ((fanMap & (1u << i)) != 0)
- {
- platform->SetFanValue(i, lastDefaultFanSpeed);
- }
- }
- }
-}
-
-// Handle sending a reply back to the appropriate interface(s).
-// Note that 'reply' may be empty. If it isn't, then we need to append newline when sending it.
-// Also, gb may be null if we were executing a trigger macro.
-void GCodes::HandleReply(GCodeBuffer& gb, bool error, const char* reply)
-{
- // Don't report "ok" responses if a (macro) file is being processed
- // Also check that this response was triggered by a gcode
- if ((gb.MachineState().doingFileMacro || &gb == fileGCode) && reply[0] == 0)
- {
- return;
- }
-
- // Second UART device, e.g. dc42's PanelDue. Do NOT use emulation for this one!
- if (&gb == auxGCode)
- {
- platform->AppendAuxReply(reply);
- return;
- }
-
- const Compatibility c = (&gb == serialGCode || &gb == telnetGCode) ? platform->Emulating() : me;
- MessageType type = GENERIC_MESSAGE;
- if (&gb == httpGCode)
- {
- type = HTTP_MESSAGE;
- }
- else if (&gb == telnetGCode)
- {
- type = TELNET_MESSAGE;
- }
- else if (&gb == serialGCode)
- {
- type = HOST_MESSAGE;
- }
-
- const char* response = (gb.Seen('M') && gb.GetIValue() == 998) ? "rs " : "ok";
- const char* emulationType = 0;
-
- switch (c)
- {
- case me:
- case reprapFirmware:
- if (error)
- {
- platform->Message(type, "Error: ");
- }
- platform->Message(type, reply);
- platform->Message(type, "\n");
- return;
-
- case marlin:
- // We don't need to handle M20 here because we always allocate an output buffer for that one
- if (gb.Seen('M') && gb.GetIValue() == 28)
- {
- platform->Message(type, response);
- platform->Message(type, "\n");
- platform->Message(type, reply);
- platform->Message(type, "\n");
- return;
- }
-
- if ((gb.Seen('M') && gb.GetIValue() == 105) || (gb.Seen('M') && gb.GetIValue() == 998))
- {
- platform->Message(type, response);
- platform->Message(type, " ");
- platform->Message(type, reply);
- platform->Message(type, "\n");
- return;
- }
-
- if (reply[0] != 0 && !gb.IsDoingFileMacro())
- {
- platform->Message(type, reply);
- platform->Message(type, "\n");
- platform->Message(type, response);
- platform->Message(type, "\n");
- }
- else if (reply[0] != 0)
- {
- platform->Message(type, reply);
- platform->Message(type, "\n");
- }
- else
- {
- platform->Message(type, response);
- platform->Message(type, "\n");
- }
- return;
-
- case teacup:
- emulationType = "teacup";
- break;
- case sprinter:
- emulationType = "sprinter";
- break;
- case repetier:
- emulationType = "repetier";
- break;
- default:
- emulationType = "unknown";
- }
-
- if (emulationType != 0)
- {
- platform->MessageF(type, "Emulation of %s is not yet supported.\n", emulationType); // don't send this one to the web as well, it concerns only the USB interface
- }
-}
-
-void GCodes::HandleReply(GCodeBuffer& gb, bool error, OutputBuffer *reply)
-{
- // Although unlikely, it's possible that we get a nullptr reply. Don't proceed if this is the case
- if (reply == nullptr)
- {
- return;
- }
-
- // Second UART device, e.g. dc42's PanelDue. Do NOT use emulation for this one!
- if (&gb == auxGCode)
- {
- platform->AppendAuxReply(reply);
- return;
- }
-
- const Compatibility c = (&gb == serialGCode || &gb == telnetGCode) ? platform->Emulating() : me;
- MessageType type = GENERIC_MESSAGE;
- if (&gb == httpGCode)
- {
- type = HTTP_MESSAGE;
- }
- else if (&gb == telnetGCode)
- {
- type = TELNET_MESSAGE;
- }
- else if (&gb == serialGCode)
- {
- type = HOST_MESSAGE;
- }
-
- const char* response = (gb.Seen('M') && gb.GetIValue() == 998) ? "rs " : "ok";
- const char* emulationType = nullptr;
-
- switch (c)
- {
- case me:
- case reprapFirmware:
- if (error)
- {
- platform->Message(type, "Error: ");
- }
- platform->Message(type, reply);
- return;
-
- case marlin:
- if (gb.Seen('M') && gb.GetIValue() == 20)
- {
- platform->Message(type, "Begin file list\n");
- platform->Message(type, reply);
- platform->Message(type, "End file list\n");
- platform->Message(type, response);
- platform->Message(type, "\n");
- return;
- }
-
- if (gb.Seen('M') && gb.GetIValue() == 28)
- {
- platform->Message(type, response);
- platform->Message(type, "\n");
- platform->Message(type, reply);
- return;
- }
-
- if ((gb.Seen('M') && gb.GetIValue() == 105) || (gb.Seen('M') && gb.GetIValue() == 998))
- {
- platform->Message(type, response);
- platform->Message(type, " ");
- platform->Message(type, reply);
- return;
- }
-
- if (reply->Length() != 0 && !gb.IsDoingFileMacro())
- {
- platform->Message(type, reply);
- platform->Message(type, "\n");
- platform->Message(type, response);
- platform->Message(type, "\n");
- }
- else if (reply->Length() != 0)
- {
- platform->Message(type, reply);
- }
- else
- {
- OutputBuffer::ReleaseAll(reply);
- platform->Message(type, response);
- platform->Message(type, "\n");
- }
- return;
-
- case teacup:
- emulationType = "teacup";
- break;
- case sprinter:
- emulationType = "sprinter";
- break;
- case repetier:
- emulationType = "repetier";
- break;
- default:
- emulationType = "unknown";
- }
-
- // If we get here then we didn't handle the message, so release the buffer(s)
- OutputBuffer::ReleaseAll(reply);
- if (emulationType != 0)
- {
- platform->MessageF(type, "Emulation of %s is not yet supported.\n", emulationType); // don't send this one to the web as well, it concerns only the USB interface
- }
-}
-
-// Set PID parameters (M301 or M304 command). 'heater' is the default heater number to use.
-void GCodes::SetPidParameters(GCodeBuffer& gb, int heater, StringRef& reply)
-{
- if (gb.Seen('H'))
- {
- heater = gb.GetIValue();
- }
-
- if (heater >= 0 && heater < HEATERS)
- {
- PidParameters pp = platform->GetPidParameters(heater);
- bool seen = false;
- gb.TryGetFValue('P', pp.kP, seen);
- gb.TryGetFValue('I', pp.kI, seen);
- gb.TryGetFValue('D', pp.kD, seen);
- gb.TryGetFValue('T', pp.kT, seen);
- gb.TryGetFValue('S', pp.kS, seen);
-
- if (seen)
- {
- platform->SetPidParameters(heater, pp);
- reprap.GetHeat()->UseModel(heater, false);
- }
- else
- {
- reply.printf("Heater %d P:%.2f I:%.3f D:%.2f T:%.2f S:%.2f", heater, pp.kP, pp.kI, pp.kD, pp.kT, pp.kS);
- }
- }
-}
-
-void GCodes::SetHeaterParameters(GCodeBuffer& gb, StringRef& reply)
-{
- if (gb.Seen('P'))
- {
- int heater = gb.GetIValue();
- if (heater >= 0 && heater < HEATERS)
- {
- Thermistor& th = platform->GetThermistor(heater);
- bool seen = false;
-
- // We must set the 25C resistance and beta together in order to calculate Rinf. Check for these first.
- float r25 = th.GetR25();
- float beta = th.GetBeta();
- float shC = th.GetShc();
- float seriesR = th.GetSeriesR();
-
- gb.TryGetFValue('T', r25, seen);
- gb.TryGetFValue('B', beta, seen);
- gb.TryGetFValue('C', shC, seen);
- gb.TryGetFValue('R', seriesR, seen);
- if (seen)
- {
- th.SetParameters(r25, beta, shC, seriesR);
- }
-
- if (gb.Seen('L'))
- {
- th.SetLowOffset((int8_t)constrain<int>(gb.GetIValue(), -100, 100));
- seen = true;
- }
- if (gb.Seen('H'))
- {
- th.SetHighOffset((int8_t)constrain<int>(gb.GetIValue(), -100, 100));
- seen = true;
- }
-
- if (gb.Seen('X'))
- {
- int thermistor = gb.GetIValue();
- if ( (0 <= thermistor && thermistor < HEATERS)
- || ((int)FirstThermocoupleChannel <= thermistor && thermistor < (int)(FirstThermocoupleChannel + MaxSpiTempSensors))
- || ((int)FirstRtdChannel <= thermistor && thermistor < (int)(FirstRtdChannel + MaxSpiTempSensors))
- )
- {
- platform->SetThermistorNumber(heater, thermistor);
- }
- else
- {
- platform->MessageF(GENERIC_MESSAGE, "Thermistor number %d is out of range\n", thermistor);
- }
- seen = true;
- }
-
- if (!seen)
- {
- reply.printf("T:%.1f B:%.1f C:%.2e R:%.1f L:%d H:%d X:%d",
- th.GetR25(), th.GetBeta(), th.GetShc(), th.GetSeriesR(),
- th.GetLowOffset(), th.GetHighOffset(), platform->GetThermistorNumber(heater));
- }
- }
- else
- {
- platform->MessageF(GENERIC_MESSAGE, "Heater number %d is out of range\n", heater);
- }
- }
-}
-
-void GCodes::SetToolHeaters(Tool *tool, float temperature)
-{
- if (tool == NULL)
- {
- platform->Message(GENERIC_MESSAGE, "Setting temperature: no tool selected.\n");
- return;
- }
-
- float standby[HEATERS];
- float active[HEATERS];
- tool->GetVariables(standby, active);
- for (size_t h = 0; h < tool->HeaterCount(); h++)
- {
- active[h] = temperature;
- }
- tool->SetVariables(standby, active);
-}
-
-// Retract or un-retract filament, returning true if movement has been queued, false if this needs to be called again
-bool GCodes::RetractFilament(bool retract)
-{
- if (retractLength != 0.0 || retractHop != 0.0 || (!retract && retractExtra != 0.0))
- {
- const Tool *tool = reprap.GetCurrentTool();
- if (tool != nullptr)
- {
- size_t nDrives = tool->DriveCount();
- if (nDrives != 0)
- {
- if (moveAvailable)
- {
- return false;
- }
-
- reprap.GetMove()->GetCurrentUserPosition(moveBuffer.coords, 0, reprap.GetCurrentXAxes());
- for (size_t i = numAxes; i < DRIVES; ++i)
- {
- moveBuffer.coords[i] = 0.0;
- }
- // Set the feed rate. If there is any Z hop then we need to pass the Z speed, else we pass the extrusion speed.
- const float speedToUse = (retract) ? retractSpeed : unRetractSpeed;
- moveBuffer.feedRate = (retractHop == 0.0)
- ? speedToUse * secondsToMinutes
- : speedToUse * secondsToMinutes * retractHop/retractLength;
- moveBuffer.coords[Z_AXIS] += (retract) ? retractHop : -retractHop;
- const float lengthToUse = (retract) ? -retractLength : retractLength + retractExtra;
- for (size_t i = 0; i < nDrives; ++i)
- {
- moveBuffer.coords[E0_AXIS + tool->Drive(i)] = lengthToUse;
- }
-
- moveBuffer.isFirmwareRetraction = true;
- moveBuffer.usePressureAdvance = false;
- moveBuffer.filePos = filePos;
- moveBuffer.xAxes = reprap.GetCurrentXAxes();
- moveAvailable = true;
- }
- }
- }
- return true;
-}
-
-// If the code to act on is completed, this returns true,
-// otherwise false. It is called repeatedly for a given
-// code until it returns true for that code.
-bool GCodes::ActOnCode(GCodeBuffer& gb, StringRef& reply)
-{
- // Discard empty buffers right away
- if (gb.IsEmpty())
- {
- return true;
- }
-
- // M-code parameters might contain letters T and G, e.g. in filenames.
- // dc42 assumes that G-and T-code parameters never contain the letter M.
- // Therefore we must check for an M-code first.
- if (gb.Seen('M'))
- {
- return HandleMcode(gb, reply);
- }
- // dc42 doesn't think a G-code parameter ever contains letter T, or a T-code ever contains letter G.
- // So it doesn't matter in which order we look for them.
- if (gb.Seen('G'))
- {
- return HandleGcode(gb, reply);
- }
- if (gb.Seen('T'))
- {
- return HandleTcode(gb, reply);
- }
-
- // An invalid or queued buffer gets discarded
- HandleReply(gb, false, "");
- return true;
-}
-
-bool GCodes::HandleGcode(GCodeBuffer& gb, StringRef& reply)
-{
- bool result = true;
- bool error = false;
-
- int code = gb.GetIValue();
- if (simulationMode != 0 && code != 0 && code != 1 && code != 4 && code != 10 && code != 20 && code != 21 && code != 90 && code != 91 && code != 92)
- {
- return true; // we only simulate some gcodes
- }
-
- switch (code)
- {
- case 0: // There are no rapid moves...
- case 1: // Ordinary move
- if (!LockMovement(gb))
- {
- return false;
- }
- {
- // Check for 'R' parameter here to go back to the coordinates at which the print was paused
- // NOTE: restore point 2 (tool change) won't work when changing tools on dual axis machines because of X axis mapping.
- // We could possibly fix this by saving the virtual X axis position instead of the physical axis positions.
- // However, slicers normally command the tool to the correct place after a tool change, so we don't need this feature anyway.
- int rParam = (gb.Seen('R')) ? gb.GetIValue() : 0;
- RestorePoint *rp = (rParam == 1) ? &pauseRestorePoint : (rParam == 2) ? &toolChangeRestorePoint : nullptr;
- if (rp != nullptr)
- {
- if (moveAvailable)
- {
- return false;
- }
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- float offset = gb.Seen(axisLetters[axis]) ? gb.GetFValue() * distanceScale : 0.0;
- moveBuffer.coords[axis] = rp->moveCoords[axis] + offset;
- }
- // For now we don't handle extrusion at the same time
- for (size_t drive = numAxes; drive < DRIVES; ++drive)
- {
- moveBuffer.coords[drive] = 0.0;
- }
- moveBuffer.feedRate = (gb.Seen(feedrateLetter)) ? gb.GetFValue() : feedRate;
- moveBuffer.filePos = noFilePosition;
- moveBuffer.usePressureAdvance = false;
- moveAvailable = true;
- }
- else
- {
- int res = SetUpMove(gb, reply);
- if (res == 2)
- {
- gb.SetState(GCodeState::waitingForMoveToComplete);
- }
- result = (res != 0);
- }
- }
- break;
-
- case 4: // Dwell
- result = DoDwell(gb);
- break;
-
- case 10: // Set/report offsets and temperatures, or retract
- if (gb.Seen('P'))
- {
- if (!SetOrReportOffsets(gb, reply))
- {
- return false;
- }
- }
- else
- {
- if (!LockMovement(gb))
- {
- return false;
- }
- result = RetractFilament(true);
- }
- break;
-
- case 11: // Un-retract
- if (!LockMovement(gb))
- {
- return false;
- }
- result = RetractFilament(false);
- break;
-
- case 20: // Inches (which century are we living in, here?)
- distanceScale = INCH_TO_MM;
- break;
-
- case 21: // mm
- distanceScale = 1.0;
- break;
-
- case 28: // Home
- result = DoHome(gb, reply, error);
- break;
-
- case 29: // Grid-based bed probing
- if (!LockMovementAndWaitForStandstill(gb)) // do this first to make sure that a new grid isn't being defined
- {
- return false;
- }
- error = ProbeGrid(gb, reply);
- break;
-
- case 30: // Z probe/manually set at a position and set that as point P
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- if (reprap.GetMove()->IsDeltaMode() && !AllAxesAreHomed())
- {
- reply.copy("Must home a delta printer before bed probing");
- error = true;
- }
- else
- {
- result = SetSingleZProbeAtAPosition(gb, reply);
- }
- break;
-
- case 31: // Return the probe value, or set probe variables
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- result = SetPrintZProbe(gb, reply);
- break;
-
- case 32: // Probe Z at multiple positions and generate the bed transform
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
-
- // Try to execute bed.g
- if (!DoFileMacro(gb, BED_EQUATION_G, reprap.GetMove()->IsDeltaMode()))
- {
- // If we get here then we are not on a delta printer and there is no bed.g file
- if (GetAxisIsHomed(X_AXIS) && GetAxisIsHomed(Y_AXIS))
- {
- gb.SetState(GCodeState::setBed1); // no bed.g file, so use the coordinates specified by M557
- }
- else
- {
- // We can only do bed levelling if X and Y have already been homed
- reply.copy("Must home X and Y before bed probing");
- error = true;
- }
- }
- break;
-
- case 90: // Absolute coordinates
- gb.MachineState().axesRelative = false;
- break;
-
- case 91: // Relative coordinates
- gb.MachineState().axesRelative = true; // Axis movements (i.e. X, Y and Z)
- break;
-
- case 92: // Set position
- result = SetPositions(gb);
- break;
-
- default:
- error = true;
- reply.printf("invalid G Code: %s", gb.Buffer());
- }
-
- if (result && gb.GetState() == GCodeState::normal)
- {
- UnlockAll(gb);
- HandleReply(gb, error, reply.Pointer());
- }
- return result;
-}
-
-bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply)
-{
- bool result = true;
- bool error = false;
-
- const int code = gb.GetIValue();
- if (simulationMode != 0 && (code < 20 || code > 37) && code != 0 && code != 1 && code != 82 && code != 83 && code != 105 && code != 111 && code != 112 && code != 122 && code != 408 && code != 999)
- {
- return true; // we don't yet simulate most M codes
- }
-
- switch (code)
- {
- case 0: // Stop
- case 1: // Sleep
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- {
- bool wasPaused = isPaused; // isPaused gets cleared by CancelPrint
- CancelPrint();
- if (wasPaused)
- {
- reply.copy("Print cancelled");
- // If we are cancelling a paused print with M0 and cancel.g exists then run it and do nothing else
- if (code == 0)
- {
- if (DoFileMacro(gb, CANCEL_G, false))
- {
- break;
- }
- }
- }
- }
-
- gb.SetState((code == 0) ? GCodeState::stopping : GCodeState::sleeping);
- DoFileMacro(gb, (code == 0) ? STOP_G : SLEEP_G, false);
- break;
-
-#if SUPPORT_ROLAND
- case 3: // Spin spindle
- if (reprap.GetRoland()->Active())
- {
- if (gb.Seen('S'))
- {
- result = reprap.GetRoland()->ProcessSpindle(gb.GetFValue());
- }
- }
- break;
-#endif
-
- case 18: // Motors off
- case 84:
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- {
- bool seen = false;
- for (size_t axis = 0; axis < numAxes; axis++)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- SetAxisNotHomed(axis);
- platform->DisableDrive(axis);
- seen = true;
- }
- }
-
- if (gb.Seen(extrudeLetter))
- {
- long int eDrive[MaxExtruders];
- size_t eCount = numExtruders;
- gb.GetLongArray(eDrive, eCount);
- for (size_t i = 0; i < eCount; i++)
- {
- seen = true;
- if (eDrive[i] < 0 || (size_t)eDrive[i] >= numExtruders)
- {
- reply.printf("Invalid extruder number specified: %ld", eDrive[i]);
- error = true;
- break;
- }
- platform->DisableDrive(numAxes + eDrive[i]);
- }
- }
-
- if (gb.Seen('S'))
- {
- seen = true;
-
- float idleTimeout = gb.GetFValue();
- if (idleTimeout < 0.0)
- {
- reply.copy("Idle timeouts cannot be negative!");
- error = true;
- }
- else
- {
- reprap.GetMove()->SetIdleTimeout(idleTimeout);
- }
- }
-
- if (!seen)
- {
- DisableDrives();
- }
- }
- break;
-
- case 20: // List files on SD card
- if (!LockFileSystem(gb)) // don't allow more than one at a time to avoid contention on output buffers
- {
- return false;
- }
- {
- OutputBuffer *fileResponse;
- const int sparam = (gb.Seen('S')) ? gb.GetIValue() : 0;
- const char* dir = (gb.Seen('P')) ? gb.GetString() : platform->GetGCodeDir();
-
- if (sparam == 2)
- {
- fileResponse = reprap.GetFilesResponse(dir, true); // Send the file list in JSON format
- fileResponse->cat('\n');
- }
- else
- {
- if (!OutputBuffer::Allocate(fileResponse))
- {
- // Cannot allocate an output buffer, try again later
- return false;
- }
-
- // To mimic the behaviour of the official RepRapPro firmware:
- // If we are emulating RepRap then we print "GCode files:\n" at the start, otherwise we don't.
- // If we are emulating Marlin and the code came via the serial/USB interface, then we don't put quotes around the names and we separate them with newline;
- // otherwise we put quotes around them and separate them with comma.
- if (platform->Emulating() == me || platform->Emulating() == reprapFirmware)
- {
- fileResponse->copy("GCode files:\n");
- }
-
- bool encapsulateList = ((&gb != serialGCode && &gb != telnetGCode) || platform->Emulating() != marlin);
- FileInfo fileInfo;
- if (platform->GetMassStorage()->FindFirst(dir, fileInfo))
- {
- // iterate through all entries and append each file name
- do {
- if (encapsulateList)
- {
- fileResponse->catf("%c%s%c%c", FILE_LIST_BRACKET, fileInfo.fileName, FILE_LIST_BRACKET, FILE_LIST_SEPARATOR);
- }
- else
- {
- fileResponse->catf("%s\n", fileInfo.fileName);
- }
- } while (platform->GetMassStorage()->FindNext(fileInfo));
-
- if (encapsulateList)
- {
- // remove the last separator
- (*fileResponse)[fileResponse->Length() - 1] = 0;
- }
- }
- else
- {
- fileResponse->cat("NONE\n");
- }
- }
-
- UnlockAll(gb);
- HandleReply(gb, false, fileResponse);
- return true;
- }
-
- case 21: // Initialise SD card
- if (!LockFileSystem(gb)) // don't allow more than one at a time to avoid contention on output buffers
- {
- return false;
- }
- {
- size_t card = (gb.Seen('P')) ? gb.GetIValue() : 0;
- result = platform->GetMassStorage()->Mount(card, reply, true);
- }
- break;
-
- case 22: // Release SD card
- if (!LockFileSystem(gb)) // don't allow more than one at a time to avoid contention on output buffers
- {
- return false;
- }
- {
- size_t card = (gb.Seen('P')) ? gb.GetIValue() : 0;
- result = platform->GetMassStorage()->Unmount(card, reply);
- }
- break;
-
- case 23: // Set file to print
- case 32: // Select file and start SD print
- if (fileGCode->OriginalMachineState().fileState.IsLive())
- {
- reply.copy("Cannot set file to print, because a file is already being printed");
- error = true;
- break;
- }
-
- if (code == 32 && !LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- {
- const char* filename = gb.GetUnprecedentedString();
- if (filename != nullptr)
- {
- QueueFileToPrint(filename);
- if (fileToPrint.IsLive())
- {
- reprap.GetPrintMonitor()->StartingPrint(filename);
- if (platform->Emulating() == marlin && (&gb == serialGCode || &gb == telnetGCode))
- {
- reply.copy("File opened\nFile selected");
- }
- else
- {
- // Command came from web interface or PanelDue, or not emulating Marlin, so send a nicer response
- reply.printf("File %s selected for printing", filename);
- }
-
- if (code == 32)
- {
- fileGCode->OriginalMachineState().fileState.MoveFrom(fileToPrint);
- reprap.GetPrintMonitor()->StartedPrint();
- }
- }
- else
- {
- reply.printf("Failed to open file %s", filename);
- }
- }
- }
- break;
-
- case 24: // Print/resume-printing the selected file
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
-
- if (isPaused)
- {
- gb.SetState(GCodeState::resuming1);
- DoFileMacro(gb, RESUME_G);
- }
- else if (!fileToPrint.IsLive())
- {
- reply.copy("Cannot print, because no file is selected!");
- error = true;
- }
- else
- {
- fileGCode->OriginalMachineState().fileState.MoveFrom(fileToPrint);
- reprap.GetPrintMonitor()->StartedPrint();
- }
- break;
-
- case 226: // Gcode Initiated Pause
- if (&gb == fileGCode) // ignore M226 if it did't come from within a file being printed
- {
- DoPause(gb);
- }
- break;
-
- case 25: // Pause the print
- if (isPaused)
- {
- reply.copy("Printing is already paused!!");
- error = true;
- }
- else if (!reprap.GetPrintMonitor()->IsPrinting())
- {
- reply.copy("Cannot pause print, because no file is being printed!");
- error = true;
- }
- else
- {
- if (!LockMovement(gb)) // lock movement before calling DoPause
- {
- return false;
- }
- DoPause(gb);
- }
- break;
-
- case 26: // Set SD position
- if (gb.Seen('S'))
- {
- const FilePosition value = gb.GetIValue();
- if (value < 0)
- {
- reply.copy("SD positions can't be negative!");
- error = true;
- }
- else if (fileGCode->OriginalMachineState().fileState.IsLive())
- {
- if (!fileGCode->OriginalMachineState().fileState.Seek(value))
- {
- reply.copy("The specified SD position is invalid!");
- error = true;
- }
- }
- else if (fileToPrint.IsLive())
- {
- if (!fileToPrint.Seek(value))
- {
- reply.copy("The specified SD position is invalid!");
- error = true;
- }
- }
- else
- {
- reply.copy("Cannot set SD file position, because no print is in progress!");
- error = true;
- }
- }
- else
- {
- reply.copy("You must specify the SD position in bytes using the S parameter.");
- error = true;
- }
- break;
-
- case 27: // Report print status - Deprecated
- if (reprap.GetPrintMonitor()->IsPrinting())
- {
- // Pronterface keeps sending M27 commands if "Monitor status" is checked, and it specifically expects the following response syntax
- FileData& fileBeingPrinted = fileGCode->OriginalMachineState().fileState;
- reply.printf("SD printing byte %lu/%lu", fileBeingPrinted.GetPosition(), fileBeingPrinted.Length());
- }
- else
- {
- reply.copy("Not SD printing.");
- }
- break;
-
- case 28: // Write to file
- {
- const char* str = gb.GetUnprecedentedString();
- if (str != nullptr)
- {
- bool ok = OpenFileToWrite(gb, platform->GetGCodeDir(), str);
- if (ok)
- {
- reply.printf("Writing to file: %s", str);
- }
- else
- {
- reply.printf("Can't open file %s for writing.", str);
- error = true;
- }
- }
- }
- break;
-
- case 29: // End of file being written; should be intercepted before getting here
- reply.copy("GCode end-of-file being interpreted.");
- break;
-
- case 30: // Delete file
- {
- const char *filename = gb.GetUnprecedentedString();
- if (filename != nullptr)
- {
- DeleteFile(filename);
- }
- }
- break;
-
- // For case 32, see case 24
-
- case 36: // Return file information
- if (!LockFileSystem(gb)) // getting file info takes several calls and isn't reentrant
- {
- return false;
- }
- {
- const char* filename = gb.GetUnprecedentedString(true); // get filename, or nullptr if none provided
- OutputBuffer *fileInfoResponse;
- result = reprap.GetPrintMonitor()->GetFileInfoResponse(filename, fileInfoResponse);
- if (result)
- {
- fileInfoResponse->cat('\n');
- UnlockAll(gb);
- HandleReply(gb, false, fileInfoResponse);
- return true;
- }
- }
- break;
-
- case 37: // Simulation mode on/off
- if (gb.Seen('S'))
- {
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
-
- bool wasSimulating = (simulationMode != 0);
- simulationMode = (uint8_t)gb.GetIValue();
- reprap.GetMove()->Simulate(simulationMode);
-
- if (simulationMode != 0)
- {
- simulationTime = 0.0;
- if (!wasSimulating)
- {
- // Starting a new simulation, so save the current position
- reprap.GetMove()->GetCurrentUserPosition(simulationRestorePoint.moveCoords, 0, reprap.GetCurrentXAxes());
- simulationRestorePoint.feedRate = feedRate;
- }
- }
- else if (wasSimulating)
- {
- // Ending a simulation, so restore the position
- SetPositions(simulationRestorePoint.moveCoords);
- for (size_t i = 0; i < DRIVES; ++i)
- {
- moveBuffer.coords[i] = simulationRestorePoint.moveCoords[i];
- }
- feedRate = simulationRestorePoint.feedRate;
- }
- }
- else
- {
- reply.printf("Simulation mode: %s, move time: %.1f sec, other time: %.1f sec",
- (simulationMode != 0) ? "on" : "off", reprap.GetMove()->GetSimulationTime(), simulationTime);
- }
- break;
-
- case 38: // Report SHA1 of file
- if (!LockFileSystem(gb)) // getting file hash takes several calls and isn't reentrant
- {
- return false;
- }
- if (fileBeingHashed == nullptr)
- {
- // See if we can open the file and start hashing
- const char* filename = gb.GetUnprecedentedString(true);
- if (StartHash(filename))
- {
- // Hashing is now in progress...
- result = false;
- }
- else
- {
- error = true;
- reply.printf("Cannot open file: %s", filename);
- }
- }
- else
- {
- // This can take some time. All the actual heavy lifting is in dedicated methods
- result = AdvanceHash(reply);
- }
- break;
-
- case 42: // Turn an output pin on or off
- if (gb.Seen('P'))
- {
- const int logicalPin = gb.GetIValue();
- Pin pin;
- bool invert;
- if (platform->GetFirmwarePin(logicalPin, PinAccess::pwm, pin, invert))
- {
- if (gb.Seen('S'))
- {
- float val = gb.GetFValue();
- if (val > 1.0)
- {
- val /= 255.0;
- }
- val = constrain<float>(val, 0.0, 1.0);
- if (invert)
- {
- val = 1.0 - val;
- }
- Platform::WriteAnalog(pin, val, DefaultPinWritePwmFreq);
- }
- // Ignore the command if no S parameter provided
- }
- else
- {
- reply.printf("Logical pin %d is not available for writing", logicalPin);
- error = true;
- }
- }
- break;
-
- case 80: // ATX power on
- platform->SetAtxPower(true);
- break;
-
- case 81: // ATX power off
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- platform->SetAtxPower(false);
- break;
-
- case 82: // Use absolute extruder positioning
- if (gb.MachineState().drivesRelative) // don't reset the absolute extruder position if it was already absolute
- {
- for (size_t extruder = 0; extruder < MaxExtruders; extruder++)
- {
- lastRawExtruderPosition[extruder] = 0.0;
- }
- gb.MachineState().drivesRelative = false;
- }
- break;
-
- case 83: // Use relative extruder positioning
- if (!gb.MachineState().drivesRelative) // don't reset the absolute extruder position if it was already relative
- {
- for (size_t extruder = 0; extruder < MaxExtruders; extruder++)
- {
- lastRawExtruderPosition[extruder] = 0.0;
- }
- gb.MachineState().drivesRelative = true;
- }
- break;
-
- // For case 84, see case 18
-
- case 85: // Set inactive time
- break;
-
- case 92: // Set/report steps/mm for some axes
- {
- // Save the current positions as we may need them later
- float positionNow[DRIVES];
- Move *move = reprap.GetMove();
- move->GetCurrentUserPosition(positionNow, 0, reprap.GetCurrentXAxes());
-
- bool seen = false;
- for (size_t axis = 0; axis < numAxes; axis++)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- platform->SetDriveStepsPerUnit(axis, gb.GetFValue());
- seen = true;
- }
- }
-
- if (gb.Seen(extrudeLetter))
- {
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- seen = true;
- float eVals[MaxExtruders];
- size_t eCount = numExtruders;
- gb.GetFloatArray(eVals, eCount, true);
-
- // The user may not have as many extruders as we allow for, so just set the ones for which a value is provided
- for (size_t e = 0; e < eCount; e++)
- {
- platform->SetDriveStepsPerUnit(numAxes + e, eVals[e]);
- }
- }
-
- if (seen)
- {
- // On a delta, if we change the drive steps/mm then we need to recalculate the motor positions
- SetPositions(positionNow);
- }
- else
- {
- reply.copy("Steps/mm: ");
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- reply.catf("%c: %.3f, ", axisLetters[axis], platform->DriveStepsPerUnit(axis));
- }
- reply.catf("E:");
- char sep = ' ';
- for (size_t extruder = 0; extruder < numExtruders; extruder++)
- {
- reply.catf("%c%.3f", sep, platform->DriveStepsPerUnit(extruder + numAxes));
- sep = ':';
- }
- }
- }
- break;
-
- case 98: // Call Macro/Subprogram
- if (gb.Seen('P'))
- {
- DoFileMacro(gb, gb.GetString());
- }
- break;
-
- case 99: // Return from Macro/Subprogram
- FileMacroCyclesReturn(gb);
- break;
-
- case 104: // Deprecated. This sets the active temperature of every heater of the active tool
- if (gb.Seen('S'))
- {
- float temperature = gb.GetFValue();
- Tool* tool;
- if (gb.Seen('T'))
- {
- int toolNumber = gb.GetIValue();
- toolNumber += gb.GetToolNumberAdjust();
- tool = reprap.GetTool(toolNumber);
- }
- else
- {
- tool = reprap.GetCurrentTool();
- // If no tool is selected, and there is only one tool, set the active temperature for that one
- if (tool == nullptr)
- {
- tool = reprap.GetOnlyTool();
- }
- }
- SetToolHeaters(tool, temperature);
- }
- break;
-
- case 105: // Get temperatures
- {
- const int8_t bedHeater = reprap.GetHeat()->GetBedHeater();
- const int8_t chamberHeater = reprap.GetHeat()->GetChamberHeater();
- reply.copy("T:");
- for (int8_t heater = 0; heater < HEATERS; heater++)
- {
- if (heater != bedHeater && heater != chamberHeater)
- {
- Heat::HeaterStatus hs = reprap.GetHeat()->GetStatus(heater);
- if (hs != Heat::HS_off && hs != Heat::HS_fault)
- {
- reply.catf("%.1f ", reprap.GetHeat()->GetTemperature(heater));
- }
- }
- }
- if (bedHeater >= 0)
- {
- reply.catf("B:%.1f", reprap.GetHeat()->GetTemperature(bedHeater));
- }
- else
- {
- // I'm not sure whether Pronterface etc. can handle a missing bed temperature, so return zero
- reply.cat("B:0.0");
- }
- if (chamberHeater >= 0.0)
- {
- reply.catf(" C:%.1f", reprap.GetHeat()->GetTemperature(chamberHeater));
- }
- }
- break;
-
- case 106: // Set/report fan values
- {
- bool seenFanNum = false;
- int32_t fanNum = 0; // Default to the first fan
- gb.TryGetIValue('P', fanNum, seenFanNum);
- if (fanNum < 0 || fanNum > (int)NUM_FANS)
- {
- reply.printf("Fan number %d is invalid, must be between 0 and %u", fanNum, NUM_FANS);
- }
- else
- {
- bool seen = false;
- Fan& fan = platform->GetFan(fanNum);
-
- if (gb.Seen('I')) // Invert cooling
- {
- const int invert = gb.GetIValue();
- if (invert < 0)
- {
- fan.Disable();
- }
- else
- {
- fan.SetInverted(invert > 0);
- }
- seen = true;
- }
-
- if (gb.Seen('F')) // Set PWM frequency
- {
- fan.SetPwmFrequency(gb.GetFValue());
- seen = true;
- }
-
- if (gb.Seen('T')) // Set thermostatic trigger temperature
- {
- seen = true;
- fan.SetTriggerTemperature(gb.GetFValue());
- }
-
- if (gb.Seen('B')) // Set blip time
- {
- seen = true;
- fan.SetBlipTime(gb.GetFValue());
- }
-
- if (gb.Seen('L')) // Set minimum speed
- {
- seen = true;
- fan.SetMinValue(gb.GetFValue());
- }
-
- if (gb.Seen('H')) // Set thermostatically-controller heaters
- {
- seen = true;
- long heaters[HEATERS];
- size_t numH = HEATERS;
- gb.GetLongArray(heaters, numH);
- // Note that M106 H-1 disables thermostatic mode. The following code implements that automatically.
- uint16_t hh = 0;
- for (size_t h = 0; h < numH; ++h)
- {
- const int hnum = heaters[h];
- if (hnum >= 0 && hnum < HEATERS)
- {
- hh |= (1u << (unsigned int)hnum);
- }
- }
- if (hh != 0)
- {
- platform->SetFanValue(fanNum, 1.0); // default the fan speed to full for safety
- }
- fan.SetHeatersMonitored(hh);
- }
-
- if (gb.Seen('S')) // Set new fan value - process this after processing 'H' or it may not be acted on
- {
- const float f = constrain<float>(gb.GetFValue(), 0.0, 255.0);
- if (seen || seenFanNum)
- {
- platform->SetFanValue(fanNum, f);
- }
- else
- {
- // We are processing an M106 S### command with no other recognised parameters and we have a tool selected.
- // Apply the fan speed setting to the fans in the fan mapping for the current tool.
- lastDefaultFanSpeed = f;
- SetMappedFanSpeed();
- }
- }
- else if (gb.Seen('R'))
- {
- const int i = gb.GetIValue();
- switch(i)
- {
- case 0:
- case 1:
- // Restore fan speed to value when print was paused
- platform->SetFanValue(fanNum, pausedFanValues[fanNum]);
- break;
- case 2:
- // Set the speeds of mapped fans to the last known value. Fan number is ignored.
- SetMappedFanSpeed();
- break;
- default:
- break;
- }
- }
- else if (!seen)
- {
- reply.printf("Fan%i frequency: %dHz, speed: %d%%, min: %d%%, blip: %.2f, inverted: %s",
- fanNum,
- (int)(fan.GetPwmFrequency()),
- (int)(fan.GetValue() * 100.0),
- (int)(fan.GetMinValue() * 100.0),
- fan.GetBlipTime(),
- (fan.GetInverted()) ? "yes" : "no");
- uint16_t hh = fan.GetHeatersMonitored();
- if (hh != 0)
- {
- reply.catf(", trigger: %dC, heaters:", (int)fan.GetTriggerTemperature());
- for (unsigned int i = 0; i < HEATERS; ++i)
- {
- if ((hh & (1u << i)) != 0)
- {
- reply.catf(" %u", i);
- }
- }
- }
- }
- }
- }
- break;
-
- case 107: // Fan off - deprecated
- platform->SetFanValue(0, 0.0); //T3P3 as deprecated only applies to fan0
- break;
-
- case 108: // Cancel waiting for temperature
- if (isWaiting)
- {
- cancelWait = true;
- }
- break;
-
- case 109: // Deprecated in RRF, but widely generated by slicers
- {
- float temperature;
- bool waitWhenCooling;
- if (gb.Seen('R'))
- {
- waitWhenCooling = true;
- temperature = gb.GetFValue();
- }
- else if (gb.Seen('S'))
- {
- waitWhenCooling = false;
- temperature = gb.GetFValue();
- }
- else
- {
- break; // no target temperature given
- }
-
- Tool *tool;
- if (gb.Seen('T'))
- {
- int toolNumber = gb.GetIValue();
- toolNumber += gb.GetToolNumberAdjust();
- tool = reprap.GetTool(toolNumber);
- }
- else
- {
- tool = reprap.GetCurrentTool();
- // If no tool is selected, and there is only one tool, set the active temperature for that one
- if (tool == nullptr)
- {
- tool = reprap.GetOnlyTool();
- }
- }
- SetToolHeaters(tool, temperature);
- if (cancelWait || ToolHeatersAtSetTemperatures(tool, waitWhenCooling))
- {
- cancelWait = isWaiting = false;
- break;
- }
- // In Marlin emulation mode we should return some sort of (undocumented) message here every second...
- isWaiting = true;
- return false;
- }
- break;
-
- case 110: // Set line numbers - line numbers are dealt with in the GCodeBuffer class
- break;
-
- case 111: // Debug level
- if (gb.Seen('S'))
- {
- bool dbv = (gb.GetIValue() != 0);
- if (gb.Seen('P'))
- {
- reprap.SetDebug(static_cast<Module>(gb.GetIValue()), dbv);
- }
- else
- {
- reprap.SetDebug(dbv);
- }
- }
- else
- {
- reprap.PrintDebug();
- }
- break;
-
- case 112: // Emergency stop - acted upon in Webserver, but also here in case it comes from USB etc.
- DoEmergencyStop();
- break;
-
- case 114:
- GetCurrentCoordinates(reply);
- break;
-
- case 115: // Print firmware version or set hardware type
- if (gb.Seen('P'))
- {
- platform->SetBoardType((BoardType)gb.GetIValue());
- }
- else
- {
- reply.printf("FIRMWARE_NAME: %s FIRMWARE_VERSION: %s ELECTRONICS: %s", NAME, VERSION, platform->GetElectronicsString());
-#ifdef DUET_NG
- const char* expansionName = DuetExpansion::GetExpansionBoardName();
- if (expansionName != nullptr)
- {
- reply.catf(" + %s", expansionName);
- }
-#endif
- reply.catf(" FIRMWARE_DATE: %s", DATE);
- }
- break;
-
- case 116: // Wait for set temperatures
- {
- bool seen = false;
- if (gb.Seen('P'))
- {
- // Wait for the heaters associated with the specified tool to be ready
- int toolNumber = gb.GetIValue();
- toolNumber += gb.GetToolNumberAdjust();
- if (!cancelWait && !ToolHeatersAtSetTemperatures(reprap.GetTool(toolNumber), true))
- {
- isWaiting = true;
- return false;
- }
- seen = true;
- }
-
- if (gb.Seen('H'))
- {
- // Wait for specified heaters to be ready
- long heaters[HEATERS];
- size_t heaterCount = HEATERS;
- gb.GetLongArray(heaters, heaterCount);
- if (!cancelWait)
- {
- for (size_t i=0; i<heaterCount; i++)
- {
- if (!reprap.GetHeat()->HeaterAtSetTemperature(heaters[i], true))
- {
- isWaiting = true;
- return false;
- }
- }
- }
- seen = true;
- }
-
- if (gb.Seen('C'))
- {
- // Wait for chamber heater to be ready
- const int8_t chamberHeater = reprap.GetHeat()->GetChamberHeater();
- if (chamberHeater != -1)
- {
- if (!cancelWait && !reprap.GetHeat()->HeaterAtSetTemperature(chamberHeater, true))
- {
- isWaiting = true;
- return false;
- }
- }
- seen = true;
- }
-
- // Wait for all heaters to be ready
- if (!seen && !cancelWait && !reprap.GetHeat()->AllHeatersAtSetTemperatures(true))
- {
- isWaiting = true;
- return false;
- }
-
- // If we get here, there is nothing more to wait for
- cancelWait = isWaiting = false;
- }
- break;
-
- case 117: // Display message
- {
- const char *msg = gb.GetUnprecedentedString(true);
- reprap.SetMessage((msg == nullptr) ? "" : msg);
- }
- break;
-
- case 119:
- reply.copy("Endstops - ");
- for (size_t axis = 0; axis < numAxes; axis++)
- {
- reply.catf("%c: %s, ", axisLetters[axis], TranslateEndStopResult(platform->Stopped(axis)));
- }
- reply.catf("Z probe: %s", TranslateEndStopResult(platform->GetZProbeResult()));
- break;
-
- case 120:
- Push(gb);
- break;
-
- case 121:
- Pop(gb);
- break;
-
- case 122:
- {
- int val = (gb.Seen('P')) ? gb.GetIValue() : 0;
- if (val == 0)
- {
- reprap.Diagnostics(gb.GetResponseMessageType());
- }
- else
- {
- platform->DiagnosticTest(val);
- }
- }
- break;
-
- case 135: // Set PID sample interval
- if (gb.Seen('S'))
- {
- platform->SetHeatSampleTime(gb.GetFValue() * 0.001); // Value is in milliseconds; we want seconds
- }
- else
- {
- reply.printf("Heat sample time is %.3f seconds", platform->GetHeatSampleTime());
- }
- break;
-
- case 140: // Set bed temperature
- {
- int8_t bedHeater;
- if (gb.Seen('H'))
- {
- bedHeater = gb.GetIValue();
- if (bedHeater < 0)
- {
- // Make sure we stay within reasonable boundaries...
- bedHeater = -1;
-
- // If we're disabling the hot bed, make sure the old heater is turned off
- reprap.GetHeat()->SwitchOff(reprap.GetHeat()->GetBedHeater());
- }
- else if (bedHeater >= HEATERS)
- {
- reply.copy("Invalid heater number!");
- error = true;
- break;
- }
- reprap.GetHeat()->SetBedHeater(bedHeater);
-
- if (bedHeater < 0)
- {
- // Stop here if the hot bed has been disabled
- break;
- }
- }
- else
- {
- bedHeater = reprap.GetHeat()->GetBedHeater();
- if (bedHeater < 0)
- {
- reply.copy("Hot bed is not present!");
- error = true;
- break;
- }
- }
-
- if(gb.Seen('S'))
- {
- float temperature = gb.GetFValue();
- if (temperature < NEARLY_ABS_ZERO)
- {
- reprap.GetHeat()->SwitchOff(bedHeater);
- }
- else
- {
- reprap.GetHeat()->SetActiveTemperature(bedHeater, temperature);
- reprap.GetHeat()->Activate(bedHeater);
- }
- }
- if(gb.Seen('R'))
- {
- reprap.GetHeat()->SetStandbyTemperature(bedHeater, gb.GetFValue());
- }
- }
- break;
-
- case 141: // Chamber temperature
- {
- bool seen = false;
- if (gb.Seen('H'))
- {
- seen = true;
-
- int heater = gb.GetIValue();
- if (heater < 0)
- {
- const int8_t currentHeater = reprap.GetHeat()->GetChamberHeater();
- if (currentHeater != -1)
- {
- reprap.GetHeat()->SwitchOff(currentHeater);
- }
-
- reprap.GetHeat()->SetChamberHeater(-1);
- }
- else if (heater < HEATERS)
- {
- reprap.GetHeat()->SetChamberHeater(heater);
- }
- else
- {
- reply.copy("Bad heater number specified!");
- error = true;
- }
- }
-
- if (gb.Seen('S'))
- {
- seen = true;
-
- const int8_t currentHeater = reprap.GetHeat()->GetChamberHeater();
- if (currentHeater != -1)
- {
- float temperature = gb.GetFValue();
-
- if (temperature < NEARLY_ABS_ZERO)
- {
- reprap.GetHeat()->SwitchOff(currentHeater);
- }
- else
- {
- reprap.GetHeat()->SetActiveTemperature(currentHeater, temperature);
- reprap.GetHeat()->Activate(currentHeater);
- }
- }
- else
- {
- reply.copy("No chamber heater has been set up yet!");
- error = true;
- }
- }
-
- if (!seen)
- {
- const int8_t currentHeater = reprap.GetHeat()->GetChamberHeater();
- if (currentHeater != -1)
- {
- reply.printf("Chamber heater %d is currently at %.1fC", currentHeater, reprap.GetHeat()->GetTemperature(currentHeater));
- }
- else
- {
- reply.copy("No chamber heater has been configured yet.");
- }
- }
- }
- break;
-
- case 143: // Set temperature limit
- {
- const int heater = (gb.Seen('H')) ? gb.GetIValue() : 1; // default to extruder 1 if no heater number provided
- if (heater < 0 || heater >= HEATERS)
- {
- reply.copy("Invalid heater number");
- error = true;
- }
- else if (gb.Seen('S'))
- {
- const float limit = gb.GetFValue();
- if (limit > BAD_LOW_TEMPERATURE && limit < BAD_ERROR_TEMPERATURE)
- {
- reprap.GetHeat()->SetTemperatureLimit(heater, limit);
- }
- else
- {
- reply.copy("Invalid temperature limit");
- error = true;
- }
- }
- else
- {
- reply.printf("Temperature limit for heater %d is %.1fC", heater, reprap.GetHeat()->GetTemperatureLimit(heater));
- }
- }
- break;
-
- case 144: // Set bed to standby
- {
- const int8_t bedHeater = reprap.GetHeat()->GetBedHeater();
- if (bedHeater >= 0)
- {
- reprap.GetHeat()->Standby(bedHeater);
- }
- }
- break;
-
- case 190: // Set bed temperature and wait
- case 191: // Set chamber temperature and wait
- {
- const int8_t heater = (code == 191) ? reprap.GetHeat()->GetChamberHeater() : reprap.GetHeat()->GetBedHeater();
- if (heater >= 0)
- {
- float temperature;
- bool waitWhenCooling;
- if (gb.Seen('R'))
- {
- waitWhenCooling = true;
- temperature = gb.GetFValue();
- }
- else if (gb.Seen('S'))
- {
- waitWhenCooling = false;
- temperature = gb.GetFValue();
- }
- else
- {
- break; // no target temperature given
- }
-
- reprap.GetHeat()->SetActiveTemperature(heater, temperature);
- reprap.GetHeat()->Activate(heater);
- if (cancelWait || reprap.GetHeat()->HeaterAtSetTemperature(heater, waitWhenCooling))
- {
- cancelWait = isWaiting = false;
- break;
- }
- // In Marlin emulation mode we should return some sort of (undocumented) message here every second...
- isWaiting = true;
- return false;
- }
- }
- break;
-
- case 201: // Set/print axis accelerations
- {
- bool seen = false;
- for (size_t axis = 0; axis < numAxes; axis++)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- platform->SetAcceleration(axis, gb.GetFValue() * distanceScale);
- seen = true;
- }
- }
-
- if (gb.Seen(extrudeLetter))
- {
- seen = true;
- float eVals[MaxExtruders];
- size_t eCount = numExtruders;
- gb.GetFloatArray(eVals, eCount, true);
- for (size_t e = 0; e < eCount; e++)
- {
- platform->SetAcceleration(numAxes + e, eVals[e] * distanceScale);
- }
- }
-
- if (gb.Seen('P'))
- {
- // Set max average printing acceleration
- platform->SetMaxAverageAcceleration(gb.GetFValue() * distanceScale);
- seen = true;
- }
-
- if (!seen)
- {
- reply.printf("Accelerations: ");
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- reply.catf("%c: %.1f, ", axisLetters[axis], platform->Acceleration(axis) / distanceScale);
- }
- reply.cat("E:");
- char sep = ' ';
- for (size_t extruder = 0; extruder < numExtruders; extruder++)
- {
- reply.catf("%c%.1f", sep, platform->Acceleration(extruder + numAxes) / distanceScale);
- sep = ':';
- }
- reply.catf(", avg. printing: %.1f", platform->GetMaxAverageAcceleration());
- }
- }
- break;
-
- case 203: // Set/print maximum feedrates
- {
- bool seen = false;
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- platform->SetMaxFeedrate(axis, gb.GetFValue() * distanceScale * secondsToMinutes); // G Code feedrates are in mm/minute; we need mm/sec
- seen = true;
- }
- }
-
- if (gb.Seen(extrudeLetter))
- {
- seen = true;
- float eVals[MaxExtruders];
- size_t eCount = numExtruders;
- gb.GetFloatArray(eVals, eCount, true);
- for (size_t e = 0; e < eCount; e++)
- {
- platform->SetMaxFeedrate(numAxes + e, eVals[e] * distanceScale * secondsToMinutes);
- }
- }
-
- if (!seen)
- {
- reply.copy("Maximum feedrates: ");
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- reply.catf("%c: %.1f, ", axisLetters[axis], platform->MaxFeedrate(axis) / (distanceScale * secondsToMinutes));
- }
- reply.cat("E:");
- char sep = ' ';
- for (size_t extruder = 0; extruder < numExtruders; extruder++)
- {
- reply.catf("%c%.1f", sep, platform->MaxFeedrate(extruder + numAxes) / (distanceScale * secondsToMinutes));
- sep = ':';
- }
- }
- }
- break;
-
- case 205: //M205 advanced settings: minimum travel speed S=while printing T=travel only, B=minimum segment time X= maximum xy jerk, Z=maximum Z jerk
- // This is superseded in this firmware by M codes for the separate types (e.g. M566).
- break;
-
- case 206: // Offset axes - Deprecated
- result = OffsetAxes(gb);
- break;
-
- case 207: // Set firmware retraction details
- {
- bool seen = false;
- if (gb.Seen('S'))
- {
- retractLength = max<float>(gb.GetFValue(), 0.0);
- seen = true;
- }
- if (gb.Seen('R'))
- {
- retractExtra = max<float>(gb.GetFValue(), -retractLength);
- seen = true;
- }
- if (gb.Seen('F'))
- {
- unRetractSpeed = retractSpeed = max<float>(gb.GetFValue(), 60.0);
- seen = true;
- }
- if (gb.Seen('T')) // must do this one after 'F'
- {
- unRetractSpeed = max<float>(gb.GetFValue(), 60.0);
- seen = true;
- }
- if (gb.Seen('Z'))
- {
- retractHop = max<float>(gb.GetFValue(), 0.0);
- seen = true;
- }
- if (!seen)
- {
- reply.printf("Retraction settings: length %.2f/%.2fmm, speed %d/%dmm/min, Z hop %.2fmm",
- retractLength, retractLength + retractExtra, (int)retractSpeed, (int)unRetractSpeed, retractHop);
- }
- }
- break;
-
- case 208: // Set/print maximum axis lengths. If there is an S parameter with value 1 then we set the min value, else we set the max value.
- {
- bool setMin = (gb.Seen('S') ? (gb.GetIValue() == 1) : false);
- bool seen = false;
- for (size_t axis = 0; axis < numAxes; axis++)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- float value = gb.GetFValue() * distanceScale;
- if (setMin)
- {
- platform->SetAxisMinimum(axis, value);
- }
- else
- {
- platform->SetAxisMaximum(axis, value);
- }
- seen = true;
- }
- }
-
- if (!seen)
- {
- reply.copy("Axis limits ");
- char sep = '-';
- for (size_t axis = 0; axis < numAxes; axis++)
- {
- reply.catf("%c %c: %.1f min, %.1f max", sep, axisLetters[axis], platform->AxisMinimum(axis),
- platform->AxisMaximum(axis));
- sep = ',';
- }
- }
- }
- break;
-
- case 210: // Set/print homing feed rates
- // This is no longer used, but for backwards compatibility we don't report an error
- break;
-
- case 220: // Set/report speed factor override percentage
- if (gb.Seen('S'))
- {
- float newSpeedFactor = (gb.GetFValue() / 100.0) * secondsToMinutes; // include the conversion from mm/minute to mm/second
- if (newSpeedFactor > 0.0)
- {
- feedRate *= newSpeedFactor / speedFactor;
- if (moveAvailable && !moveBuffer.isFirmwareRetraction)
- {
- // The last move has not gone yet, so we can update it
- moveBuffer.feedRate *= newSpeedFactor / speedFactor;
- }
- speedFactor = newSpeedFactor;
- }
- else
- {
- reply.printf("Invalid speed factor specified.");
- error = true;
- }
- }
- else
- {
- reply.printf("Speed factor override: %.1f%%", speedFactor * minutesToSeconds * 100.0);
- }
- break;
-
- case 221: // Set/report extrusion factor override percentage
- {
- int extruder = 0;
- if (gb.Seen('D')) // D parameter (if present) selects the extruder number
- {
- extruder = gb.GetIValue();
- }
-
- if (gb.Seen('S')) // S parameter sets the override percentage
- {
- float extrusionFactor = gb.GetFValue() / 100.0;
- if (extruder >= 0 && (size_t)extruder < numExtruders && extrusionFactor >= 0.0)
- {
- if (moveAvailable && !moveBuffer.isFirmwareRetraction)
- {
- moveBuffer.coords[extruder + numAxes] *= extrusionFactor/extrusionFactors[extruder]; // last move not gone, so update it
- }
- extrusionFactors[extruder] = extrusionFactor;
- }
- }
- else
- {
- reply.printf("Extrusion factor override for extruder %d: %.1f%%", extruder,
- extrusionFactors[extruder] * 100.0);
- }
- }
- break;
-
- // For case 226, see case 25
-
- case 280: // Servos
- if (gb.Seen('P'))
- {
- const int servoIndex = gb.GetIValue();
- Pin servoPin;
- bool invert;
- if (platform->GetFirmwarePin(servoIndex, PinAccess::servo, servoPin, invert))
- {
- if (gb.Seen('I'))
- {
- if (gb.GetIValue() > 0)
- {
- invert = !invert;
- }
- }
- if (gb.Seen('S'))
- {
- float angleOrWidth = gb.GetFValue();
- if (angleOrWidth < 0.0)
- {
- // Disable the servo by setting the pulse width to zero
- Platform::WriteAnalog(servoPin, (invert) ? 1.0 : 0.0, ServoRefreshFrequency);
- }
- else
- {
- if (angleOrWidth < MinServoPulseWidth)
- {
- // User gave an angle so convert it to a pulse width in microseconds
- angleOrWidth = (min<float>(angleOrWidth, 180.0) * ((MaxServoPulseWidth - MinServoPulseWidth) / 180.0)) + MinServoPulseWidth;
- }
- else if (angleOrWidth > MaxServoPulseWidth)
- {
- angleOrWidth = MaxServoPulseWidth;
- }
- float pwm = angleOrWidth * (ServoRefreshFrequency/1e6);
- if (invert)
- {
- pwm = 1.0 - pwm;
- }
- Platform::WriteAnalog(servoPin, pwm, ServoRefreshFrequency);
- }
- }
- // We don't currently allow the servo position to be read back
- }
- else
- {
- platform->MessageF(GENERIC_MESSAGE, "Error: Invalid servo index %d in M280 command\n", servoIndex);
- }
- }
- break;
-
- case 300: // Beep
- {
- int ms = (gb.Seen('P')) ? gb.GetIValue() : 1000; // time in milliseconds
- int freq = (gb.Seen('S')) ? gb.GetIValue() : 4600; // 4600Hz produces the loudest sound on a PanelDue
- reprap.Beep(freq, ms);
- }
- break;
-
- case 301: // Set/report hot end PID values
- SetPidParameters(gb, 1, reply);
- break;
-
- case 302: // Allow, deny or report cold extrudes
- if (gb.Seen('P'))
- {
- if (gb.GetIValue() > 0)
- {
- reprap.GetHeat()->AllowColdExtrude();
- }
- else
- {
- reprap.GetHeat()->DenyColdExtrude();
- }
- }
- else
- {
- reply.printf("Cold extrusion is %s, use M302 P[1/0] to allow/deny it",
- reprap.GetHeat()->ColdExtrude() ? "allowed" : "denied");
- }
- break;
-
- case 303: // Run PID tuning
- if (gb.Seen('H'))
- {
- const size_t heater = gb.GetIValue();
- const float temperature = (gb.Seen('S')) ? gb.GetFValue() : 225.0;
- const float maxPwm = (gb.Seen('P')) ? gb.GetFValue() : 0.5;
- if (heater < HEATERS && maxPwm >= 0.1 && maxPwm <= 1.0 && temperature >= 55.0 && temperature <= reprap.GetHeat()->GetTemperatureLimit(heater))
- {
- reprap.GetHeat()->StartAutoTune(heater, temperature, maxPwm, reply);
- }
- else
- {
- reply.printf("Bad parameter in M303 command");
- }
- }
- else
- {
- reprap.GetHeat()->GetAutoTuneStatus(reply);
- }
- break;
-
- case 304: // Set/report heated bed PID values
- {
- const int8_t bedHeater = reprap.GetHeat()->GetBedHeater();
- if (bedHeater >= 0)
- {
- SetPidParameters(gb, bedHeater, reply);
- }
- }
- break;
-
- case 305: // Set/report specific heater parameters
- SetHeaterParameters(gb, reply);
- break;
-
- case 307: // Set heater process model parameters
- if (gb.Seen('H'))
- {
- size_t heater = gb.GetIValue();
- if (heater < HEATERS)
- {
- const FopDt& model = reprap.GetHeat()->GetHeaterModel(heater);
- bool seen = false;
- float gain = model.GetGain(), tc = model.GetTimeConstant(), td = model.GetDeadTime(), maxPwm = model.GetMaxPwm();
- int32_t dontUsePid = model.UsePid() ? 0 : 1;
-
- gb.TryGetFValue('A', gain, seen);
- gb.TryGetFValue('C', tc, seen);
- gb.TryGetFValue('D', td, seen);
- gb.TryGetIValue('B', dontUsePid, seen);
- gb.TryGetFValue('S', maxPwm, seen);
-
- if (seen)
- {
- if (reprap.GetHeat()->SetHeaterModel(heater, gain, tc, td, maxPwm, dontUsePid == 0))
- {
- reprap.GetHeat()->UseModel(heater, true);
- }
- else
- {
- reply.copy("Error: bad model parameters");
- }
- }
- else if (!model.IsEnabled())
- {
- reply.printf("Heater %u is disabled", heater);
- }
- else
- {
- reply.printf("Heater %u model: gain %.1f, time constant %.1f, dead time %.1f, max PWM %.2f, in use: %s, mode: %s",
- heater, model.GetGain(), model.GetTimeConstant(), model.GetDeadTime(), model.GetMaxPwm(),
- (reprap.GetHeat()->IsModelUsed(heater)) ? "yes" : "no",
- (model.UsePid()) ? "PID" : "bang-bang");
- if (model.UsePid())
- {
- // When reporting the PID parameters, we scale them by 255 for compatibility with older firmware and other firmware
- const PidParams& spParams = model.GetPidParameters(false);
- const float scaledSpKp = 255.0 * spParams.kP;
- reply.catf("\nSetpoint change: P%.1f, I%.2f, D%.1f",
- scaledSpKp, scaledSpKp * spParams.recipTi, scaledSpKp * spParams.tD);
- const PidParams& ldParams = model.GetPidParameters(true);
- const float scaledLoadKp = 255.0 * ldParams.kP;
- reply.catf("\nLoad change: P%.1f, I%.2f, D%.1f",
- scaledLoadKp, scaledLoadKp * ldParams.recipTi, scaledLoadKp * ldParams.tD);
- }
- }
- }
- }
- break;
-
- case 350: // Set/report microstepping
- {
- // interp is current an int not a bool, because we use special values of interp to set the chopper control register
- int interp = 0;
- if (gb.Seen('I'))
- {
- interp = gb.GetIValue();
- }
-
- bool seen = false;
- for (size_t axis = 0; axis < numAxes; axis++)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- seen = true;
- int microsteps = gb.GetIValue();
- if (ChangeMicrostepping(axis, microsteps, interp))
- {
- SetAxisNotHomed(axis);
- }
- else
- {
- platform->MessageF(GENERIC_MESSAGE, "Drive %c does not support %dx microstepping%s\n",
- axisLetters[axis], microsteps, (interp) ? " with interpolation" : "");
- }
- }
- }
-
- if (gb.Seen(extrudeLetter))
- {
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- seen = true;
- long eVals[MaxExtruders];
- size_t eCount = numExtruders;
- gb.GetLongArray(eVals, eCount);
- for (size_t e = 0; e < eCount; e++)
- {
- if (!ChangeMicrostepping(numAxes + e, (int)eVals[e], interp))
- {
- platform->MessageF(GENERIC_MESSAGE, "Drive E%u does not support %dx microstepping%s\n",
- e, (int)eVals[e], (interp) ? " with interpolation" : "");
- }
- }
- }
-
- if (!seen)
- {
- reply.copy("Microstepping - ");
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- bool interp;
- int microsteps = platform->GetMicrostepping(axis, interp);
- reply.catf("%c:%d%s, ", axisLetters[axis], microsteps, (interp) ? "(on)" : "");
- }
- reply.cat("E");
- for (size_t extruder = 0; extruder < numExtruders; extruder++)
- {
- bool interp;
- int microsteps = platform->GetMicrostepping(extruder + numAxes, interp);
- reply.catf(":%d%s", microsteps, (interp) ? "(on)" : "");
- }
- }
- }
- break;
-
- case 400: // Wait for current moves to finish
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- break;
-
- case 404: // Filament width and nozzle diameter
- {
- bool seen = false;
-
- if (gb.Seen('N'))
- {
- platform->SetFilamentWidth(gb.GetFValue());
- seen = true;
- }
- if (gb.Seen('D'))
- {
- platform->SetNozzleDiameter(gb.GetFValue());
- seen = true;
- }
-
- if (!seen)
- {
- reply.printf("Filament width: %.2fmm, nozzle diameter: %.2fmm", platform->GetFilamentWidth(), platform->GetNozzleDiameter());
- }
- }
- break;
-
- case 408: // Get status in JSON format
- {
- int type = gb.Seen('S') ? gb.GetIValue() : 0;
- int seq = gb.Seen('R') ? gb.GetIValue() : -1;
-
- OutputBuffer *statusResponse = nullptr;
- switch (type)
- {
- case 0:
- case 1:
- statusResponse = reprap.GetLegacyStatusResponse(type + 2, seq);
- break;
-
- case 2:
- case 3:
- case 4:
- statusResponse = reprap.GetStatusResponse(type - 1, (&gb == auxGCode) ? ResponseSource::AUX : ResponseSource::Generic);
- break;
-
- case 5:
- statusResponse = reprap.GetConfigResponse();
- break;
- }
-
- if (statusResponse != nullptr)
- {
- UnlockAll(gb);
- statusResponse->cat('\n');
- HandleReply(gb, false, statusResponse);
- return true;
- }
- }
- break;
-
- case 500: // Store parameters in EEPROM
- reprap.GetPlatform()->WriteNvData();
- break;
-
- case 501: // Load parameters from EEPROM
- reprap.GetPlatform()->ReadNvData();
- if (gb.Seen('S'))
- {
- reprap.GetPlatform()->SetAutoSave(gb.GetIValue() > 0);
- }
- break;
-
- case 502: // Revert to default "factory settings"
- reprap.GetPlatform()->ResetNvData();
- break;
-
- case 503: // List variable settings
- {
- if (!LockFileSystem(gb))
- {
- return false;
- }
-
- // Need a valid output buffer to continue...
- OutputBuffer *configResponse;
- if (!OutputBuffer::Allocate(configResponse))
- {
- // No buffer available, try again later
- return false;
- }
-
- // Read the entire file
- FileStore * const f = platform->GetFileStore(platform->GetSysDir(), platform->GetConfigFile(), false);
- if (f == nullptr)
- {
- error = true;
- reply.copy("Configuration file not found!");
- }
- else
- {
- char fileBuffer[FILE_BUFFER_SIZE];
- size_t bytesRead, bytesLeftForWriting = OutputBuffer::GetBytesLeft(configResponse);
- while ((bytesRead = f->Read(fileBuffer, FILE_BUFFER_SIZE)) > 0 && bytesLeftForWriting > 0)
- {
- // Don't write more data than we can process
- if (bytesRead < bytesLeftForWriting)
- {
- bytesLeftForWriting -= bytesRead;
- }
- else
- {
- bytesRead = bytesLeftForWriting;
- bytesLeftForWriting = 0;
- }
-
- // Write it
- configResponse->cat(fileBuffer, bytesRead);
- }
- f->Close();
-
- UnlockAll(gb);
- HandleReply(gb, false, configResponse);
- return true;
- }
- }
- break;
-
- case 540: // Set/report MAC address
- if (gb.Seen('P'))
- {
- SetMACAddress(gb);
- }
- else
- {
- const byte* mac = platform->MACAddress();
- reply.printf("MAC: %x:%x:%x:%x:%x:%x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
- }
- break;
-
- case 550: // Set/report machine name
- if (gb.Seen('P'))
- {
- reprap.SetName(gb.GetString());
- }
- else
- {
- reply.printf("RepRap name: %s", reprap.GetName());
- }
- break;
-
- case 551: // Set password (no option to report it)
- if (gb.Seen('P'))
- {
- reprap.SetPassword(gb.GetString());
- }
- break;
-
- case 552: // Enable/Disable network and/or Set/Get IP address
- {
- bool seen = false;
- if (gb.Seen('P'))
- {
- seen = true;
- SetEthernetAddress(gb, code);
- }
-
- if (gb.Seen('R'))
- {
- reprap.GetNetwork()->SetHttpPort(gb.GetIValue());
- seen = true;
- }
-
- // Process this one last in case the IP address is changed and the network enabled in the same command
- if (gb.Seen('S')) // Has the user turned the network on or off?
- {
- seen = true;
- if (gb.GetIValue() != 0)
- {
- reprap.GetNetwork()->Enable();
- }
- else
- {
- reprap.GetNetwork()->Disable();
- }
- }
-
- if (!seen)
- {
- const byte *config_ip = platform->IPAddress();
- const byte *actual_ip = reprap.GetNetwork()->IPAddress();
- reply.printf("Network is %s, configured IP address: %d.%d.%d.%d, actual IP address: %d.%d.%d.%d, HTTP port: %d",
- reprap.GetNetwork()->IsEnabled() ? "enabled" : "disabled",
- config_ip[0], config_ip[1], config_ip[2], config_ip[3], actual_ip[0], actual_ip[1], actual_ip[2], actual_ip[3],
- reprap.GetNetwork()->GetHttpPort());
- }
- }
- break;
-
- case 553: // Set/Get netmask
- if (gb.Seen('P'))
- {
- SetEthernetAddress(gb, code);
- }
- else
- {
- const byte *nm = platform->NetMask();
- reply.printf("Net mask: %d.%d.%d.%d ", nm[0], nm[1], nm[2], nm[3]);
- }
- break;
-
- case 554: // Set/Get gateway
- if (gb.Seen('P'))
- {
- SetEthernetAddress(gb, code);
- }
- else
- {
- const byte *gw = platform->GateWay();
- reply.printf("Gateway: %d.%d.%d.%d ", gw[0], gw[1], gw[2], gw[3]);
- }
- break;
-
- case 555: // Set/report firmware type to emulate
- if (gb.Seen('P'))
- {
- platform->SetEmulating((Compatibility) gb.GetIValue());
- }
- else
- {
- reply.copy("Emulating ");
- switch (platform->Emulating())
- {
- case me:
- case reprapFirmware:
- reply.cat("RepRap Firmware (i.e. in native mode)");
- break;
-
- case marlin:
- reply.cat("Marlin");
- break;
-
- case teacup:
- reply.cat("Teacup");
- break;
-
- case sprinter:
- reply.cat("Sprinter");
- break;
-
- case repetier:
- reply.cat("Repetier");
- break;
-
- default:
- reply.catf("Unknown: (%d)", platform->Emulating());
- }
- }
- break;
-
- case 556: // Axis compensation (we support only X, Y, Z)
- if (gb.Seen('S'))
- {
- float value = gb.GetFValue();
- for (size_t axis = 0; axis <= Z_AXIS; axis++)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- reprap.GetMove()->SetAxisCompensation(axis, gb.GetFValue() / value);
- }
- }
- }
- else
- {
- reply.printf("Axis compensations - XY: %.5f, YZ: %.5f, ZX: %.5f",
- reprap.GetMove()->AxisCompensation(X_AXIS), reprap.GetMove()->AxisCompensation(Y_AXIS),
- reprap.GetMove()->AxisCompensation(Z_AXIS));
- }
- break;
-
- case 557: // Set/report Z probe point coordinates
- if (gb.Seen('P'))
- {
- int point = gb.GetIValue();
- if (point < 0 || (unsigned int)point >= MaxProbePoints)
- {
- reprap.GetPlatform()->Message(GENERIC_MESSAGE, "Z probe point index out of range.\n");
- }
- else
- {
- bool seen = false;
- if (gb.Seen(axisLetters[X_AXIS]))
- {
- reprap.GetMove()->SetXBedProbePoint(point, gb.GetFValue());
- seen = true;
- }
- if (gb.Seen(axisLetters[Y_AXIS]))
- {
- reprap.GetMove()->SetYBedProbePoint(point, gb.GetFValue());
- seen = true;
- }
-
- if (!seen)
- {
- reply.printf("Probe point %d - [%.1f, %.1f]", point, reprap.GetMove()->XBedProbePoint(point), reprap.GetMove()->YBedProbePoint(point));
- }
- }
- }
- else
- {
- LockMovement(gb); // to ensure that probing is not already in progress
- error = DefineGrid(gb, reply);
- }
- break;
-
- case 558: // Set or report Z probe type and for which axes it is used
- {
- bool seenAxes = false, seenType = false, seenParam = false;
- uint32_t zProbeAxes = platform->GetZProbeAxes();
- for (size_t axis = 0; axis < numAxes; axis++)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- if (gb.GetIValue() > 0)
- {
- zProbeAxes |= (1u << axis);
- }
- else
- {
- zProbeAxes &= ~(1u << axis);
- }
- seenAxes = true;
- }
- }
- if (seenAxes)
- {
- platform->SetZProbeAxes(zProbeAxes);
- }
-
- // We must get and set the Z probe type first before setting the dive height etc., because different probe types may have different parameters
- if (gb.Seen('P')) // probe type
- {
- platform->SetZProbeType(gb.GetIValue());
- seenType = true;
- }
-
- ZProbeParameters params = platform->GetZProbeParameters();
- gb.TryGetFValue('H', params.diveHeight, seenParam); // dive height
-
- if (gb.Seen('F')) // feed rate i.e. probing speed
- {
- params.probeSpeed = gb.GetFValue() * secondsToMinutes;
- seenParam = true;
- }
-
- if (gb.Seen('T')) // travel speed to probe point
- {
- params.travelSpeed = gb.GetFValue() * secondsToMinutes;
- seenParam = true;
- }
-
- if (gb.Seen('I'))
- {
- params.invertReading = (gb.GetIValue() != 0);
- seenParam = true;
- }
-
- gb.TryGetFValue('S', params.param1, seenParam); // extra parameter for experimentation
- gb.TryGetFValue('R', params.param2, seenParam); // extra parameter for experimentation
-
- if (seenParam)
- {
- platform->SetZProbeParameters(params);
- }
-
- if (!(seenAxes || seenType || seenParam))
- {
- reply.printf("Z Probe type %d, invert %s, dive height %.1fmm, probe speed %dmm/min, travel speed %dmm/min",
- platform->GetZProbeType(), (params.invertReading) ? "yes" : "no", params.diveHeight,
- (int)(params.probeSpeed * minutesToSeconds), (int)(params.travelSpeed * minutesToSeconds));
- if (platform->GetZProbeType() == ZProbeTypeDelta)
- {
- reply.catf(", parameters %.2f %.2f", params.param1, params.param2);
- }
- reply.cat(", used for axes:");
- for (size_t axis = 0; axis < numAxes; axis++)
- {
- if ((zProbeAxes & (1u << axis)) != 0)
- {
- reply.catf(" %c", axisLetters[axis]);
- }
- }
- }
- }
- break;
-
- case 559: // Upload config.g or another gcode file to put in the sys directory
- {
- const char* str = (gb.Seen('P') ? gb.GetString() : platform->GetConfigFile());
- bool ok = OpenFileToWrite(gb, platform->GetSysDir(), str);
- if (ok)
- {
- reply.printf("Writing to file: %s", str);
- }
- else
- {
- reply.printf("Can't open file %s for writing.", str);
- error = true;
- }
- }
- break;
-
- case 560: // Upload reprap.htm or another web interface file
- {
- const char* str = (gb.Seen('P') ? gb.GetString() : INDEX_PAGE_FILE);
- bool ok = OpenFileToWrite(gb, platform->GetWebDir(), str);
- if (ok)
- {
- reply.printf("Writing to file: %s", str);
- }
- else
- {
- reply.printf("Can't open file %s for writing.", str);
- error = true;
- }
- }
- break;
-
- case 561:
- reprap.GetMove()->SetIdentityTransform();
- break;
-
- case 562: // Reset temperature fault - use with great caution
- if (gb.Seen('P'))
- {
- int heater = gb.GetIValue();
- if (heater >= 0 && heater < HEATERS)
- {
- reprap.ClearTemperatureFault(heater);
- }
- else
- {
- reply.copy("Invalid heater number.\n");
- error = true;
- }
- }
- break;
-
- case 563: // Define tool
- ManageTool(gb, reply);
- break;
-
- case 564: // Think outside the box?
- if (gb.Seen('S'))
- {
- limitAxes = (gb.GetIValue() != 0);
- }
- else
- {
- reply.printf("Movement outside the bed is %spermitted", (limitAxes) ? "not " : "");
- }
- break;
-
- case 566: // Set/print maximum jerk speeds
- {
- bool seen = false;
- for (size_t axis = 0; axis < numAxes; axis++)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- platform->SetInstantDv(axis, gb.GetFValue() * distanceScale * secondsToMinutes); // G Code feedrates are in mm/minute; we need mm/sec
- seen = true;
- }
- }
-
- if (gb.Seen(extrudeLetter))
- {
- seen = true;
- float eVals[MaxExtruders];
- size_t eCount = numExtruders;
- gb.GetFloatArray(eVals, eCount, true);
- for (size_t e = 0; e < eCount; e++)
- {
- platform->SetInstantDv(numAxes + e, eVals[e] * distanceScale * secondsToMinutes);
- }
- }
- else if (!seen)
- {
- reply.copy("Maximum jerk rates: ");
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- reply.catf("%c: %.1f, ", axisLetters[axis], platform->ConfiguredInstantDv(axis) / (distanceScale * secondsToMinutes));
- }
- reply.cat("E:");
- char sep = ' ';
- for (size_t extruder = 0; extruder < numExtruders; extruder++)
- {
- reply.catf("%c%.1f", sep, platform->ConfiguredInstantDv(extruder + numAxes) / (distanceScale * secondsToMinutes));
- sep = ':';
- }
- }
- }
- break;
-
- case 567: // Set/report tool mix ratios
- if (gb.Seen('P'))
- {
- int8_t tNumber = gb.GetIValue();
- Tool* tool = reprap.GetTool(tNumber);
- if (tool != NULL)
- {
- if (gb.Seen(extrudeLetter))
- {
- float eVals[MaxExtruders];
- size_t eCount = tool->DriveCount();
- gb.GetFloatArray(eVals, eCount, false);
- if (eCount != tool->DriveCount())
- {
- reply.printf("Setting mix ratios - wrong number of E drives: %s", gb.Buffer());
- }
- else
- {
- tool->DefineMix(eVals);
- }
- }
- else
- {
- reply.printf("Tool %d mix ratios:", tNumber);
- char sep = ' ';
- for (size_t drive = 0; drive < tool->DriveCount(); drive++)
- {
- reply.catf("%c%.3f", sep, tool->GetMix()[drive]);
- sep = ':';
- }
- }
- }
- }
- break;
-
- case 568: // Turn on/off automatic tool mixing
- if (gb.Seen('P'))
- {
- Tool* tool = reprap.GetTool(gb.GetIValue());
- if (tool != NULL)
- {
- if (gb.Seen('S'))
- {
- tool->SetMixing(gb.GetIValue() != 0);
- }
- else
- {
- reply.printf("Tool %d mixing is %s", tool->Number(), (tool->GetMixing()) ? "enabled" : "disabled");
- }
- }
- }
- break;
-
- case 569: // Set/report axis direction
- if (gb.Seen('P'))
- {
- size_t drive = gb.GetIValue();
- if (drive < DRIVES)
- {
- bool seen = false;
- if (gb.Seen('S'))
- {
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- platform->SetDirectionValue(drive, gb.GetIValue() != 0);
- seen = true;
- }
- if (gb.Seen('R'))
- {
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- platform->SetEnableValue(drive, gb.GetIValue() != 0);
- seen = true;
- }
- if (gb.Seen('T'))
- {
- platform->SetDriverStepTiming(drive, gb.GetFValue());
- seen = true;
- }
- bool badParameter = false;
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- badParameter = true;
- }
- }
- if (gb.Seen(extrudeLetter))
- {
- badParameter = true;
- }
- if (badParameter)
- {
- platform->Message(GENERIC_MESSAGE, "Error: M569 no longer accepts XYZE parameters; use M584 instead\n");
- }
- else if (!seen)
- {
- reply.printf("A %d sends drive %u forwards, a %d enables it, and the minimum pulse width is %.1f microseconds",
- (int)platform->GetDirectionValue(drive), drive,
- (int)platform->GetEnableValue(drive),
- platform->GetDriverStepTiming(drive));
- }
- }
- }
- break;
-
- case 570: // Set/report heater timeout
- if (gb.Seen('H'))
- {
- const size_t heater = gb.GetIValue();
- bool seen = false;
- if (heater < HEATERS)
- {
- float maxTempExcursion, maxFaultTime;
- reprap.GetHeat()->GetHeaterProtection(heater, maxTempExcursion, maxFaultTime);
- gb.TryGetFValue('P', maxFaultTime, seen);
- gb.TryGetFValue('T', maxTempExcursion, seen);
- if (seen)
- {
- reprap.GetHeat()->SetHeaterProtection(heater, maxTempExcursion, maxFaultTime);
- }
- else
- {
- reply.printf("Heater %u allowed excursion %.1fC, fault trigger time %.1f seconds", heater, maxTempExcursion, maxFaultTime);
- }
- }
- }
- else if (gb.Seen('S'))
- {
- reply.copy("M570 S parameter is no longer required or supported");
- }
- break;
-
- case 571: // Set output on extrude
- if (gb.Seen('S'))
- {
- platform->SetExtrusionAncilliaryPWM(gb.GetFValue());
- }
- else
- {
- reply.printf("Extrusion ancillary PWM: %.3f.", platform->GetExtrusionAncilliaryPWM());
- }
- break;
-
- case 572: // Set/report elastic compensation
- if (gb.Seen('D'))
- {
- // New usage: specify the extruder drive using the D parameter
- size_t extruder = gb.GetIValue();
- if (gb.Seen('S'))
- {
- platform->SetPressureAdvance(extruder, gb.GetFValue());
- }
- else
- {
- reply.printf("Pressure advance for extruder %u is %.3f seconds", extruder, platform->GetPressureAdvance(extruder));
- }
- }
- break;
-
- case 573: // Report heater average PWM
- if (gb.Seen('P'))
- {
- int heater = gb.GetIValue();
- if (heater >= 0 && heater < HEATERS)
- {
- reply.printf("Average heater %d PWM: %.3f", heater, reprap.GetHeat()->GetAveragePWM(heater));
- }
- }
- break;
-
- case 574: // Set endstop configuration
- {
- bool seen = false;
- bool logicLevel = (gb.Seen('S')) ? (gb.GetIValue() != 0) : true;
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- int ival = gb.GetIValue();
- if (ival >= 0 && ival <= 3)
- {
- platform->SetEndStopConfiguration(axis, (EndStopType) ival, logicLevel);
- seen = true;
- }
- }
- }
- if (!seen)
- {
- reply.copy("Endstop configuration:");
- EndStopType config;
- bool logic;
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- platform->GetEndStopConfiguration(axis, config, logic);
- reply.catf(" %c %s (active %s),", axisLetters[axis],
- (config == EndStopType::highEndStop) ? "high end" : (config == EndStopType::lowEndStop) ? "low end" : "none",
- (config == EndStopType::noEndStop) ? "" : (logic) ? "high" : "low");
- }
- }
- }
- break;
-
- case 575: // Set communications parameters
- if (gb.Seen('P'))
- {
- size_t chan = gb.GetIValue();
- if (chan < NUM_SERIAL_CHANNELS)
- {
- bool seen = false;
- if (gb.Seen('B'))
- {
- platform->SetBaudRate(chan, gb.GetIValue());
- seen = true;
- }
- if (gb.Seen('S'))
- {
- uint32_t val = gb.GetIValue();
- platform->SetCommsProperties(chan, val);
- switch (chan)
- {
- case 0:
- serialGCode->SetCommsProperties(val);
- break;
- case 1:
- auxGCode->SetCommsProperties(val);
- break;
- default:
- break;
- }
- seen = true;
- }
- if (!seen)
- {
- uint32_t cp = platform->GetCommsProperties(chan);
- reply.printf("Channel %d: baud rate %d, %s checksum", chan, platform->GetBaudRate(chan),
- (cp & 1) ? "requires" : "does not require");
- }
- }
- }
- break;
-
- case 577: // Wait until endstop is triggered
- if (gb.Seen('S'))
- {
- // Determine trigger type
- EndStopHit triggerCondition;
- switch (gb.GetIValue())
- {
- case 1:
- triggerCondition = EndStopHit::lowHit;
- break;
- case 2:
- triggerCondition = EndStopHit::highHit;
- break;
- case 3:
- triggerCondition = EndStopHit::lowNear;
- break;
- default:
- triggerCondition = EndStopHit::noStop;
- break;
- }
-
- // Axis endstops
- for (size_t axis=0; axis < numAxes; axis++)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- if (platform->Stopped(axis) != triggerCondition)
- {
- result = false;
- break;
- }
- }
- }
-
- // Extruder drives
- size_t eDriveCount = MaxExtruders;
- long eDrives[MaxExtruders];
- if (gb.Seen(extrudeLetter))
- {
- gb.GetLongArray(eDrives, eDriveCount);
- for(size_t extruder = 0; extruder < eDriveCount; extruder++)
- {
- const size_t eDrive = eDrives[extruder];
- if (eDrive >= MaxExtruders)
- {
- reply.copy("Invalid extruder drive specified!");
- error = result = true;
- break;
- }
-
- if (platform->Stopped(eDrive + E0_AXIS) != triggerCondition)
- {
- result = false;
- break;
- }
- }
- }
- }
- break;
-
-#if SUPPORT_INKJET
- case 578: // Fire Inkjet bits
- if (!AllMovesAreFinishedAndMoveBufferIsLoaded())
- {
- return false;
- }
-
- if (gb.Seen('S')) // Need to handle the 'P' parameter too; see http://reprap.org/wiki/G-code#M578:_Fire_inkjet_bits
- {
- platform->Inkjet(gb.GetIValue());
- }
- break;
-#endif
-
- case 579: // Scale Cartesian axes (mostly for Delta configurations)
- {
- bool seen = false;
- for (size_t axis = 0; axis < numAxes; axis++)
- {
- gb.TryGetFValue(axisLetters[axis], axisScaleFactors[axis], seen);
- }
-
- if (!seen)
- {
- char sep = ':';
- reply.copy("Axis scale factors");
- for(size_t axis = 0; axis < numAxes; axis++)
- {
- reply.catf("%c %c: %.3f", sep, axisLetters[axis], axisScaleFactors[axis]);
- sep = ',';
- }
- }
- }
- break;
-
-#if SUPPORT_ROLAND
- case 580: // (De)Select Roland mill
- if (gb.Seen('R'))
- {
- if (gb.GetIValue())
- {
- reprap.GetRoland()->Activate();
- if (gb.Seen('P'))
- {
- result = reprap.GetRoland()->RawWrite(gb.GetString());
- }
- }
- else
- {
- result = reprap.GetRoland()->Deactivate();
- }
- }
- else
- {
- reply.printf("Roland is %s.", reprap.GetRoland()->Active() ? "active" : "inactive");
- }
- break;
-#endif
-
- case 581: // Configure external trigger
- case 582: // Check external trigger
- if (gb.Seen('T'))
- {
- unsigned int triggerNumber = gb.GetIValue();
- if (triggerNumber < MaxTriggers)
- {
- if (code == 582)
- {
- uint32_t states = platform->GetAllEndstopStates();
- if ((triggers[triggerNumber].rising & states) != 0 || (triggers[triggerNumber].falling & ~states) != 0)
- {
- triggersPending |= (1u << triggerNumber);
- }
- }
- else
- {
- bool seen = false;
- if (gb.Seen('C'))
- {
- seen = true;
- triggers[triggerNumber].condition = gb.GetIValue();
- }
- else if (triggers[triggerNumber].IsUnused())
- {
- triggers[triggerNumber].condition = 0; // this is a new trigger, so set no condition
- }
- if (gb.Seen('S'))
- {
- seen = true;
- int sval = gb.GetIValue();
- TriggerMask triggerMask = 0;
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- triggerMask |= (1u << axis);
- }
- }
- if (gb.Seen(extrudeLetter))
- {
- long eStops[MaxExtruders];
- size_t numEntries = MaxExtruders;
- gb.GetLongArray(eStops, numEntries);
- for (size_t i = 0; i < numEntries; ++i)
- {
- if (eStops[i] >= 0 && (unsigned long)eStops[i] < MaxExtruders)
- {
- triggerMask |= (1u << (eStops[i] + E0_AXIS));
- }
- }
- }
- switch(sval)
- {
- case -1:
- if (triggerMask == 0)
- {
- triggers[triggerNumber].rising = triggers[triggerNumber].falling = 0;
- }
- else
- {
- triggers[triggerNumber].rising &= (~triggerMask);
- triggers[triggerNumber].falling &= (~triggerMask);
- }
- break;
-
- case 0:
- triggers[triggerNumber].falling |= triggerMask;
- break;
-
- case 1:
- triggers[triggerNumber].rising |= triggerMask;
- break;
-
- default:
- platform->Message(GENERIC_MESSAGE, "Bad S parameter in M581 command\n");
- }
- }
- if (!seen)
- {
- reply.printf("Trigger %u fires on a rising edge on ", triggerNumber);
- ListTriggers(reply, triggers[triggerNumber].rising);
- reply.cat(" or a falling edge on ");
- ListTriggers(reply, triggers[triggerNumber].falling);
- reply.cat(" endstop inputs");
- if (triggers[triggerNumber].condition == 1)
- {
- reply.cat(" when printing from SD card");
- }
- }
- }
- }
- else
- {
- platform->Message(GENERIC_MESSAGE, "Trigger number out of range\n");
- }
- }
- break;
-
- case 584: // Set axis/extruder to stepper driver(s) mapping
- if (!LockMovementAndWaitForStandstill(gb)) // we also rely on this to retrieve the current motor positions to moveBuffer
- {
- return false;
- }
- {
- bool seen = false, badDrive = false;
- for (size_t drive = 0; drive < MAX_AXES; ++drive)
- {
- if (gb.Seen(axisLetters[drive]))
- {
- seen = true;
- size_t numValues = MaxDriversPerAxis;
- long drivers[MaxDriversPerAxis];
- gb.GetLongArray(drivers, numValues);
-
- // Check all the driver numbers are in range
- bool badAxis = false;
- AxisDriversConfig config;
- config.numDrivers = numValues;
- for (size_t i = 0; i < numValues; ++i)
- {
- if ((unsigned long)drivers[i] >= DRIVES)
- {
- badAxis = true;
- }
- else
- {
- config.driverNumbers[i] = (uint8_t)drivers[i];
- }
- }
- if (badAxis)
- {
- badDrive = true;
- }
- else
- {
- while (numAxes <= drive)
- {
- moveBuffer.coords[numAxes] = 0.0; // user has defined a new axis, so set its position
- ++numAxes;
- }
- SetPositions(moveBuffer.coords); // tell the Move system where any new axes are
- platform->SetAxisDriversConfig(drive, config);
- if (numAxes + numExtruders > DRIVES)
- {
- numExtruders = DRIVES - numAxes; // if we added axes, we may have fewer extruders now
- }
- }
- }
- }
-
- if (gb.Seen(extrudeLetter))
- {
- seen = true;
- size_t numValues = DRIVES - numAxes;
- long drivers[MaxExtruders];
- gb.GetLongArray(drivers, numValues);
- numExtruders = numValues;
- for (size_t i = 0; i < numValues; ++i)
- {
- if ((unsigned long)drivers[i] >= DRIVES)
- {
- badDrive = true;
- }
- else
- {
- platform->SetExtruderDriver(i, (uint8_t)drivers[i]);
- }
- }
- }
-
- if (badDrive)
- {
- platform->Message(GENERIC_MESSAGE, "Error: invalid drive number in M584 command\n");
- }
- else if (!seen)
- {
- reply.copy("Driver assignments:");
- for (size_t drive = 0; drive < numAxes; ++ drive)
- {
- reply.cat(' ');
- const AxisDriversConfig& axisConfig = platform->GetAxisDriversConfig(drive);
- char c = axisLetters[drive];
- for (size_t i = 0; i < axisConfig.numDrivers; ++i)
- {
- reply.catf("%c%u", c, axisConfig.driverNumbers[i]);
- c = ':';
- }
- }
- reply.cat(' ');
- char c = extrudeLetter;
- for (size_t extruder = 0; extruder < numExtruders; ++extruder)
- {
- reply.catf("%c%u", c, platform->GetExtruderDriver(extruder));
- c = ':';
- }
- }
- }
- break;
-
- case 665: // Set delta configuration
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- {
- float positionNow[DRIVES];
- Move *move = reprap.GetMove();
- move->GetCurrentUserPosition(positionNow, 0, reprap.GetCurrentXAxes()); // get the current position, we may need it later
- DeltaParameters& params = move->AccessDeltaParams();
- bool wasInDeltaMode = params.IsDeltaMode(); // remember whether we were in delta mode
- bool seen = false;
-
- if (gb.Seen('L'))
- {
- params.SetDiagonal(gb.GetFValue() * distanceScale);
- seen = true;
- }
- if (gb.Seen('R'))
- {
- params.SetRadius(gb.GetFValue() * distanceScale);
- seen = true;
- }
- if (gb.Seen('B'))
- {
- params.SetPrintRadius(gb.GetFValue() * distanceScale);
- seen = true;
- }
- if (gb.Seen('X'))
- {
- // X tower position correction
- params.SetXCorrection(gb.GetFValue());
- seen = true;
- }
- if (gb.Seen('Y'))
- {
- // Y tower position correction
- params.SetYCorrection(gb.GetFValue());
- seen = true;
- }
- if (gb.Seen('Z'))
- {
- // Y tower position correction
- params.SetZCorrection(gb.GetFValue());
- seen = true;
- }
-
- // The homed height must be done last, because it gets recalculated when some of the other factors are changed
- if (gb.Seen('H'))
- {
- params.SetHomedHeight(gb.GetFValue() * distanceScale);
- seen = true;
- }
-
- if (seen)
- {
- move->SetCoreXYMode(0); // CoreXYMode needs to be zero when executing special moves on a delta
-
- // If we have changed between Cartesian and Delta mode, we need to reset the motor coordinates to agree with the XYZ coordinates.
- // This normally happens only when we process the M665 command in config.g. Also flag that the machine is not homed.
- if (params.IsDeltaMode() != wasInDeltaMode)
- {
- SetPositions(positionNow);
- }
- SetAllAxesNotHomed();
- }
- else
- {
- if (params.IsDeltaMode())
- {
- reply.printf("Diagonal %.3f, delta radius %.3f, homed height %.3f, bed radius %.1f"
- ", X %.3f" DEGREE_SYMBOL ", Y %.3f" DEGREE_SYMBOL ", Z %.3f" DEGREE_SYMBOL,
- params.GetDiagonal() / distanceScale, params.GetRadius() / distanceScale,
- params.GetHomedHeight() / distanceScale, params.GetPrintRadius() / distanceScale,
- params.GetXCorrection(), params.GetYCorrection(), params.GetZCorrection());
- }
- else
- {
- reply.printf("Printer is not in delta mode");
- }
- }
- }
- break;
-
- case 666: // Set delta endstop adjustments
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- {
- DeltaParameters& params = reprap.GetMove()->AccessDeltaParams();
- bool seen = false;
- if (gb.Seen('X'))
- {
- params.SetEndstopAdjustment(X_AXIS, gb.GetFValue());
- seen = true;
- }
- if (gb.Seen('Y'))
- {
- params.SetEndstopAdjustment(Y_AXIS, gb.GetFValue());
- seen = true;
- }
- if (gb.Seen('Z'))
- {
- params.SetEndstopAdjustment(Z_AXIS, gb.GetFValue());
- seen = true;
- }
- if (gb.Seen('A'))
- {
- params.SetXTilt(gb.GetFValue() * 0.01);
- seen = true;
- }
- if (gb.Seen('B'))
- {
- params.SetYTilt(gb.GetFValue() * 0.01);
- seen = true;
- }
-
- if (seen)
- {
- SetAllAxesNotHomed();
- }
- else
- {
- reply.printf("Endstop adjustments X%.2f Y%.2f Z%.2f, tilt X%.2f%% Y%.2f%%",
- params.GetEndstopAdjustment(X_AXIS), params.GetEndstopAdjustment(Y_AXIS), params.GetEndstopAdjustment(Z_AXIS),
- params.GetXTilt() * 100.0, params.GetYTilt() * 100.0);
- }
- }
- break;
-
- case 667: // Set CoreXY mode
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- {
- Move* move = reprap.GetMove();
- bool seen = false;
- float positionNow[DRIVES];
- move->GetCurrentUserPosition(positionNow, 0, reprap.GetCurrentXAxes()); // get the current position, we may need it later
- if (gb.Seen('S'))
- {
- move->SetCoreXYMode(gb.GetIValue());
- seen = true;
- }
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- move->SetCoreAxisFactor(axis, gb.GetFValue());
- seen = true;
- }
- }
-
- if (seen)
- {
- SetPositions(positionNow);
- SetAllAxesNotHomed();
- }
- else
- {
- reply.printf("Printer mode is %s with axis factors", move->GetGeometryString());
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- reply.catf(" %c:%f", axisLetters[axis], move->GetCoreAxisFactor(axis));
- }
- }
- }
- break;
-
- case 905: // Set current RTC date and time
- {
- const time_t now = platform->GetDateTime();
- struct tm * const timeInfo = gmtime(&now);
- bool seen = false;
-
- if (gb.Seen('P'))
- {
- // Set date
- const char * const dateString = gb.GetString();
- if (strptime(dateString, "%Y-%m-%d", timeInfo) != nullptr)
- {
- if (!platform->SetDate(mktime(timeInfo)))
- {
- reply.copy("Could not set date");
- error = true;
- break;
- }
- }
- else
- {
- reply.copy("Invalid date format");
- error = true;
- break;
- }
-
- seen = true;
- }
-
- if (gb.Seen('S'))
- {
- // Set time
- const char * const timeString = gb.GetString();
- if (strptime(timeString, "%H:%M:%S", timeInfo) != nullptr)
- {
- if (!platform->SetTime(mktime(timeInfo)))
- {
- reply.copy("Could not set time");
- error = true;
- break;
- }
- }
- else
- {
- reply.copy("Invalid time format");
- error = true;
- break;
- }
-
- seen = true;
- }
-
- // TODO: Add correction parameters for SAM4E
-
- if (!seen)
- {
- // Report current date and time
- reply.printf("Current date and time: %04u-%02u-%02u %02u:%02u:%02u",
- timeInfo->tm_year + 1900, timeInfo->tm_mon + 1, timeInfo->tm_mday,
- timeInfo->tm_hour, timeInfo->tm_min, timeInfo->tm_sec);
-
- if (!platform->IsDateTimeSet())
- {
- reply.cat("\nWarning: RTC has not been configured yet!");
- }
- }
- }
- break;
-
- case 906: // Set/report Motor currents
- case 913: // Set/report motor current percent
- {
- bool seen = false;
- for (size_t axis = 0; axis < numAxes; axis++)
- {
- if (gb.Seen(axisLetters[axis]))
- {
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- platform->SetMotorCurrent(axis, gb.GetFValue(), code == 913);
- seen = true;
- }
- }
-
- if (gb.Seen(extrudeLetter))
- {
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
-
- float eVals[MaxExtruders];
- size_t eCount = numExtruders;
- gb.GetFloatArray(eVals, eCount, true);
- // 2014-09-29 DC42: we no longer insist that the user supplies values for all possible extruder drives
- for (size_t e = 0; e < eCount; e++)
- {
- platform->SetMotorCurrent(numAxes + e, eVals[e], code == 913);
- }
- seen = true;
- }
-
- if (code == 906 && gb.Seen('I'))
- {
- float idleFactor = gb.GetFValue();
- if (idleFactor >= 0 && idleFactor <= 100.0)
- {
- platform->SetIdleCurrentFactor(idleFactor/100.0);
- seen = true;
- }
- }
-
- if (!seen)
- {
- reply.copy((code == 913) ? "Motor current % of normal - " : "Motor current (mA) - ");
- for (size_t axis = 0; axis < numAxes; ++axis)
- {
- reply.catf("%c:%d, ", axisLetters[axis], (int)platform->GetMotorCurrent(axis, code == 913));
- }
- reply.cat("E");
- for (size_t extruder = 0; extruder < numExtruders; extruder++)
- {
- reply.catf(":%d", (int)platform->GetMotorCurrent(extruder + numAxes, code == 913));
- }
- if (code == 906)
- {
- reply.catf(", idle factor %d%%", (int)(platform->GetIdleCurrentFactor() * 100.0));
- }
- }
- }
- break;
-
- case 911: // Set power monitor threshold voltages
- reply.printf("M911 not implemented yet");
- break;
-
- case 912: // Set electronics temperature monitor adjustment
- // Currently we ignore the P parameter (i.e. temperature measurement channel)
- if (gb.Seen('S'))
- {
- platform->SetMcuTemperatureAdjust(gb.GetFValue());
- }
- else
- {
- reply.printf("MCU temperature calibration adjustment is %.1f" DEGREE_SYMBOL "C", platform->GetMcuTemperatureAdjust());
- }
- break;
-
- // For case 913, see 906
-
- case 997: // Perform firmware update
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
- reprap.GetHeat()->SwitchOffAll(); // turn all heaters off because the main loop may get suspended
- DisableDrives(); // all motors off
-
- if (firmwareUpdateModuleMap == 0) // have we worked out which modules to update?
- {
- // Find out which modules we have been asked to update
- if (gb.Seen('S'))
- {
- long modulesToUpdate[3];
- size_t numUpdateModules = ARRAY_SIZE(modulesToUpdate);
- gb.GetLongArray(modulesToUpdate, numUpdateModules);
- for (size_t i = 0; i < numUpdateModules; ++i)
- {
- long t = modulesToUpdate[i];
- if (t < 0 || (unsigned long)t >= NumFirmwareUpdateModules)
- {
- platform->MessageF(GENERIC_MESSAGE, "Invalid module number '%ld'\n", t);
- firmwareUpdateModuleMap = 0;
- break;
- }
- firmwareUpdateModuleMap |= (1u << (unsigned int)t);
- }
- }
- else
- {
- firmwareUpdateModuleMap = (1u << 0); // no modules specified, so update module 0 to match old behaviour
- }
-
- if (firmwareUpdateModuleMap == 0)
- {
- break; // nothing to update
- }
-
- // Check prerequisites of all modules to be updated, if any are not met then don't update any of them
-#ifdef DUET_NG
- if (!FirmwareUpdater::CheckFirmwareUpdatePrerequisites(firmwareUpdateModuleMap))
- {
- firmwareUpdateModuleMap = 0;
- break;
- }
-#endif
- if ((firmwareUpdateModuleMap & 1) != 0 && !platform->CheckFirmwareUpdatePrerequisites())
- {
- firmwareUpdateModuleMap = 0;
- break;
- }
- }
-
- // If we get here then we have the module map, and all prerequisites are satisfied
- isFlashing = true; // this tells the web interface and PanelDue that we are about to flash firmware
- if (!DoDwellTime(1.0)) // wait a second so all HTTP clients and PanelDue are notified
- {
- return false;
- }
-
- gb.SetState(GCodeState::flashing1);
- break;
-
- case 998:
- // The input handling code replaces the gcode by this when it detects a checksum error.
- // Since we have no way of asking for the line to be re-sent, just report an error.
- if (gb.Seen('P'))
- {
- int val = gb.GetIValue();
- if (val != 0)
- {
- reply.printf("Checksum error on line %d", val);
- }
- }
- break;
-
- case 999:
- result = DoDwellTime(0.5); // wait half a second to allow the response to be sent back to the web server, otherwise it may retry
- if (result)
- {
- reprap.EmergencyStop(); // this disables heaters and drives - Duet WiFi pre-production boards need drives disabled here
- uint16_t reason = (gb.Seen('P') && StringStartsWith(gb.GetString(), "ERASE"))
- ? (uint16_t)SoftwareResetReason::erase
- : (uint16_t)SoftwareResetReason::user;
- platform->SoftwareReset(reason); // doesn't return
- }
- break;
-
- default:
- error = true;
- reply.printf("unsupported command: %s", gb.Buffer());
- }
-
- if (result && gb.GetState() == GCodeState::normal)
- {
- UnlockAll(gb);
- HandleReply(gb, error, reply.Pointer());
- }
- return result;
-}
-
-bool GCodes::HandleTcode(GCodeBuffer& gb, StringRef& reply)
-{
- if (!LockMovementAndWaitForStandstill(gb))
- {
- return false;
- }
-
- newToolNumber = gb.GetIValue();
- newToolNumber += gb.GetToolNumberAdjust();
-
- // TODO for the tool change restore point to be useful, we should undo any X axis mapping and remove any tool offsets
- for (size_t drive = 0; drive < DRIVES; ++drive)
- {
- toolChangeRestorePoint.moveCoords[drive] = moveBuffer.coords[drive];
- }
- toolChangeRestorePoint.feedRate = feedRate;
-
- if (simulationMode == 0) // we don't yet simulate any T codes
- {
- const Tool * const oldTool = reprap.GetCurrentTool();
- // If old and new are the same we no longer follow the sequence. User can deselect and then reselect the tool if he wants the macros run.
- if (oldTool->Number() != newToolNumber)
- {
- gb.SetState(GCodeState::toolChange1);
- if (oldTool != nullptr && AllAxesAreHomed())
- {
- scratchString.printf("tfree%d.g", oldTool->Number());
- DoFileMacro(gb, scratchString.Pointer(), false);
- }
- return true; // proceeding with state machine, so don't unlock or send a reply
- }
- }
-
- // If we get here, we have finished
- UnlockAll(gb);
- HandleReply(gb, false, "");
- return true;
-}
-
-// Return the amount of filament extruded
-float GCodes::GetRawExtruderPosition(size_t extruder) const
-{
- return (extruder < numExtruders) ? lastRawExtruderPosition[extruder] : 0.0;
-}
-
-float GCodes::GetRawExtruderTotalByDrive(size_t extruder) const
-{
- return (extruder < numExtruders) ? rawExtruderTotalByDrive[extruder] : 0.0;
-}
-
-// Cancel the current SD card print.
-// This is called from Pid.cpp when there is a heater fault, and from elsewhere in this module.
-void GCodes::CancelPrint()
-{
- moveAvailable = false;
- isPaused = false;
-
- fileGCode->Init();
- FileData& fileBeingPrinted = fileGCode->OriginalMachineState().fileState;
- if (fileBeingPrinted.IsLive())
- {
- fileBeingPrinted.Close();
- }
-
- reprap.GetPrintMonitor()->StoppedPrint();
-}
-
-// Return true if all the heaters for the specified tool are at their set temperatures
-bool GCodes::ToolHeatersAtSetTemperatures(const Tool *tool, bool waitWhenCooling) const
-{
- if (tool != NULL)
- {
- for (size_t i = 0; i < tool->HeaterCount(); ++i)
- {
- if (!reprap.GetHeat()->HeaterAtSetTemperature(tool->Heater(i), waitWhenCooling))
- {
- return false;
- }
- }
- }
- return true;
-}
-
-// Set the current position
-void GCodes::SetPositions(float positionNow[DRIVES])
-{
- // Transform the position so that e.g. if the user does G92 Z0,
- // the position we report (which gets inverse-transformed) really is Z=0 afterwards
- reprap.GetMove()->Transform(positionNow, reprap.GetCurrentXAxes());
- reprap.GetMove()->SetLiveCoordinates(positionNow);
- reprap.GetMove()->SetPositions(positionNow);
-}
-
-bool GCodes::IsPaused() const
-{
- return isPaused && !IsPausing() && !IsResuming();
-}
-
-bool GCodes::IsPausing() const
-{
- const GCodeState topState = fileGCode->OriginalMachineState().state;
- return topState == GCodeState::pausing1 || topState == GCodeState::pausing2;
-}
-
-bool GCodes::IsResuming() const
-{
- const GCodeState topState = fileGCode->OriginalMachineState().state;
- return topState == GCodeState::resuming1 || topState == GCodeState::resuming2 || topState == GCodeState::resuming3;
-}
-
-bool GCodes::IsRunning() const
-{
- return !IsPaused() && !IsPausing() && !IsResuming();
-}
-
-const char *GCodes::TranslateEndStopResult(EndStopHit es)
-{
- switch (es)
- {
- case EndStopHit::lowHit:
- return "at min stop";
-
- case EndStopHit::highHit:
- return "at max stop";
-
- case EndStopHit::lowNear:
- return "near min stop";
-
- case EndStopHit::noStop:
- default:
- return "not stopped";
- }
-}
-
-// Append a list of trigger endstops to a message
-void GCodes::ListTriggers(StringRef reply, TriggerMask mask)
-{
- if (mask == 0)
- {
- reply.cat("(none)");
- }
- else
- {
- bool printed = false;
- for (unsigned int i = 0; i < DRIVES; ++i)
- {
- if ((mask & (1u << i)) != 0)
- {
- if (printed)
- {
- reply.cat(' ');
- }
- if (i < numAxes)
- {
- reply.cat(axisLetters[i]);
- }
- else
- {
- reply.catf("E%d", i - numAxes);
- }
- printed = true;
- }
- }
- }
-}
-
-// M38 (SHA1 hash of a file) implementation:
-bool GCodes::StartHash(const char* filename)
-{
- // Get a FileStore object
- fileBeingHashed = platform->GetFileStore(FS_PREFIX, filename, false);
- if (fileBeingHashed == nullptr)
- {
- return false;
- }
-
- // Start hashing
- SHA1Reset(&hash);
- return true;
-}
-
-bool GCodes::AdvanceHash(StringRef &reply)
-{
- // Read and process some more data from the file
- uint32_t buf32[(FILE_BUFFER_SIZE + 3) / 4];
- char *buffer = reinterpret_cast<char *>(buf32);
-
- int bytesRead = fileBeingHashed->Read(buffer, FILE_BUFFER_SIZE);
- if (bytesRead != -1)
- {
- SHA1Input(&hash, reinterpret_cast<const uint8_t *>(buffer), bytesRead);
-
- if (bytesRead != FILE_BUFFER_SIZE)
- {
- // Calculate and report the final result
- SHA1Result(&hash);
- for(size_t i = 0; i < 5; i++)
- {
- reply.catf("%x", hash.Message_Digest[i]);
- }
-
- // Clean up again
- fileBeingHashed->Close();
- fileBeingHashed = nullptr;
- return true;
- }
- return false;
- }
-
- // Something went wrong, we cannot read any more from the file
- fileBeingHashed->Close();
- fileBeingHashed = nullptr;
- return true;
-}
-
-bool GCodes::AllAxesAreHomed() const
-{
- const uint32_t allAxes = (1u << numAxes) - 1;
- return (axesHomed & allAxes) == allAxes;
-}
-
-void GCodes::SetAllAxesNotHomed()
-{
- axesHomed = 0;
-}
-
-// Resource locking/unlocking
-
-// Lock the resource, returning true if success
-bool GCodes::LockResource(const GCodeBuffer& gb, Resource r)
-{
- if (resourceOwners[r] == &gb)
- {
- return true;
- }
- if (resourceOwners[r] == nullptr)
- {
- resourceOwners[r] = &gb;
- gb.MachineState().lockedResources |= (1 << r);
- return true;
- }
- return false;
-}
-
-bool GCodes::LockHeater(const GCodeBuffer& gb, int heater)
-{
- if (heater >= 0 && heater < HEATERS)
- {
- return LockResource(gb, HeaterResourceBase + heater);
- }
- return true;
-}
-
-bool GCodes::LockFan(const GCodeBuffer& gb, int fan)
-{
- if (fan >= 0 && fan < (int)NUM_FANS)
- {
- return LockResource(gb, FanResourceBase + fan);
- }
- return true;
-}
-
-// Lock the unshareable parts of the file system
-bool GCodes::LockFileSystem(const GCodeBuffer &gb)
-{
- return LockResource(gb, FileSystemResource);
-}
-
-// Lock movement
-bool GCodes::LockMovement(const GCodeBuffer& gb)
-{
- return LockResource(gb, MoveResource);
-}
-
-// Lock movement and wait for pending moves to finish
-bool GCodes::LockMovementAndWaitForStandstill(const GCodeBuffer& gb)
-{
- bool b = LockMovement(gb);
- if (b)
- {
- b = AllMovesAreFinishedAndMoveBufferIsLoaded();
- }
- return b;
-}
-
-// Release all locks, except those that were owned when the current macro was started
-void GCodes::UnlockAll(const GCodeBuffer& gb)
-{
- const GCodeMachineState * const mc = gb.MachineState().previous;
- const uint32_t resourcesToKeep = (mc == nullptr) ? 0 : mc->lockedResources;
- for (size_t i = 0; i < NumResources; ++i)
- {
- if (resourceOwners[i] == &gb && ((1 << i) & resourcesToKeep) == 0)
- {
- resourceOwners[i] = nullptr;
- gb.MachineState().lockedResources &= ~(1 << i);
- }
- }
-}
-
-// Convert an array of longs to a bit map
-/*static*/ uint32_t GCodes::LongArrayToBitMap(const long *arr, size_t numEntries)
-{
- uint32_t res = 0;
- for (size_t i = 0; i < numEntries; ++i)
- {
- const long f = arr[i];
- if (f >= 0 && f < 32)
- {
- res |= 1u << (unsigned int)f;
- }
- }
- return res;
-}
-
-// End
diff --git a/src/GCodes/GCodes.h b/src/GCodes/GCodes.h
index c6b6b476..eee3cd72 100644
--- a/src/GCodes/GCodes.h
+++ b/src/GCodes/GCodes.h
@@ -33,9 +33,6 @@ typedef uint16_t EndstopChecks; // must be large enough to hold a bitmap o
const EndstopChecks ZProbeActive = 1 << 15; // must be distinct from 1 << (any drive number)
const EndstopChecks LogProbeChanges = 1 << 14; // must be distinct from 1 << (any drive number)
-const float minutesToSeconds = 60.0;
-const float secondsToMinutes = 1.0/minutesToSeconds;
-
typedef uint16_t TriggerMask;
struct Trigger
@@ -66,6 +63,7 @@ public:
struct RawMove
{
float coords[DRIVES]; // new positions for the axes, amount of movement for the extruders
+ float initialCoords[MAX_AXES]; // the initial positions of the axes
float feedRate; // feed rate of this move
FilePosition filePos; // offset in the file being printed that this move was read from
uint32_t xAxes; // axes that X is mapped to
@@ -156,7 +154,6 @@ private:
void StartNextGCode(GCodeBuffer& gb, StringRef& reply); // Fetch a new or old GCode and process it
void DoFilePrint(GCodeBuffer& gb, StringRef& reply); // Get G Codes from a file and print them
- bool AllMovesAreFinishedAndMoveBufferIsLoaded(); // Wait for move queue to exhaust and the current position is loaded
bool DoFileMacro(GCodeBuffer& gb, const char* fileName, bool reportMissing = true); // Run a GCode macro in a file, optionally report error if not found
bool DoCannedCycleMove(GCodeBuffer& gb, EndstopChecks ce); // Do a move from an internally programmed canned cycle
void FileMacroCyclesReturn(GCodeBuffer& gb); // End a macro
@@ -176,7 +173,7 @@ private:
bool SetPrintZProbe(GCodeBuffer& gb, StringRef& reply); // Either return the probe value, or set its threshold
bool SetOrReportOffsets(GCodeBuffer& gb, StringRef& reply); // Deal with a G10
bool SetPositions(GCodeBuffer& gb); // Deal with a G92
- bool LoadMoveBufferFromGCode(GCodeBuffer& gb, int moveType); // Set up a move for the Move class
+ unsigned int LoadMoveBufferFromGCode(GCodeBuffer& gb, int moveType); // Set up a move for the Move class
bool NoHome() const; // Are we homing and not finished?
bool Push(GCodeBuffer& gb); // Push feedrate etc on the stack
void Pop(GCodeBuffer& gb); // Pop feedrate etc
@@ -185,17 +182,18 @@ private:
void SetMACAddress(GCodeBuffer& gb); // Deals with an M540
void HandleReply(GCodeBuffer& gb, bool error, const char *reply); // Handle G-Code replies
void HandleReply(GCodeBuffer& gb, bool error, OutputBuffer *reply);
- bool OpenFileToWrite(GCodeBuffer& gb, const char* directory, // Start saving GCodes in a file
- const char* fileName);
+ bool OpenFileToWrite(GCodeBuffer& gb, const char* directory, const char* fileName); // Start saving GCodes in a file
void WriteGCodeToFile(GCodeBuffer& gb); // Write this GCode into a file
bool SendConfigToLine(); // Deal with M503
void WriteHTMLToFile(GCodeBuffer& gb, char b); // Save an HTML file (usually to upload a new web interface)
bool OffsetAxes(GCodeBuffer& gb); // Set offsets - deprecated, use G10
- void SetPidParameters(GCodeBuffer& gb, int heater, StringRef& reply); // Set the P/I/D parameters for a heater
- void SetHeaterParameters(GCodeBuffer& gb, StringRef& reply); // Set the thermistor and ADC parameters for a heater
+ void SetPidParameters(GCodeBuffer& gb, int heater, StringRef& reply); // Set the P/I/D parameters for a heater
+ void SetHeaterParameters(GCodeBuffer& gb, StringRef& reply); // Set the thermistor and ADC parameters for a heater
void ManageTool(GCodeBuffer& gb, StringRef& reply); // Create a new tool definition
void SetToolHeaters(Tool *tool, float temperature); // Set all a tool's heaters to the temperature. For M104...
- bool ToolHeatersAtSetTemperatures(const Tool *tool, bool waitWhenCooling) const; // Wait for the heaters associated with the specified tool to reach their set temperatures
+ bool ToolHeatersAtSetTemperatures(const Tool *tool, bool waitWhenCooling) const; // Wait for the heaters associated with the specified tool to reach their set temperatures
+ void StartToolChange(GCodeBuffer& gb, bool inM109); // Begin the tool change sequence
+
void SetAllAxesNotHomed(); // Flag all axes as not homed
void SetPositions(float positionNow[DRIVES]); // Set the current position to be this
const char *TranslateEndStopResult(EndStopHit es); // Translate end stop result to text
@@ -204,11 +202,15 @@ private:
void ListTriggers(StringRef reply, TriggerMask mask); // Append a list of trigger endstops to a message
void CheckTriggers(); // Check for and execute triggers
void DoEmergencyStop(); // Execute an emergency stop
- void DoPause(GCodeBuffer& gb); // Pause the print
+
+ void DoPause(GCodeBuffer& gb) // Pause the print
+ pre(resourceOwners[movementResource] = &gb);
+
void SetMappedFanSpeed(); // Set the speeds of fans mapped for the current tool
bool DefineGrid(GCodeBuffer& gb, StringRef &reply); // Define the probing grid, returning true if error
bool ProbeGrid(GCodeBuffer& gb, StringRef& reply); // Start probing the grid, returning true if we didn't because of an error
+ bool SaveHeightMapToFile(StringRef& reply) const; // Save the height map to file
static uint32_t LongArrayToBitMap(const long *arr, size_t numEntries); // Convert an array of longs to a bit map
@@ -230,9 +232,8 @@ private:
bool active; // Live and running?
bool isPaused; // true if the print has been paused
bool dwellWaiting; // We are in a dwell
- bool moveAvailable; // Have we seen a move G Code and set it up?
+ unsigned int segmentsLeft; // The number of segments left to do in the current move, or 0 if no move available
float dwellTime; // How long a pause for a dwell (seconds)?
- float feedRate; // The feed rate of the last G0/G1 command that had an F parameter
RawMove moveBuffer; // Move details to pass to Move class
RestorePoint simulationRestorePoint; // The position and feed rate when we started a simulation
RestorePoint pauseRestorePoint; // The position and feed rate when we paused the print
@@ -269,6 +270,7 @@ private:
// Z probe
float lastProbedZ; // the last height at which the Z probe stopped
+ uint32_t lastProbedTime; // time in milliseconds that the probe was last triggered
bool zProbesSet; // True if all Z probing is done and we can set the bed equation
volatile bool zProbeTriggered; // Set by the step ISR when a move is aborted because the Z probe is triggered
size_t gridXindex, gridYindex; // Which grid probe point is next
diff --git a/src/GCodes/GCodes1.cpp b/src/GCodes/GCodes1.cpp
new file mode 100644
index 00000000..e5622453
--- /dev/null
+++ b/src/GCodes/GCodes1.cpp
@@ -0,0 +1,3286 @@
+/****************************************************************************************************
+
+ RepRapFirmware - G Codes
+
+ This class interprets G Codes from one or more sources, and calls the functions in Move, Heat etc
+ that drive the machine to do what the G Codes command.
+
+ Most of the functions in here are designed not to wait, and they return a boolean. When you want them to do
+ something, you call them. If they return false, the machine can't do what you want yet. So you go away
+ and do something else. Then you try again. If they return true, the thing you wanted done has been done.
+
+ -----------------------------------------------------------------------------------------------------
+
+ Version 0.1
+
+ 13 February 2013
+
+ Adrian Bowyer
+ RepRap Professional Ltd
+ http://reprappro.com
+
+ Licence: GPL
+
+ ****************************************************************************************************/
+
+#include "RepRapFirmware.h"
+
+#ifdef DUET_NG
+#include "FirmwareUpdater.h"
+#endif
+
+#define DEGREE_SYMBOL "\xC2\xB0" // degree-symbol encoding in UTF8
+
+const char GCodes::axisLetters[MAX_AXES] = { 'X', 'Y', 'Z', 'U', 'V', 'W' };
+
+const char* PAUSE_G = "pause.g";
+const char* HomingFileNames[MAX_AXES] = { "homex.g", "homey.g", "homez.g", "homeu.g", "homev.g", "homew.g" };
+const char* HOME_ALL_G = "homeall.g";
+const char* HOME_DELTA_G = "homedelta.g";
+const char* DefaultHeightMapFile = "heightmap.csv";
+
+const size_t gcodeReplyLength = 2048; // long enough to pass back a reasonable number of files in response to M20
+
+void GCodes::RestorePoint::Init()
+{
+ for (size_t i = 0; i < DRIVES; ++i)
+ {
+ moveCoords[i] = 0.0;
+ }
+ feedRate = DEFAULT_FEEDRATE/minutesToSeconds;
+}
+
+GCodes::GCodes(Platform* p, Webserver* w) :
+ platform(p), webserver(w), active(false), isFlashing(false),
+ fileBeingHashed(nullptr)
+{
+ httpGCode = new GCodeBuffer("http", HTTP_MESSAGE);
+ telnetGCode = new GCodeBuffer("telnet", TELNET_MESSAGE);
+ fileGCode = new GCodeBuffer("file", GENERIC_MESSAGE);
+ serialGCode = new GCodeBuffer("serial", HOST_MESSAGE);
+ auxGCode = new GCodeBuffer("aux", AUX_MESSAGE);
+ daemonGCode = new GCodeBuffer("daemon", GENERIC_MESSAGE);
+}
+
+void GCodes::Exit()
+{
+ platform->Message(HOST_MESSAGE, "GCodes class exited.\n");
+ active = false;
+}
+
+void GCodes::Init()
+{
+ Reset();
+ numAxes = MIN_AXES;
+ numExtruders = MaxExtruders;
+ distanceScale = 1.0;
+ rawExtruderTotal = 0.0;
+ for (size_t extruder = 0; extruder < MaxExtruders; extruder++)
+ {
+ lastRawExtruderPosition[extruder] = 0.0;
+ rawExtruderTotalByDrive[extruder] = 0.0;
+ }
+ eofString = EOF_STRING;
+ eofStringCounter = 0;
+ eofStringLength = strlen(eofString);
+ offSetSet = false;
+ zProbesSet = false;
+ active = true;
+ longWait = platform->Time();
+ dwellTime = longWait;
+ limitAxes = true;
+ for(size_t axis = 0; axis < MAX_AXES; axis++)
+ {
+ axisScaleFactors[axis] = 1.0;
+ }
+ SetAllAxesNotHomed();
+ for (size_t i = 0; i < NUM_FANS; ++i)
+ {
+ pausedFanValues[i] = 0.0;
+ }
+ lastDefaultFanSpeed = 0.0;
+
+ retractLength = retractExtra = retractHop = 0.0;
+ retractSpeed = unRetractSpeed = 600.0;
+}
+
+// This is called from Init and when doing an emergency stop
+void GCodes::Reset()
+{
+ httpGCode->Init();
+ telnetGCode->Init();
+ fileGCode->Init();
+ serialGCode->Init();
+ auxGCode->Init();
+ auxGCode->SetCommsProperties(1); // by default, we require a checksum on the aux port
+ daemonGCode->Init();
+
+ nextGcodeSource = 0;
+
+ fileToPrint.Close();
+ fileBeingWritten = NULL;
+ dwellWaiting = false;
+ probeCount = 0;
+ cannedCycleMoveCount = 0;
+ cannedCycleMoveQueued = false;
+ speedFactor = 1.0 / minutesToSeconds; // default is just to convert from mm/minute to mm/second
+ for (size_t i = 0; i < MaxExtruders; ++i)
+ {
+ extrusionFactors[i] = 1.0;
+ }
+ for (size_t i = 0; i < DRIVES; ++i)
+ {
+ moveBuffer.coords[i] = 0.0;
+ }
+ moveBuffer.xAxes = DefaultXAxisMapping;
+
+ pauseRestorePoint.Init();
+ toolChangeRestorePoint.Init();
+
+ ClearMove();
+
+ for (size_t i = 0; i < MaxTriggers; ++i)
+ {
+ triggers[i].Init();
+ }
+ triggersPending = 0;
+
+ simulationMode = 0;
+ simulationTime = 0.0;
+ isPaused = false;
+ filePos = moveBuffer.filePos = noFilePosition;
+ lastEndstopStates = platform->GetAllEndstopStates();
+ firmwareUpdateModuleMap = 0;
+
+ cancelWait = isWaiting = false;
+
+ for (size_t i = 0; i < NumResources; ++i)
+ {
+ resourceOwners[i] = nullptr;
+ }
+
+ lastProbedTime = millis();
+}
+
+float GCodes::FractionOfFilePrinted() const
+{
+ const FileData& fileBeingPrinted = fileGCode->OriginalMachineState().fileState;
+ return (fileBeingPrinted.IsLive()) ? fileBeingPrinted.FractionRead() : -1.0;
+}
+
+// Start running the config file
+// We use triggerCGode as the source to prevent any triggers being executed until we have finished
+bool GCodes::RunConfigFile(const char* fileName)
+{
+ return DoFileMacro(*daemonGCode, fileName, false);
+}
+
+// Are we still running the config file?
+bool GCodes::IsRunningConfigFile() const
+{
+ return daemonGCode->MachineState().fileState.IsLive();
+}
+
+void GCodes::Spin()
+{
+ if (!active)
+ {
+ return;
+ }
+
+ CheckTriggers();
+
+ // Get the GCodeBuffer that we want to work from
+ GCodeBuffer& gb = *(gcodeSources[nextGcodeSource]);
+
+ // Set up a buffer for the reply
+ char replyBuffer[gcodeReplyLength];
+ StringRef reply(replyBuffer, ARRAY_SIZE(replyBuffer));
+ reply.Clear();
+
+ if (gb.GetState() == GCodeState::normal)
+ {
+ StartNextGCode(gb, reply);
+ }
+ else
+ {
+ // Perform the next operation of the state machine for this gcode source
+ bool error = false;
+
+ switch (gb.GetState())
+ {
+ case GCodeState::waitingForMoveToComplete:
+ if (LockMovementAndWaitForStandstill(gb))
+ {
+ gb.SetState(GCodeState::normal);
+ }
+ break;
+
+ case GCodeState::homing:
+ if (toBeHomed == 0)
+ {
+ gb.SetState(GCodeState::normal);
+ }
+ else
+ {
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ // Leave the Z axis until all other axes are done
+ if ((toBeHomed & (1u << axis)) != 0 && (axis != Z_AXIS || toBeHomed == (1u << Z_AXIS)))
+ {
+ toBeHomed &= ~(1u << axis);
+ DoFileMacro(gb, HomingFileNames[axis]);
+ break;
+ }
+ }
+ }
+ break;
+
+ case GCodeState::setBed1:
+ reprap.GetMove()->SetIdentityTransform();
+ probeCount = 0;
+ gb.SetState(GCodeState::setBed2);
+ // no break
+
+ case GCodeState::setBed2:
+ {
+ int numProbePoints = reprap.GetMove()->NumberOfXYProbePoints();
+ if (DoSingleZProbeAtPoint(gb, probeCount, 0.0))
+ {
+ probeCount++;
+ if (probeCount >= numProbePoints)
+ {
+ zProbesSet = true;
+ reprap.GetMove()->FinishedBedProbing(0, reply);
+ gb.SetState(GCodeState::normal);
+ }
+ }
+ }
+ break;
+
+ case GCodeState::toolChange1: // Release the old tool (if any)
+ case GCodeState::m109ToolChange1: // Release the old tool (if any)
+ {
+ const Tool *oldTool = reprap.GetCurrentTool();
+ if (oldTool != NULL)
+ {
+ reprap.StandbyTool(oldTool->Number());
+ }
+ }
+ gb.AdvanceState();
+ if (reprap.GetTool(newToolNumber) != nullptr && AllAxesAreHomed())
+ {
+ scratchString.printf("tpre%d.g", newToolNumber);
+ DoFileMacro(gb, scratchString.Pointer(), false);
+ }
+ break;
+
+ case GCodeState::toolChange2: // Select the new tool (even if it doesn't exist - that just deselects all tools)
+ case GCodeState::m109ToolChange2: // Select the new tool (even if it doesn't exist - that just deselects all tools)
+ reprap.SelectTool(newToolNumber);
+ gb.AdvanceState();
+ if (reprap.GetTool(newToolNumber) != nullptr && AllAxesAreHomed())
+ {
+ scratchString.printf("tpost%d.g", newToolNumber);
+ DoFileMacro(gb, scratchString.Pointer(), false);
+ }
+ break;
+
+ case GCodeState::toolChangeComplete:
+ gb.SetState(GCodeState::normal);
+ break;
+
+ case GCodeState::m109ToolChangeComplete:
+ if (cancelWait || ToolHeatersAtSetTemperatures(reprap.GetCurrentTool(), gb.MachineState().waitWhileCooling))
+ {
+ cancelWait = isWaiting = false;
+ gb.SetState(GCodeState::normal);
+ }
+ // In Marlin emulation mode we should return some sort of (undocumented) message here every second...
+ isWaiting = true;
+ break;
+
+ case GCodeState::pausing1:
+ if (LockMovementAndWaitForStandstill(gb))
+ {
+ gb.SetState(GCodeState::pausing2);
+ DoFileMacro(gb, PAUSE_G);
+ }
+ break;
+
+ case GCodeState::pausing2:
+ if (LockMovementAndWaitForStandstill(gb))
+ {
+ reply.copy("Printing paused");
+ gb.SetState(GCodeState::normal);
+ }
+ break;
+
+ case GCodeState::resuming1:
+ case GCodeState::resuming2:
+ // Here when we have just finished running the resume macro file.
+ // Move the head back to the paused location
+ if (LockMovementAndWaitForStandstill(gb))
+ {
+ float currentZ = moveBuffer.coords[Z_AXIS];
+ for (size_t drive = 0; drive < numAxes; ++drive)
+ {
+ moveBuffer.coords[drive] = pauseRestorePoint.moveCoords[drive];
+ }
+ for (size_t drive = numAxes; drive < DRIVES; ++drive)
+ {
+ moveBuffer.coords[drive] = 0.0;
+ }
+ moveBuffer.feedRate = DEFAULT_FEEDRATE/minutesToSeconds; // ask for a good feed rate, we may have paused during a slow move
+ moveBuffer.moveType = 0;
+ moveBuffer.endStopsToCheck = 0;
+ moveBuffer.usePressureAdvance = false;
+ moveBuffer.filePos = noFilePosition;
+ if (gb.GetState() == GCodeState::resuming1 && currentZ > pauseRestorePoint.moveCoords[Z_AXIS])
+ {
+ // First move the head to the correct XY point, then move it down in a separate move
+ moveBuffer.coords[Z_AXIS] = currentZ;
+ gb.SetState(GCodeState::resuming2);
+ }
+ else
+ {
+ // Just move to the saved position in one go
+ gb.SetState(GCodeState::resuming3);
+ }
+ segmentsLeft = 1;
+ }
+ break;
+
+ case GCodeState::resuming3:
+ if (LockMovementAndWaitForStandstill(gb))
+ {
+ for (size_t i = 0; i < NUM_FANS; ++i)
+ {
+ platform->SetFanValue(i, pausedFanValues[i]);
+ }
+ for (size_t drive = numAxes; drive < DRIVES; ++drive)
+ {
+ lastRawExtruderPosition[drive - numAxes] = pauseRestorePoint.moveCoords[drive]; // reset the extruder position in case we are receiving absolute extruder moves
+ }
+ fileGCode->MachineState().feedrate = pauseRestorePoint.feedRate;
+ isPaused = false;
+ reply.copy("Printing resumed");
+ gb.SetState(GCodeState::normal);
+ }
+ break;
+
+ case GCodeState::flashing1:
+#ifdef DUET_NG
+ // Update additional modules before the main firmware
+ if (FirmwareUpdater::IsReady())
+ {
+ bool updating = false;
+ for (unsigned int module = 1; module < NumFirmwareUpdateModules; ++module)
+ {
+ if ((firmwareUpdateModuleMap & (1u << module)) != 0)
+ {
+ firmwareUpdateModuleMap &= ~(1u << module);
+ FirmwareUpdater::UpdateModule(module);
+ updating = true;
+ break;
+ }
+ }
+ if (!updating)
+ {
+ gb.SetState(GCodeState::flashing2);
+ }
+ }
+#else
+ gb.SetState(GCodeState::flashing2);
+#endif
+ break;
+
+ case GCodeState::flashing2:
+ if ((firmwareUpdateModuleMap & 1) != 0)
+ {
+ // Update main firmware
+ firmwareUpdateModuleMap = 0;
+ platform->UpdateFirmware();
+ // The above call does not return unless an error occurred
+ }
+ isFlashing = false;
+ gb.SetState(GCodeState::normal);
+ break;
+
+ case GCodeState::stopping: // MO after executing stop.g if present
+ case GCodeState::sleeping: // M1 after executing sleep.g if present
+ // Deselect the active tool and turn off all heaters, unless parameter Hn was used with n > 0
+ if (!gb.Seen('H') || gb.GetIValue() <= 0)
+ {
+ Tool* tool = reprap.GetCurrentTool();
+ if (tool != nullptr)
+ {
+ reprap.StandbyTool(tool->Number());
+ }
+ reprap.GetHeat()->SwitchOffAll();
+ }
+
+ // chrishamm 2014-18-10: Although RRP says M0 is supposed to turn off all drives and heaters,
+ // I think M1 is sufficient for this purpose. Leave M0 for a normal reset.
+ if (gb.GetState() == GCodeState::sleeping)
+ {
+ DisableDrives();
+ }
+ else
+ {
+ platform->SetDriversIdle();
+ }
+ gb.SetState(GCodeState::normal);
+ break;
+
+ case GCodeState::gridProbing1: // ready to move to next grid probe point
+ {
+ // Move to the current probe point
+ const GridDefinition& grid = reprap.GetMove()->AccessBedProbeGrid().GetGrid();
+ const float x = grid.GetXCoordinate(gridXindex);
+ const float y = grid.GetYCoordinate(gridYindex);
+ if (grid.IsInRadius(x, y) && platform->IsAccessibleProbePoint(x, y))
+ {
+ moveBuffer.moveType = 0;
+ moveBuffer.endStopsToCheck = 0;
+ moveBuffer.usePressureAdvance = false;
+ moveBuffer.filePos = noFilePosition;
+ moveBuffer.coords[X_AXIS] = x - platform->GetZProbeParameters().xOffset;
+ moveBuffer.coords[Y_AXIS] = y - platform->GetZProbeParameters().yOffset;
+ moveBuffer.coords[Z_AXIS] = platform->GetZProbeDiveHeight();
+ moveBuffer.feedRate = platform->GetZProbeTravelSpeed();
+ moveBuffer.xAxes = 0;
+ segmentsLeft = 1;
+ gb.SetState(GCodeState::gridProbing2);
+ }
+ else
+ {
+ gb.SetState(GCodeState::gridProbing4);
+ }
+ }
+ break;
+
+ case GCodeState::gridProbing2: // ready to probe the current grid probe point
+ if (LockMovementAndWaitForStandstill(gb)
+ && millis() - lastProbedTime >= (uint32_t)(reprap.GetPlatform()->GetZProbeParameters().recoveryTime * SecondsToMillis)
+ )
+ {
+ // Probe the bed at the current XY coordinates
+ // Check for probe already triggered at start
+ if (reprap.GetPlatform()->GetZProbeResult() == EndStopHit::lowHit)
+ {
+ reply.copy("Z probe already triggered before probing move started");
+ error = true;
+ gb.SetState(GCodeState::normal);
+ break;
+ }
+
+ zProbeTriggered = false;
+ moveBuffer.moveType = 0;
+ moveBuffer.endStopsToCheck = ZProbeActive;
+ moveBuffer.usePressureAdvance = false;
+ moveBuffer.filePos = noFilePosition;
+ moveBuffer.coords[Z_AXIS] = -platform->GetZProbeDiveHeight();
+ moveBuffer.feedRate = platform->GetZProbeParameters().probeSpeed;
+ moveBuffer.xAxes = 0;
+ segmentsLeft = 1;
+ gb.SetState(GCodeState::gridProbing3);
+ }
+ break;
+
+ case GCodeState::gridProbing3: // ready to lift the probe after probing the current grid probe point
+ if (LockMovementAndWaitForStandstill(gb))
+ {
+ if (!zProbeTriggered)
+ {
+ reply.copy("Z probe was not triggered during probing move");
+ error = true;
+ gb.SetState(GCodeState::normal);
+ break;
+ }
+
+ lastProbedTime = millis();
+ const float heightError = moveBuffer.coords[Z_AXIS] - platform->ZProbeStopHeight();
+ reprap.GetMove()->AccessBedProbeGrid().SetGridHeight(gridXindex, gridYindex, heightError);
+ ++numPointsProbed;
+ heightSum += (double)heightError;
+ heightSquaredSum += (double)heightError * (double)heightError;
+
+ // Move back up to the dive height
+ moveBuffer.moveType = 0;
+ moveBuffer.endStopsToCheck = 0;
+ moveBuffer.usePressureAdvance = false;
+ moveBuffer.filePos = noFilePosition;
+ moveBuffer.coords[Z_AXIS] = platform->GetZProbeDiveHeight();
+ moveBuffer.feedRate = platform->GetZProbeTravelSpeed();
+ moveBuffer.xAxes = 0;
+ segmentsLeft = 1;
+ gb.SetState(GCodeState::gridProbing4);
+ }
+ break;
+
+ case GCodeState::gridProbing4: // ready to compute the next probe point
+ if (LockMovementAndWaitForStandstill(gb))
+ {
+ const GridDefinition& grid = reprap.GetMove()->AccessBedProbeGrid().GetGrid();
+ if (gridYindex & 1)
+ {
+ // Odd row, so decreasing X
+ if (gridXindex == 0)
+ {
+ ++gridYindex;
+ }
+ else
+ {
+ --gridXindex;
+ }
+ }
+ else
+ {
+ // Even row, so increasing X
+ if (gridXindex + 1 == grid.NumXpoints())
+ {
+ ++gridYindex;
+ }
+ else
+ {
+ ++gridXindex;
+ }
+ }
+ if (gridYindex == grid.NumYpoints())
+ {
+ // Finished probing the grid
+ if (numPointsProbed >= 4)
+ {
+ error = SaveHeightMapToFile(reply);
+ const double mean = heightSum/numPointsProbed;
+ const double deviation = sqrt(((heightSquaredSum * numPointsProbed) - (heightSum * heightSum)))/numPointsProbed;
+ reply.catf(" - %u points probed, mean error %.2f, deviation %.2f", numPointsProbed, mean, deviation);
+ reprap.GetMove()->UseHeightMap(true);
+ }
+ else
+ {
+ reply.copy("Too few points probed");
+ error = true;
+ }
+ gb.SetState(GCodeState::normal);
+ }
+ else
+ {
+ gb.SetState(GCodeState::gridProbing1);
+ }
+ }
+ break;
+
+ default: // should not happen
+ platform->Message(GENERIC_MESSAGE, "Error: undefined GCodeState\n");
+ gb.SetState(GCodeState::normal);
+ break;
+ }
+
+ if (gb.GetState() == GCodeState::normal)
+ {
+ // We completed a command, so unlock resources and tell the host about it
+ UnlockAll(gb);
+ HandleReply(gb, error, reply.Pointer());
+ }
+ }
+
+ // Move on to the next gcode source ready for next time
+ ++nextGcodeSource;
+ if (nextGcodeSource == ARRAY_SIZE(gcodeSources))
+ {
+ nextGcodeSource = 0;
+ }
+
+ platform->ClassReport(longWait);
+}
+
+// Start a new gcode, or continue to execute one that has already been started:
+void GCodes::StartNextGCode(GCodeBuffer& gb, StringRef& reply)
+{
+ if (isPaused && &gb == fileGCode)
+ {
+ // We are paused, so don't process any more gcodes from the file being printed.
+ // There is a potential issue here if fileGCode holds any locks, so unlock everything.
+ UnlockAll(gb);
+ }
+ else if (gb.IsReady() || gb.IsExecuting())
+ {
+ gb.SetFinished(ActOnCode(gb, reply));
+ }
+ else if (gb.MachineState().fileState.IsLive())
+ {
+ DoFilePrint(gb, reply);
+ }
+ else if (&gb == httpGCode)
+ {
+ // Webserver
+ for (unsigned int i = 0; i < 16 && webserver->GCodeAvailable(WebSource::HTTP); ++i)
+ {
+ const char b = webserver->ReadGCode(WebSource::HTTP);
+ if (gb.Put(b))
+ {
+ // We have a complete gcode
+ if (gb.WritingFileDirectory() != nullptr)
+ {
+ WriteGCodeToFile(gb);
+ gb.SetFinished(true);
+ }
+ else
+ {
+ gb.SetFinished(ActOnCode(gb, reply));
+ }
+ break;
+ }
+ }
+ }
+ else if (&gb == telnetGCode)
+ {
+ // Telnet
+ for (unsigned int i = 0; i < GCODE_LENGTH && webserver->GCodeAvailable(WebSource::Telnet); ++i)
+ {
+ char b = webserver->ReadGCode(WebSource::Telnet);
+ if (gb.Put(b))
+ {
+ gb.SetFinished(ActOnCode(gb, reply));
+ break;
+ }
+ }
+ }
+ else if (&gb == serialGCode)
+ {
+ // USB interface
+ for (unsigned int i = 0; i < 16 && platform->GCodeAvailable(SerialSource::USB); ++i)
+ {
+ const char b = platform->ReadFromSource(SerialSource::USB);
+ // Check the special case of uploading the reprap.htm file
+ if (gb.WritingFileDirectory() == platform->GetWebDir())
+ {
+ WriteHTMLToFile(gb, b);
+ }
+ else if (gb.Put(b)) // add char to buffer and test whether the gcode is complete
+ {
+ // We have a complete gcode
+ if (gb.WritingFileDirectory() != nullptr)
+ {
+ WriteGCodeToFile(gb);
+ gb.SetFinished(true);
+ }
+ else
+ {
+ gb.SetFinished(ActOnCode(gb, reply));
+ }
+ break;
+ }
+ }
+ }
+ else if (&gb == auxGCode)
+ {
+ // Aux serial port (typically PanelDue)
+ for (unsigned int i = 0; i < 16 && platform->GCodeAvailable(SerialSource::AUX); ++i)
+ {
+ char b = platform->ReadFromSource(SerialSource::AUX);
+ if (gb.Put(b)) // add char to buffer and test whether the gcode is complete
+ {
+ platform->SetAuxDetected();
+ gb.SetFinished(ActOnCode(gb, reply));
+ break;
+ }
+ }
+ }
+}
+
+void GCodes::DoFilePrint(GCodeBuffer& gb, StringRef& reply)
+{
+ FileData& fd = gb.MachineState().fileState;
+ for (int i = 0; i < 50 && fd.IsLive(); ++i)
+ {
+ char b;
+ if (fd.Read(b))
+ {
+ if (gb.StartingNewCode() && &gb == fileGCode && gb.MachineState().previous == nullptr)
+ {
+ filePos = fd.GetPosition() - 1;
+ //debugPrintf("Set file pos %u\n", filePos);
+ }
+ if (gb.Put(b))
+ {
+ gb.SetFinished(ActOnCode(gb, reply));
+ return;
+ }
+ }
+ else
+ {
+ // We have reached the end of the file. Check for the last line of gcode not ending in newline.
+ if (!gb.StartingNewCode()) // if there is something in the buffer
+ {
+ if (gb.Put('\n')) // in case there wasn't a newline ending the file
+ {
+ gb.SetFinished(ActOnCode(gb, reply));
+ return;
+ }
+ }
+
+ gb.Init(); // mark buffer as empty
+
+ // Don't close the file until all moves have been completed, in case the print gets paused.
+ // Also, this keeps the state as 'Printing' until the print really has finished.
+ if (LockMovementAndWaitForStandstill(gb))
+ {
+ fd.Close();
+ if (gb.MachineState().previous == nullptr)
+ {
+ // Finished printing SD card file
+ reprap.GetPrintMonitor()->StoppedPrint();
+ if (platform->Emulating() == marlin)
+ {
+ // Pronterface expects a "Done printing" message
+ HandleReply(gb, false, "Done printing file");
+ }
+ }
+ else
+ {
+ // Finished a macro
+ Pop(gb);
+ gb.Init();
+ if (gb.GetState() == GCodeState::normal)
+ {
+ UnlockAll(gb);
+ HandleReply(gb, false, "");
+ }
+ }
+ }
+ return;
+ }
+ }
+}
+
+// Check for and execute triggers
+void GCodes::CheckTriggers()
+{
+ // Check for endstop state changes that activate new triggers
+ const TriggerMask oldEndstopStates = lastEndstopStates;
+ lastEndstopStates = platform->GetAllEndstopStates();
+ const TriggerMask risen = lastEndstopStates & ~oldEndstopStates,
+ fallen = ~lastEndstopStates & oldEndstopStates;
+ unsigned int lowestTriggerPending = MaxTriggers;
+ for (unsigned int triggerNumber = 0; triggerNumber < MaxTriggers; ++triggerNumber)
+ {
+ const Trigger& ct = triggers[triggerNumber];
+ if ( ((ct.rising & risen) != 0 || (ct.falling & fallen) != 0)
+ && (ct.condition == 0 || (ct.condition == 1 && reprap.GetPrintMonitor()->IsPrinting()))
+ )
+ {
+ triggersPending |= (1u << triggerNumber);
+ }
+ if (triggerNumber < lowestTriggerPending && (triggersPending & (1u << triggerNumber)) != 0)
+ {
+ lowestTriggerPending = triggerNumber;
+ }
+ }
+
+ // If any triggers are pending, activate the one with the lowest number
+ if (lowestTriggerPending == 0)
+ {
+ triggersPending &= ~(1u << lowestTriggerPending); // clear the trigger
+ DoEmergencyStop();
+ }
+ else if (lowestTriggerPending < MaxTriggers // if a trigger is pending
+ && !daemonGCode->MachineState().fileState.IsLive()
+ && daemonGCode->GetState() == GCodeState::normal // and we are not already executing a trigger or config.g
+ )
+ {
+ if (lowestTriggerPending == 1)
+ {
+ if (isPaused || !reprap.GetPrintMonitor()->IsPrinting())
+ {
+ triggersPending &= ~(1u << lowestTriggerPending); // ignore a pause trigger if we are already paused
+ }
+ else if (LockMovement(*daemonGCode)) // need to lock movement before executing the pause macro
+ {
+ triggersPending &= ~(1u << lowestTriggerPending); // clear the trigger
+ DoPause(*daemonGCode);
+ }
+ }
+ else
+ {
+ triggersPending &= ~(1u << lowestTriggerPending); // clear the trigger
+ char buffer[25];
+ StringRef filename(buffer, ARRAY_SIZE(buffer));
+ filename.printf(SYS_DIR "trigger%u.g", lowestTriggerPending);
+ DoFileMacro(*daemonGCode, filename.Pointer(), true);
+ }
+ }
+}
+
+// Execute an emergency stop
+void GCodes::DoEmergencyStop()
+{
+ reprap.EmergencyStop();
+ Reset();
+ platform->Message(GENERIC_MESSAGE, "Emergency Stop! Reset the controller to continue.");
+}
+
+// Pause the print. Before calling this, check that we are doing a file print that isn't already paused and get the movement lock.
+void GCodes::DoPause(GCodeBuffer& gb)
+{
+ if (&gb == fileGCode)
+ {
+ // Pausing a file print because of a command in the file itself
+ for (size_t drive = 0; drive < numAxes; ++drive)
+ {
+ pauseRestorePoint.moveCoords[drive] = moveBuffer.coords[drive];
+ }
+ for (size_t drive = numAxes; drive < DRIVES; ++drive)
+ {
+ pauseRestorePoint.moveCoords[drive] = lastRawExtruderPosition[drive - numAxes]; // get current extruder positions into pausedMoveBuffer
+ }
+ pauseRestorePoint.feedRate = gb.MachineState().feedrate;
+ }
+ else
+ {
+ // Pausing a file print via another input source
+ pauseRestorePoint.feedRate = fileGCode->MachineState().feedrate; // the call to PausePrint may or may not change this
+ FilePosition fPos = reprap.GetMove()->PausePrint(pauseRestorePoint.moveCoords, pauseRestorePoint.feedRate, reprap.GetCurrentXAxes());
+ // tell Move we wish to pause the current print
+ FileData& fdata = fileGCode->MachineState().fileState;
+ if (fPos != noFilePosition && fdata.IsLive())
+ {
+ fdata.Seek(fPos); // replay the abandoned instructions if/when we resume
+ }
+ if (segmentsLeft != 0)
+ {
+ for (size_t drive = numAxes; drive < DRIVES; ++drive)
+ {
+ pauseRestorePoint.moveCoords[drive] += moveBuffer.coords[drive]; // add on the extrusion in the move not yet taken
+ }
+ ClearMove();
+ }
+
+ for (size_t drive = numAxes; drive < DRIVES; ++drive)
+ {
+ pauseRestorePoint.moveCoords[drive] = lastRawExtruderPosition[drive - numAxes] - pauseRestorePoint.moveCoords[drive];
+ }
+
+ //TODO record the virtual extruder positions of mixing tools too. But that's very hard to do unless we store it in the move.
+
+ if (reprap.Debug(moduleGcodes))
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Paused print, file offset=%u\n", fPos);
+ }
+ }
+
+ for (size_t i = 0; i < NUM_FANS; ++i)
+ {
+ pausedFanValues[i] = platform->GetFanValue(i);
+ }
+ gb.SetState(GCodeState::pausing1);
+ isPaused = true;
+}
+
+void GCodes::Diagnostics(MessageType mtype)
+{
+ platform->Message(mtype, "=== GCodes ===\n");
+ platform->MessageF(mtype, "Segments left: %u\n", segmentsLeft);
+ platform->MessageF(mtype, "Stack records: %u allocated, %u in use\n", GCodeMachineState::GetNumAllocated(), GCodeMachineState::GetNumInUse());
+ const GCodeBuffer *movementOwner = resourceOwners[MoveResource];
+ platform->MessageF(mtype, "Movement lock held by %s\n", (movementOwner == nullptr) ? "null" : movementOwner->GetIdentity());
+
+ for (size_t i = 0; i < ARRAY_SIZE(gcodeSources); ++i)
+ {
+ gcodeSources[i]->Diagnostics(mtype);
+ }
+}
+
+// Lock movement and wait for pending moves to finish.
+// As a side-effect it loads moveBuffer with the last position and feedrate for you.
+bool GCodes::LockMovementAndWaitForStandstill(const GCodeBuffer& gb)
+{
+ // Lock movement to stop another source adding moves to the queue
+ if (!LockMovement(gb))
+ {
+ return false;
+ }
+
+ // Last one gone?
+ if (segmentsLeft != 0)
+ {
+ return false;
+ }
+
+ // Wait for all the queued moves to stop so we get the actual last position
+ if (!reprap.GetMove()->AllMovesAreFinished())
+ {
+ return false;
+ }
+
+ // Allow movement again
+ reprap.GetMove()->ResumeMoving();
+
+ // Get the current positions. These may not be the same as the ones we remembered from last time if we just did a special move.
+ reprap.GetMove()->GetCurrentUserPosition(moveBuffer.coords, 0, reprap.GetCurrentXAxes());
+ memcpy(moveBuffer.initialCoords, moveBuffer.coords, numAxes * sizeof(moveBuffer.initialCoords[0]));
+ return true;
+}
+
+// Save (some of) the state of the machine for recovery in the future.
+bool GCodes::Push(GCodeBuffer& gb)
+{
+ bool ok = gb.PushState();
+ if (!ok)
+ {
+ platform->Message(GENERIC_MESSAGE, "Push(): stack overflow!\n");
+ }
+ return ok;
+}
+
+// Recover a saved state
+void GCodes::Pop(GCodeBuffer& gb)
+{
+ if (!gb.PopState())
+ {
+ platform->Message(GENERIC_MESSAGE, "Pop(): stack underflow!\n");
+ }
+}
+
+// Move expects all axis movements to be absolute, and all extruder drive moves to be relative. This function serves that.
+// 'moveType' is the S parameter in the G0 or G1 command, or -1 if we are doing G92.
+// For regular (type 0) moves, we apply limits and do X axis mapping.
+// Returns the number of segments if we have a legal move (or 1 if we are doing G92), or zero if this gcode should be discarded
+unsigned int GCodes::LoadMoveBufferFromGCode(GCodeBuffer& gb, int moveType)
+{
+ // Zero every extruder drive as some drives may not be changed
+ for (size_t drive = numAxes; drive < DRIVES; drive++)
+ {
+ moveBuffer.coords[drive] = 0.0;
+ }
+
+ // Deal with feed rate
+ if (gb.Seen(feedrateLetter))
+ {
+ gb.MachineState().feedrate = gb.GetFValue() * distanceScale * speedFactor;
+ }
+ moveBuffer.feedRate = gb.MachineState().feedrate;
+
+ // First do extrusion, and check, if we are extruding, that we have a tool to extrude with
+ Tool* tool = reprap.GetCurrentTool();
+ if (gb.Seen(extrudeLetter))
+ {
+ if (tool == nullptr)
+ {
+ platform->Message(GENERIC_MESSAGE, "Attempting to extrude with no tool selected.\n");
+ return 0;
+ }
+ size_t eMoveCount = tool->DriveCount();
+ if (eMoveCount > 0)
+ {
+ // Set the drive values for this tool.
+ // chrishamm-2014-10-03: Do NOT check extruder temperatures here, because we may be executing queued codes like M116
+ if (tool->GetMixing())
+ {
+ const float moveArg = gb.GetFValue() * distanceScale;
+ if (moveType == -1) // if doing G92
+ {
+ tool->virtualExtruderPosition = moveArg;
+ }
+ else
+ {
+ const float requestedExtrusionAmount = (gb.MachineState().drivesRelative)
+ ? moveArg
+ : moveArg - tool->virtualExtruderPosition;
+ for (size_t eDrive = 0; eDrive < eMoveCount; eDrive++)
+ {
+ const int drive = tool->Drive(eDrive);
+ const float extrusionAmount = requestedExtrusionAmount * tool->GetMix()[eDrive];
+ lastRawExtruderPosition[drive] += extrusionAmount;
+ rawExtruderTotalByDrive[drive] += extrusionAmount;
+ rawExtruderTotal += extrusionAmount;
+ moveBuffer.coords[drive + numAxes] = extrusionAmount * extrusionFactors[drive];
+ }
+ }
+ }
+ else
+ {
+ float eMovement[MaxExtruders];
+ size_t mc = eMoveCount;
+ gb.GetFloatArray(eMovement, mc, false);
+ if (eMoveCount != mc)
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Wrong number of extruder drives for the selected tool: %s\n", gb.Buffer());
+ return 0;
+ }
+
+ for (size_t eDrive = 0; eDrive < eMoveCount; eDrive++)
+ {
+ const int drive = tool->Drive(eDrive);
+ const float moveArg = eMovement[eDrive] * distanceScale;
+ if (moveType == -1)
+ {
+ moveBuffer.coords[drive + numAxes] = moveArg;
+ lastRawExtruderPosition[drive] = moveArg;
+ }
+ else
+ {
+ const float extrusionAmount = (gb.MachineState().drivesRelative)
+ ? moveArg
+ : moveArg - lastRawExtruderPosition[drive];
+ lastRawExtruderPosition[drive] += extrusionAmount;
+ rawExtruderTotalByDrive[drive] += extrusionAmount;
+ rawExtruderTotal += extrusionAmount;
+ moveBuffer.coords[drive + numAxes] = extrusionAmount * extrusionFactors[drive];
+ }
+ }
+ }
+ }
+ }
+
+ // Now the movement axes
+ const Tool * const currentTool = reprap.GetCurrentTool();
+ unsigned int numSegments = 1;
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ float moveArg = gb.GetFValue() * distanceScale * axisScaleFactors[axis];
+ if (moveType == -1) // if doing G92
+ {
+ SetAxisIsHomed(axis); // doing a G92 defines the absolute axis position
+ moveBuffer.coords[axis] = moveArg;
+ }
+ else if (axis == X_AXIS && moveType == 0 && currentTool != nullptr)
+ {
+ // Perform X axis mapping
+ const uint32_t xMap = currentTool->GetXAxisMap();
+ for (size_t mappedAxis = 0; mappedAxis < numAxes; ++mappedAxis)
+ {
+ if ((xMap & (1u << mappedAxis)) != 0)
+ {
+ float mappedMoveArg = moveArg;
+ if (gb.MachineState().axesRelative)
+ {
+ mappedMoveArg += moveBuffer.coords[mappedAxis];
+ }
+ else
+ {
+ mappedMoveArg -= currentTool->GetOffset()[mappedAxis]; // adjust requested position to compensate for tool offset
+ }
+ if (reprap.GetMove()->UsingHeightMap())
+ {
+ const unsigned int minSegments = reprap.GetMove()->AccessBedProbeGrid().GetMinimumSegments(fabs(mappedMoveArg - moveBuffer.coords[mappedAxis]));
+ if (minSegments > numSegments)
+ {
+ numSegments = minSegments;
+ }
+ }
+ moveBuffer.coords[mappedAxis] = mappedMoveArg;
+ }
+ }
+ }
+ else
+ {
+ if (gb.MachineState().axesRelative)
+ {
+ moveArg += moveBuffer.coords[axis];
+ }
+ else if (currentTool != nullptr && moveType == 0)
+ {
+ moveArg -= currentTool->GetOffset()[axis]; // adjust requested position to compensate for tool offset
+ }
+
+ if (axis < Z_AXIS && moveType == 0 && reprap.GetMove()->UsingHeightMap())
+ {
+ const unsigned int minSegments = reprap.GetMove()->AccessBedProbeGrid().GetMinimumSegments(fabs(moveArg - moveBuffer.coords[axis]));
+ if (minSegments > numSegments)
+ {
+ numSegments = minSegments;
+ }
+ }
+ moveBuffer.coords[axis] = moveArg;
+ }
+ }
+ }
+
+ // If doing a regular move and applying limits, limit all axes
+ if ( moveType == 0
+ && limitAxes
+#if SUPPORT_ROLAND
+ && !reprap.GetRoland()->Active()
+#endif
+ )
+ {
+ if (!reprap.GetMove()->IsDeltaMode())
+ {
+ // Cartesian or CoreXY printer, so limit those axes that have been homed
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ if (GetAxisIsHomed(axis))
+ {
+ float& f = moveBuffer.coords[axis];
+ if (f < platform->AxisMinimum(axis))
+ {
+ f = platform->AxisMinimum(axis);
+ }
+ else if (f > platform->AxisMaximum(axis))
+ {
+ f = platform->AxisMaximum(axis);
+ }
+ }
+ }
+ }
+ else if (AllAxesAreHomed()) // this is to allow extruder-only moves before homing
+ {
+ // If axes have been homed on a delta printer and this isn't a homing move, check for movements outside limits.
+ // Skip this check if axes have not been homed, so that extruder-only moved are allowed before homing
+ // Constrain the move to be within the build radius
+ const float diagonalSquared = fsquare(moveBuffer.coords[X_AXIS]) + fsquare(moveBuffer.coords[Y_AXIS]);
+ if (diagonalSquared > reprap.GetMove()->GetDeltaParams().GetPrintRadiusSquared())
+ {
+ const float factor = sqrtf(reprap.GetMove()->GetDeltaParams().GetPrintRadiusSquared() / diagonalSquared);
+ moveBuffer.coords[X_AXIS] *= factor;
+ moveBuffer.coords[Y_AXIS] *= factor;
+ }
+
+ // Constrain the end height of the move to be no greater than the homed height and no lower than -0.2mm
+ moveBuffer.coords[Z_AXIS] = max<float>(platform->AxisMinimum(Z_AXIS),
+ min<float>(moveBuffer.coords[Z_AXIS], reprap.GetMove()->GetDeltaParams().GetHomedHeight()));
+ }
+ }
+
+ return numSegments;
+}
+
+// This function is called for a G Code that makes a move.
+// If the Move class can't receive the move (i.e. things have to wait), return 0.
+// If we have queued the move and the caller doesn't need to wait for it to complete, return 1.
+// If we need to wait for the move to complete before doing another one (e.g. because endstops are checked in this move), return 2.
+int GCodes::SetUpMove(GCodeBuffer& gb, StringRef& reply)
+{
+ // Last one gone yet?
+ if (segmentsLeft != 0)
+ {
+ return 0;
+ }
+
+ // Check to see if the move is a 'homing' move that endstops are checked on.
+ moveBuffer.endStopsToCheck = 0;
+ moveBuffer.moveType = 0;
+ moveBuffer.xAxes = reprap.GetCurrentXAxes();
+ if (gb.Seen('S'))
+ {
+ int ival = gb.GetIValue();
+ if (ival == 1 || ival == 2)
+ {
+ moveBuffer.moveType = ival;
+ moveBuffer.xAxes = 0; // don't do bed compensation
+ }
+
+ if (ival == 1)
+ {
+ for (size_t i = 0; i < numAxes; ++i)
+ {
+ if (gb.Seen(axisLetters[i]))
+ {
+ moveBuffer.endStopsToCheck |= (1u << i);
+ }
+ }
+ }
+ else if (ival == 99) // temporary code to log Z probe change positions
+ {
+ moveBuffer.endStopsToCheck |= LogProbeChanges;
+ }
+ }
+
+ if (reprap.GetMove()->IsDeltaMode())
+ {
+ // Extra checks to avoid damaging delta printers
+ if (moveBuffer.moveType != 0 && !gb.MachineState().axesRelative)
+ {
+ // We have been asked to do a move without delta mapping on a delta machine, but the move is not relative.
+ // This may be damaging and is almost certainly a user mistake, so ignore the move.
+ reply.copy("Attempt to move the motors of a delta printer to absolute positions");
+ return 1;
+ }
+
+ if (moveBuffer.moveType == 0 && !AllAxesAreHomed())
+ {
+ // The user may be attempting to move a delta printer to an XYZ position before homing the axes
+ // This may be damaging and is almost certainly a user mistake, so ignore the move. But allow extruder-only moves.
+ if (gb.Seen(axisLetters[X_AXIS]) || gb.Seen(axisLetters[Y_AXIS]) || gb.Seen(axisLetters[Z_AXIS]))
+ {
+ reply.copy("Attempt to move the head of a delta printer before homing the towers");
+ return 1;
+ }
+ }
+ }
+
+ // Load the last position and feed rate into moveBuffer
+#if SUPPORT_ROLAND
+ if (reprap.GetRoland()->Active())
+ {
+ reprap.GetRoland()->GetCurrentRolandPosition(moveBuffer);
+ }
+ else
+#endif
+ {
+ reprap.GetMove()->GetCurrentUserPosition(moveBuffer.coords, moveBuffer.moveType, reprap.GetCurrentXAxes());
+ }
+
+ // Load the move buffer with either the absolute movement required or the relative movement required
+ float oldCoords[MAX_AXES];
+ memcpy(oldCoords, moveBuffer.coords, sizeof(oldCoords));
+ segmentsLeft = LoadMoveBufferFromGCode(gb, moveBuffer.moveType);
+ if (segmentsLeft != 0)
+ {
+ // Flag whether we should use pressure advance, if there is any extrusion in this move.
+ // We assume it is a normal printing move needing pressure advance if there is forward extrusion and XY movement.
+ // The movement code will only apply pressure advance if there is forward extrusion, so we only need to check for XY movement here.
+ moveBuffer.usePressureAdvance = false;
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ if (axis != Z_AXIS && moveBuffer.coords[axis] != oldCoords[axis])
+ {
+ moveBuffer.usePressureAdvance = true;
+ break;
+ }
+ }
+ moveBuffer.filePos = (&gb == fileGCode) ? filePos : noFilePosition;
+ //debugPrintf("Queue move pos %u\n", moveFilePos);
+ }
+ return (moveBuffer.moveType != 0 || moveBuffer.endStopsToCheck != 0) ? 2 : 1;
+}
+
+// The Move class calls this function to find what to do next.
+
+bool GCodes::ReadMove(RawMove& m)
+{
+ if (segmentsLeft == 0)
+ {
+ return false;
+ }
+
+ m = moveBuffer;
+ if (segmentsLeft == 1)
+ {
+ ClearMove();
+ }
+ else
+ {
+ // This move needs to be divided into 2 or more segments
+ // Do the axes
+ for (size_t drive = 0; drive < numAxes; ++drive)
+ {
+ const float movementToDo = (moveBuffer.coords[drive] - moveBuffer.initialCoords[drive])/segmentsLeft;
+ moveBuffer.initialCoords[drive] += movementToDo;
+ m.coords[drive] = moveBuffer.initialCoords[drive];
+ }
+
+ // Do the extruders
+ for (size_t drive = numAxes; drive < DRIVES; ++drive)
+ {
+ const float extrusionToDo = moveBuffer.coords[drive]/segmentsLeft;
+ m.coords[drive] = extrusionToDo;
+ moveBuffer.coords[drive] -= extrusionToDo;
+ }
+ --segmentsLeft;
+ }
+ return true;
+}
+
+void GCodes::ClearMove()
+{
+ segmentsLeft = 0;
+ moveBuffer.endStopsToCheck = 0;
+ moveBuffer.moveType = 0;
+ moveBuffer.isFirmwareRetraction = false;
+}
+
+// Run a file macro. Prior to calling this, 'state' must be set to the state we want to enter when the macro has been completed.
+// Return true if the file was found or it wasn't and we were asked to report that fact.
+bool GCodes::DoFileMacro(GCodeBuffer& gb, const char* fileName, bool reportMissing)
+{
+ FileStore * const f = platform->GetFileStore(platform->GetSysDir(), fileName, false);
+ if (f == nullptr)
+ {
+ if (reportMissing)
+ {
+ // Don't use snprintf into scratchString here, because fileName may be aliased to scratchString
+ platform->MessageF(GENERIC_MESSAGE, "Macro file %s not found.\n", fileName);
+ return true;
+ }
+ return false;
+ }
+
+ if (!Push(gb))
+ {
+ return true;
+ }
+ gb.MachineState().fileState.Set(f);
+ gb.MachineState().doingFileMacro = true;
+ gb.SetState(GCodeState::normal);
+ gb.Init();
+ return true;
+}
+
+void GCodes::FileMacroCyclesReturn(GCodeBuffer& gb)
+{
+ if (gb.MachineState().doingFileMacro)
+ {
+ gb.PopState();
+ gb.Init();
+ }
+}
+
+// To execute any move, call this until it returns true.
+// There is only one copy of the canned cycle variable so you must acquire the move lock before calling this.
+bool GCodes::DoCannedCycleMove(GCodeBuffer& gb, EndstopChecks ce)
+{
+ if (LockMovementAndWaitForStandstill(gb))
+ {
+ if (cannedCycleMoveQueued) // if the move has already been queued, it must have finished
+ {
+ Pop(gb);
+ cannedCycleMoveQueued = false;
+ return true;
+ }
+
+ // Otherwise, the move has not been queued yet
+ if (!Push(gb))
+ {
+ return true; // stack overflow
+ }
+
+ for (size_t drive = 0; drive < DRIVES; drive++)
+ {
+ switch(cannedMoveType[drive])
+ {
+ case CannedMoveType::none:
+ break;
+ case CannedMoveType::relative:
+ moveBuffer.coords[drive] += cannedMoveCoords[drive];
+ break;
+ case CannedMoveType::absolute:
+ moveBuffer.coords[drive] = cannedMoveCoords[drive];
+ break;
+ }
+ }
+ moveBuffer.feedRate = cannedFeedRate;
+ moveBuffer.xAxes = 0;
+ moveBuffer.endStopsToCheck = ce;
+ moveBuffer.filePos = noFilePosition;
+ moveBuffer.usePressureAdvance = false;
+ segmentsLeft = 1;
+ cannedCycleMoveQueued = true;
+ }
+ return false;
+}
+
+// This handles G92
+bool GCodes::SetPositions(GCodeBuffer& gb)
+{
+ // Don't pause the machine if only extruder drives are being reset (DC, 2015-09-06).
+ // This avoids blobs and seams when the gcode uses absolute E coordinates and periodically includes G92 E0.
+ bool includingAxes = false;
+ for (size_t drive = 0; drive < numAxes; ++drive)
+ {
+ if (gb.Seen(axisLetters[drive]))
+ {
+ includingAxes = true;
+ break;
+ }
+ }
+
+ if (includingAxes)
+ {
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ }
+ else if (segmentsLeft != 0) // wait for previous move to be taken so that GetCurrentUserPosition returns the correct value
+ {
+ return false;
+ }
+
+ reprap.GetMove()->GetCurrentUserPosition(moveBuffer.coords, 0, reprap.GetCurrentXAxes()); // make sure move buffer is up to date
+ bool ok = LoadMoveBufferFromGCode(gb, -1);
+ if (ok && includingAxes)
+ {
+#if SUPPORT_ROLAND
+ if (reprap.GetRoland()->Active())
+ {
+ for(size_t axis = 0; axis < AXES; axis++)
+ {
+ if (!reprap.GetRoland()->ProcessG92(moveBuffer[axis], axis))
+ {
+ return false;
+ }
+ }
+ }
+#endif
+ SetPositions(moveBuffer.coords);
+ }
+
+ return true;
+}
+
+// Offset the axes by the X, Y, and Z amounts in the M code in gb. Say the machine is at [10, 20, 30] and
+// the offsets specified are [8, 2, -5]. The machine will move to [18, 22, 25] and henceforth consider that point
+// to be [10, 20, 30].
+bool GCodes::OffsetAxes(GCodeBuffer& gb)
+{
+ if (!offSetSet)
+ {
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ for (size_t drive = 0; drive < DRIVES; drive++)
+ {
+ cannedMoveType[drive] = CannedMoveType::none;
+ if (drive < numAxes)
+ {
+ record[drive] = moveBuffer.coords[drive];
+ if (gb.Seen(axisLetters[drive]))
+ {
+ cannedMoveCoords[drive] = gb.GetFValue();
+ cannedMoveType[drive] = CannedMoveType::relative;
+ }
+ }
+ else
+ {
+ record[drive] = 0.0;
+ }
+ }
+
+ if (gb.Seen(feedrateLetter)) // Has the user specified a feedrate?
+ {
+ cannedFeedRate = gb.GetFValue() * distanceScale * SECONDS_TO_MINUTES;
+ }
+ else
+ {
+ cannedFeedRate = DEFAULT_FEEDRATE;
+ }
+
+ offSetSet = true;
+ }
+
+ if (DoCannedCycleMove(gb, 0))
+ {
+ // Restore positions
+ for (size_t drive = 0; drive < DRIVES; drive++)
+ {
+ moveBuffer.coords[drive] = record[drive];
+ }
+ reprap.GetMove()->SetLiveCoordinates(record); // This doesn't transform record
+ reprap.GetMove()->SetPositions(record); // This does
+ offSetSet = false;
+ return true;
+ }
+
+ return false;
+}
+
+// Home one or more of the axes. Which ones are decided by the
+// booleans homeX, homeY and homeZ.
+// Returns true if completed, false if needs to be called again.
+// 'reply' is only written if there is an error.
+// 'error' is false on entry, gets changed to true if there is an error.
+bool GCodes::DoHome(GCodeBuffer& gb, StringRef& reply, bool& error)
+{
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+
+#if SUPPORT_ROLAND
+ // Deal with a Roland configuration
+ if (reprap.GetRoland()->Active())
+ {
+ bool rolHome = reprap.GetRoland()->ProcessHome();
+ if (rolHome)
+ {
+ for(size_t axis = 0; axis < AXES; axis++)
+ {
+ axisIsHomed[axis] = true;
+ }
+ }
+ return rolHome;
+ }
+#endif
+
+ if (reprap.GetMove()->IsDeltaMode())
+ {
+ SetAllAxesNotHomed();
+ DoFileMacro(gb, HOME_DELTA_G);
+ }
+ else
+ {
+ toBeHomed = 0;
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ toBeHomed |= (1u << axis);
+ SetAxisNotHomed(axis);
+ }
+ }
+
+ if (toBeHomed == 0 || toBeHomed == ((1u << numAxes) - 1))
+ {
+ // Homing everything
+ SetAllAxesNotHomed();
+ DoFileMacro(gb, HOME_ALL_G);
+ }
+ else if ( platform->MustHomeXYBeforeZ()
+ && ((toBeHomed & (1u << Z_AXIS)) != 0)
+ && ((toBeHomed | axesHomed | (1u << Z_AXIS)) != ((1u << numAxes) - 1))
+ )
+ {
+ // We can only home Z if both X and Y have already been homed or are being homed
+ reply.copy("Must home all other axes before homing Z");
+ error = true;
+ }
+ else
+ {
+ gb.SetState(GCodeState::homing);
+ }
+ }
+ return true;
+}
+
+// This lifts Z a bit, moves to the probe XY coordinates (obtained by a call to GetProbeCoordinates() ),
+// probes the bed height, and records the Z coordinate probed. If you want to program any general
+// internal canned cycle, this shows how to do it.
+// On entry, probePointIndex specifies which of the points this is.
+bool GCodes::DoSingleZProbeAtPoint(GCodeBuffer& gb, int probePointIndex, float heightAdjust)
+{
+ reprap.GetMove()->SetIdentityTransform(); // It doesn't matter if these are called repeatedly
+
+ for (size_t drive = 0; drive < DRIVES; drive++)
+ {
+ cannedMoveType[drive] = CannedMoveType::none;
+ }
+
+ switch (cannedCycleMoveCount)
+ {
+ case 0: // Move Z to the dive height. This only does anything on the first move; on all the others Z is already there
+ cannedMoveCoords[Z_AXIS] = platform->GetZProbeDiveHeight() + max<float>(platform->ZProbeStopHeight(), 0.0);
+ cannedMoveType[Z_AXIS] = CannedMoveType::absolute;
+ cannedFeedRate = platform->GetZProbeTravelSpeed();
+ if (DoCannedCycleMove(gb, 0))
+ {
+ cannedCycleMoveCount++;
+ }
+ return false;
+
+ case 1: // Move to the correct XY coordinates
+ GetProbeCoordinates(probePointIndex, cannedMoveCoords[X_AXIS], cannedMoveCoords[Y_AXIS], cannedMoveCoords[Z_AXIS]);
+ cannedMoveType[X_AXIS] = CannedMoveType::absolute;
+ cannedMoveType[Y_AXIS] = CannedMoveType::absolute;
+ // NB - we don't use the Z value
+ cannedFeedRate = platform->GetZProbeTravelSpeed();
+ if (DoCannedCycleMove(gb, 0))
+ {
+ cannedCycleMoveCount++;
+ }
+ return false;
+
+ case 2: // Probe the bed
+ if (millis() - lastProbedTime >= (uint32_t)(platform->GetZProbeParameters().recoveryTime * SecondsToMillis))
+ {
+ const float height = (GetAxisIsHomed(Z_AXIS))
+ ? 2 * platform->GetZProbeDiveHeight() // Z axis has been homed, so no point in going very far
+ : 1.1 * platform->AxisTotalLength(Z_AXIS); // Z axis not homed yet, so treat this as a homing move
+ switch(DoZProbe(gb, height))
+ {
+ case 0:
+ // Z probe is already triggered at the start of the move, so abandon the probe and record an error
+ platform->Message(GENERIC_MESSAGE, "Error: Z probe already triggered at start of probing move\n");
+ cannedCycleMoveCount++;
+ reprap.GetMove()->SetZBedProbePoint(probePointIndex, platform->GetZProbeDiveHeight(), true, true);
+ break;
+
+ case 1:
+ // Z probe did not trigger
+ platform->Message(GENERIC_MESSAGE, "Error: Z probe was not triggered during probing move\n");
+ cannedCycleMoveCount++;
+ reprap.GetMove()->SetZBedProbePoint(probePointIndex, -(platform->GetZProbeDiveHeight()), true, true);
+ break;
+
+ case 2:
+ // Successful probing
+ lastProbedTime = millis();
+ if (GetAxisIsHomed(Z_AXIS))
+ {
+ lastProbedZ = moveBuffer.coords[Z_AXIS] - (platform->ZProbeStopHeight() + heightAdjust);
+ }
+ else
+ {
+ // The Z axis has not yet been homed, so treat this probe as a homing move.
+ moveBuffer.coords[Z_AXIS] = platform->ZProbeStopHeight() + heightAdjust;
+ SetPositions(moveBuffer.coords);
+ SetAxisIsHomed(Z_AXIS);
+ lastProbedZ = 0.0;
+ }
+ reprap.GetMove()->SetZBedProbePoint(probePointIndex, lastProbedZ, true, false);
+ cannedCycleMoveCount++;
+ break;
+
+ default:
+ break;
+ }
+ }
+ return false;
+
+ case 3: // Raise the head back up to the dive height
+ cannedMoveCoords[Z_AXIS] = platform->GetZProbeDiveHeight() + max<float>(platform->ZProbeStopHeight(), 0.0);
+ cannedMoveType[Z_AXIS] = CannedMoveType::absolute;
+ cannedFeedRate = platform->GetZProbeTravelSpeed();
+ if (DoCannedCycleMove(gb, 0))
+ {
+ cannedCycleMoveCount = 0;
+ return true;
+ }
+ return false;
+
+ default: // should not happen
+ cannedCycleMoveCount = 0;
+ return true;
+ }
+}
+
+// This simply moves down till the Z probe/switch is triggered. Call it repeatedly until it returns true.
+// Called when we do a G30 with no P parameter.
+bool GCodes::DoSingleZProbe(GCodeBuffer& gb, bool reportOnly, float heightAdjust)
+{
+ switch (DoZProbe(gb, 1.1 * platform->AxisTotalLength(Z_AXIS)))
+ {
+ case 0: // failed
+ platform->Message(GENERIC_MESSAGE, "Error: Z probe already triggered at start of probing move\n");
+ return true;
+
+ case 1:
+ platform->Message(GENERIC_MESSAGE, "Error: Z probe was not triggered during probing move\n");
+ return true;
+
+ case 2: // success
+ if (!reportOnly)
+ {
+ moveBuffer.coords[Z_AXIS] = platform->ZProbeStopHeight() + heightAdjust;
+ SetPositions(moveBuffer.coords);
+ SetAxisIsHomed(Z_AXIS);
+ lastProbedZ = 0.0;
+ }
+ return true;
+
+ default: // not finished yet
+ return false;
+ }
+}
+
+// Do a Z probe cycle up to the maximum specified distance.
+// Returns -1 if not complete yet
+// Returns 0 if Z probe already triggered at start of probing
+// Returns 1 if Z probe didn't trigger
+// Returns 2 if success, with the current position in moveBuffer
+int GCodes::DoZProbe(GCodeBuffer& gb, float distance)
+{
+ if (platform->GetZProbeType() == ZProbeTypeDelta)
+ {
+ const ZProbeParameters& params = platform->GetZProbeParameters();
+ return reprap.GetMove()->DoDeltaProbe(params.extraParam, 1.0, params.probeSpeed, distance); //TODO second parameter
+ }
+ else
+ {
+ // Check for probe already triggered at start
+ if (!cannedCycleMoveQueued)
+ {
+ if (reprap.GetPlatform()->GetZProbeResult() == EndStopHit::lowHit)
+ {
+ return 0;
+ }
+ zProbeTriggered = false;
+ }
+
+ // Do a normal canned cycle Z movement with Z probe enabled
+ for (size_t drive = 0; drive < DRIVES; drive++)
+ {
+ cannedMoveType[drive] = CannedMoveType::none;
+ }
+
+ cannedMoveCoords[Z_AXIS] = -distance;
+ cannedMoveType[Z_AXIS] = CannedMoveType::relative;
+ cannedFeedRate = platform->GetZProbeParameters().probeSpeed;
+
+ if (DoCannedCycleMove(gb, ZProbeActive))
+ {
+ return (zProbeTriggered) ? 2 : 1;
+ }
+ return -1;
+ }
+}
+
+// This is called to execute a G30.
+// It sets wherever we are as the probe point P (probePointIndex)
+// then probes the bed, or gets all its parameters from the arguments.
+// If X or Y are specified, use those; otherwise use the machine's
+// coordinates. If no Z is specified use the machine's coordinates. If it
+// is specified and is greater than SILLY_Z_VALUE (i.e. greater than -9999.0)
+// then that value is used. If it's less than SILLY_Z_VALUE the bed is
+// probed and that value is used.
+// Call this repeatedly until it returns true.
+bool GCodes::SetSingleZProbeAtAPosition(GCodeBuffer& gb, StringRef& reply)
+{
+ if (reprap.GetMove()->IsDeltaMode() && !AllAxesAreHomed())
+ {
+ reply.copy("Must home before bed probing");
+ return true;
+ }
+
+ float heightAdjust = 0.0;
+ bool dummy;
+ gb.TryGetFValue('H', heightAdjust, dummy);
+
+ if (!gb.Seen('P'))
+ {
+ bool reportOnly = false;
+ if (gb.Seen('S') && gb.GetIValue() < 0)
+ {
+ reportOnly = true;
+ }
+ return DoSingleZProbe(gb, reportOnly, heightAdjust);
+ }
+
+ int probePointIndex = gb.GetIValue();
+ if (probePointIndex < 0 || (unsigned int)probePointIndex >= MaxProbePoints)
+ {
+ reprap.GetPlatform()->Message(GENERIC_MESSAGE, "Z probe point index out of range.\n");
+ return true;
+ }
+
+ float x = (gb.Seen(axisLetters[X_AXIS])) ? gb.GetFValue() : moveBuffer.coords[X_AXIS];
+ float y = (gb.Seen(axisLetters[Y_AXIS])) ? gb.GetFValue() : moveBuffer.coords[Y_AXIS];
+ float z = (gb.Seen(axisLetters[Z_AXIS])) ? gb.GetFValue() : moveBuffer.coords[Z_AXIS];
+
+ reprap.GetMove()->SetXBedProbePoint(probePointIndex, x);
+ reprap.GetMove()->SetYBedProbePoint(probePointIndex, y);
+
+ if (z > SILLY_Z_VALUE)
+ {
+ reprap.GetMove()->SetZBedProbePoint(probePointIndex, z, false, false);
+ if (gb.Seen('S'))
+ {
+ zProbesSet = true;
+ reprap.GetMove()->FinishedBedProbing(gb.GetIValue(), reply);
+ }
+ return true;
+ }
+ else
+ {
+ if (DoSingleZProbeAtPoint(gb, probePointIndex, heightAdjust))
+ {
+ if (gb.Seen('S'))
+ {
+ zProbesSet = true;
+ int sParam = gb.GetIValue();
+ if (sParam == 1)
+ {
+ // G30 with a silly Z value and S=1 is equivalent to G30 with no parameters in that it sets the current Z height
+ // This is useful because it adjusts the XY position to account for the probe offset.
+ moveBuffer.coords[Z_AXIS] += lastProbedZ;
+ SetPositions(moveBuffer.coords);
+ lastProbedZ = 0.0;
+ }
+ else
+ {
+ reprap.GetMove()->FinishedBedProbing(sParam, reply);
+ }
+ }
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// This returns the (X, Y) points to probe the bed at probe point count. When probing, it returns false.
+// If called after probing has ended it returns true, and the Z coordinate probed is also returned.
+bool GCodes::GetProbeCoordinates(int count, float& x, float& y, float& z) const
+{
+ const ZProbeParameters& rp = platform->GetZProbeParameters();
+ x = reprap.GetMove()->XBedProbePoint(count) - rp.xOffset;
+ y = reprap.GetMove()->YBedProbePoint(count) - rp.yOffset;
+ z = reprap.GetMove()->ZBedProbePoint(count);
+ return zProbesSet;
+}
+
+bool GCodes::SetPrintZProbe(GCodeBuffer& gb, StringRef& reply)
+{
+ ZProbeParameters params = platform->GetZProbeParameters();
+ bool seen = false;
+ gb.TryGetFValue(axisLetters[X_AXIS], params.xOffset, seen);
+ gb.TryGetFValue(axisLetters[Y_AXIS], params.yOffset, seen);
+ gb.TryGetFValue(axisLetters[Z_AXIS], params.height, seen);
+ gb.TryGetIValue('P', params.adcValue, seen);
+
+ if (gb.Seen('C'))
+ {
+ params.temperatureCoefficient = gb.GetFValue();
+ seen = true;
+ if (gb.Seen('S'))
+ {
+ params.calibTemperature = gb.GetFValue();
+ }
+ else
+ {
+ // Use the current bed temperature as the calibration temperature if no value was provided
+ params.calibTemperature = platform->GetZProbeTemperature();
+ }
+ }
+
+ if (seen)
+ {
+ platform->SetZProbeParameters(params);
+ }
+ else
+ {
+ const int v0 = platform->ZProbe();
+ int v1, v2;
+ switch (platform->GetZProbeSecondaryValues(v1, v2))
+ {
+ case 1:
+ reply.printf("%d (%d)", v0, v1);
+ break;
+ case 2:
+ reply.printf("%d (%d, %d)", v0, v1, v2);
+ break;
+ default:
+ reply.printf("%d", v0);
+ break;
+ }
+ }
+ return true;
+}
+
+// Define the probing grid, returning true if error
+// Called when we see an M557 command with no P parameter
+bool GCodes::DefineGrid(GCodeBuffer& gb, StringRef &reply)
+{
+ reprap.GetMove()->UseHeightMap(false);
+
+ bool seenX = false, seenY = false, seenR = false, seenS = false;
+ float xValues[2];
+ float yValues[2];
+
+ if (gb.Seen('X'))
+ {
+ size_t count = 2;
+ gb.GetFloatArray(xValues, count, false);
+ if (count == 2)
+ {
+ seenX = true;
+ }
+ else
+ {
+ reply.copy("ERROR: Wrong number of X values in M577, need 2");
+ return true;
+ }
+ }
+ if (gb.Seen('Y'))
+ {
+ size_t count = 2;
+ gb.GetFloatArray(yValues, count, false);
+ if (count == 2)
+ {
+ seenY = true;
+ }
+ else
+ {
+ reply.copy("ERROR: Wrong number of Y values in M577, need 2");
+ return true;
+ }
+ }
+
+ float radius = -1.0;
+ gb.TryGetFValue('R', radius, seenR);
+ float spacing = DefaultGridSpacing;
+ gb.TryGetFValue('S', spacing, seenS);
+
+ if (!seenX && !seenY && !seenR && !seenS)
+ {
+ // Just print the existing grid parameters
+ const GridDefinition& grid = reprap.GetMove()->AccessBedProbeGrid().GetGrid();
+ if (grid.IsValid())
+ {
+ reply.copy("Grid: ");
+ grid.PrintParameters(reply);
+ }
+ else
+ {
+ reply.copy("Grid is not defined");
+ }
+ return false;
+ }
+
+ if (seenX != seenY)
+ {
+ reply.copy("ERROR: specify both or neither of X and Y in M577");
+ return true;
+ }
+
+ if (!seenX && !seenR)
+ {
+ // Must have given just the S parameter
+ reply.copy("ERROR: specify at least radius or X,Y ranges in M577");
+ return true;
+
+ }
+
+ if (!seenX)
+ {
+ if (radius > 0)
+ {
+ const float effectiveRadius = floor((radius - 0.1)/spacing) * spacing;
+ xValues[0] = yValues[0] = -effectiveRadius;
+ xValues[1] = yValues[1] = effectiveRadius + 0.1;
+ }
+ else
+ {
+ reply.copy("ERROR: M577 radius must be positive unless X and Y are specified");
+ return true;
+ }
+ }
+ GridDefinition newGrid(xValues, yValues, radius, spacing); // create a new grid
+ if (newGrid.IsValid())
+ {
+ reprap.GetMove()->AccessBedProbeGrid().SetGrid(newGrid);
+ return false;
+ }
+ else
+ {
+ reply.copy("ERROR: bad grid definition: ");
+ newGrid.PrintError(reply);
+ return true;
+ }
+}
+
+// Start probing the grid, returning true if we didn't because of an error.
+// Prior to calling this the movement system must be locked.
+bool GCodes::ProbeGrid(GCodeBuffer& gb, StringRef& reply)
+{
+ int32_t sParam = 0;
+ bool dummy;
+ gb.TryGetIValue('S', sParam, dummy);
+
+ if (gb.Seen('P'))
+ {
+ heightMapFile = gb.GetString();
+ }
+ else
+ {
+ heightMapFile = DefaultHeightMapFile;
+ }
+
+ Move * const move = reprap.GetMove();
+ switch(sParam)
+ {
+ case 0: // Probe the bed and save to file
+ if (!move->AccessBedProbeGrid().GetGrid().IsValid())
+ {
+ reply.copy("No valid grid defined for G29 bed probing");
+ return true;
+ }
+
+ if (!AllAxesAreHomed())
+ {
+ reply.copy("Must home printer before G29 bed probing");
+ return true;
+ }
+
+ gridXindex = gridYindex = 0;
+ numPointsProbed = 0;
+ heightSum = heightSquaredSum = 0.0;
+
+ move->AccessBedProbeGrid().ClearGridHeights();
+ move->UseHeightMap(false);
+ move->SetIdentityTransform();
+ gb.SetState(GCodeState::gridProbing1);
+ return false;
+
+ case 1: // Load height map from file
+ {
+ const char* locHeightMapFileName;
+ if (gb.Seen('P'))
+ {
+ locHeightMapFileName = gb.GetString();
+ }
+ else
+ {
+ locHeightMapFileName = DefaultHeightMapFile;
+ }
+ FileStore * const f = platform->GetFileStore(platform->GetSysDir(), locHeightMapFileName, false);
+
+ if (f == nullptr)
+ {
+ reply.printf("Height map file %s not found", locHeightMapFileName);
+ return true;
+ }
+
+ reply.printf("Failed to load height map from file %s: ", heightMapFile); // set up error message to append to
+ const bool err = move->AccessBedProbeGrid().LoadFromFile(f, reply);
+ f->Close();
+ if (err)
+ {
+ move->AccessBedProbeGrid().ClearGridHeights(); // make sure we don't end up with a partial height map
+ }
+ else
+ {
+ reply.Clear(); // wipe the error message
+ }
+
+ move->UseHeightMap(!err);
+ return err;
+ }
+
+ case 2: // Clear height map
+ move->AccessBedProbeGrid().ClearGridHeights();
+ move->UseHeightMap(false);
+ return false;
+
+ default:
+ reply.copy("Invalid S parameter in G29 command");
+ return true;
+ }
+}
+
+// Save the height map and write the success or error message to 'reply', returning true if an error occurred
+bool GCodes::SaveHeightMapToFile(StringRef& reply) const
+{
+ Platform *platform = reprap.GetPlatform();
+ FileStore * const f = platform->GetFileStore(platform->GetSysDir(), heightMapFile, true);
+ bool err;
+ if (f == nullptr)
+ {
+ reply.printf("Failed to create height map file %s", heightMapFile);
+ err = true;
+ }
+ else
+ {
+ err = reprap.GetMove()->AccessBedProbeGrid().SaveToFile(f);
+ f->Close();
+ if (err)
+ {
+ platform->GetMassStorage()->Delete(platform->GetSysDir(), heightMapFile);
+ reply.printf("Failed to save height map to file %s", heightMapFile);
+ }
+ else
+ {
+ reply.printf("Height map saved to file %s", heightMapFile);
+ }
+ }
+ return err;
+}
+
+// Return the current coordinates as a printable string.
+// Coordinates are updated at the end of each movement, so this won't tell you where you are mid-movement.
+void GCodes::GetCurrentCoordinates(StringRef& s) const
+{
+ float liveCoordinates[DRIVES];
+ reprap.GetMove()->LiveCoordinates(liveCoordinates, reprap.GetCurrentXAxes());
+ const Tool *currentTool = reprap.GetCurrentTool();
+ if (currentTool != nullptr)
+ {
+ const float *offset = currentTool->GetOffset();
+ for (size_t i = 0; i < numAxes; ++i)
+ {
+ liveCoordinates[i] += offset[i];
+ }
+ }
+
+ s.Clear();
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ s.catf("%c: %.2f ", axisLetters[axis], liveCoordinates[axis]);
+ }
+ for (size_t i = numAxes; i < DRIVES; i++)
+ {
+ s.catf("E%u: %.1f ", i - numAxes, liveCoordinates[i]);
+ }
+
+ // Print the axis stepper motor positions as Marlin does, as an aid to debugging.
+ // Don't bother with the extruder endpoints, they are zero after any non-extruding move.
+ s.cat(" Count");
+ for (size_t i = 0; i < numAxes; ++i)
+ {
+ s.catf(" %d", reprap.GetMove()->GetEndPoint(i));
+ }
+}
+
+bool GCodes::OpenFileToWrite(GCodeBuffer& gb, const char* directory, const char* fileName)
+{
+ fileBeingWritten = platform->GetFileStore(directory, fileName, true);
+ eofStringCounter = 0;
+ if (fileBeingWritten == NULL)
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Can't open GCode file \"%s\" for writing.\n", fileName);
+ return false;
+ }
+ else
+ {
+ gb.SetWritingFileDirectory(directory);
+ return true;
+ }
+}
+
+void GCodes::WriteHTMLToFile(GCodeBuffer& gb, char b)
+{
+ if (fileBeingWritten == NULL)
+ {
+ platform->Message(GENERIC_MESSAGE, "Attempt to write to a null file.\n");
+ return;
+ }
+
+ if (eofStringCounter != 0 && b != eofString[eofStringCounter])
+ {
+ fileBeingWritten->Write(eofString);
+ eofStringCounter = 0;
+ }
+
+ if (b == eofString[eofStringCounter])
+ {
+ eofStringCounter++;
+ if (eofStringCounter >= eofStringLength)
+ {
+ fileBeingWritten->Close();
+ fileBeingWritten = NULL;
+ gb.SetWritingFileDirectory(NULL);
+ const char* r = (platform->Emulating() == marlin) ? "Done saving file." : "";
+ HandleReply(gb, false, r);
+ return;
+ }
+ }
+ else
+ {
+ fileBeingWritten->Write(b);
+ }
+}
+
+void GCodes::WriteGCodeToFile(GCodeBuffer& gb)
+{
+ if (fileBeingWritten == NULL)
+ {
+ platform->Message(GENERIC_MESSAGE, "Attempt to write to a null file.\n");
+ return;
+ }
+
+ // End of file?
+ if (gb.Seen('M'))
+ {
+ if (gb.GetIValue() == 29)
+ {
+ fileBeingWritten->Close();
+ fileBeingWritten = NULL;
+ gb.SetWritingFileDirectory(NULL);
+ const char* r = (platform->Emulating() == marlin) ? "Done saving file." : "";
+ HandleReply(gb, false, r);
+ return;
+ }
+ }
+
+ // Resend request?
+ if (gb.Seen('G'))
+ {
+ if (gb.GetIValue() == 998)
+ {
+ if (gb.Seen('P'))
+ {
+ scratchString.printf("%d\n", gb.GetIValue());
+ HandleReply(gb, false, scratchString.Pointer());
+ return;
+ }
+ }
+ }
+
+ fileBeingWritten->Write(gb.Buffer());
+ fileBeingWritten->Write('\n');
+ HandleReply(gb, false, "");
+}
+
+// Set up a file to print, but don't print it yet.
+void GCodes::QueueFileToPrint(const char* fileName)
+{
+ FileStore * const f = platform->GetFileStore(platform->GetGCodeDir(), fileName, false);
+ if (f != nullptr)
+ {
+ // Cancel current print if there is any
+ if (!reprap.GetPrintMonitor()->IsPrinting())
+ {
+ CancelPrint();
+ }
+
+ fileGCode->SetToolNumberAdjust(0); // clear tool number adjustment
+
+ // Reset all extruder positions when starting a new print
+ for (size_t extruder = 0; extruder < MaxExtruders; extruder++)
+ {
+ lastRawExtruderPosition[extruder] = 0.0;
+ rawExtruderTotalByDrive[extruder] = 0.0;
+ }
+ rawExtruderTotal = 0.0;
+ reprap.GetMove()->ResetExtruderPositions();
+
+ fileToPrint.Set(f);
+ }
+ else
+ {
+ platform->MessageF(GENERIC_MESSAGE, "GCode file \"%s\" not found\n", fileName);
+ }
+}
+
+void GCodes::DeleteFile(const char* fileName)
+{
+ if (!platform->GetMassStorage()->Delete(platform->GetGCodeDir(), fileName))
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Could not delete file \"%s\"\n", fileName);
+ }
+}
+
+// Function to handle dwell delays. Return true for dwell finished, false otherwise.
+bool GCodes::DoDwell(GCodeBuffer& gb)
+{
+ float dwell;
+ if (gb.Seen('S'))
+ {
+ dwell = gb.GetFValue();
+ }
+ else if (gb.Seen('P'))
+ {
+ dwell = 0.001 * (float) gb.GetIValue(); // P values are in milliseconds; we need seconds
+ }
+ else
+ {
+ return true; // No time given - throw it away
+ }
+
+#if SUPPORT_ROLAND
+ // Deal with a Roland configuration
+ if (reprap.GetRoland()->Active())
+ {
+ return reprap.GetRoland()->ProcessDwell(gb.GetLValue());
+ }
+#endif
+
+ // Wait for all the queued moves to stop
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+
+ if (simulationMode != 0)
+ {
+ simulationTime += dwell;
+ return true;
+ }
+ else
+ {
+ return DoDwellTime(dwell);
+ }
+}
+
+bool GCodes::DoDwellTime(float dwell)
+{
+ // Are we already in the dwell?
+ if (dwellWaiting)
+ {
+ if (platform->Time() - dwellTime >= 0.0)
+ {
+ dwellWaiting = false;
+ reprap.GetMove()->ResumeMoving();
+ return true;
+ }
+ return false;
+ }
+
+ // New dwell - set it up
+ dwellWaiting = true;
+ dwellTime = platform->Time() + dwell;
+ return false;
+}
+
+// Set offset, working and standby temperatures for a tool. I.e. handle a G10.
+bool GCodes::SetOrReportOffsets(GCodeBuffer &gb, StringRef& reply)
+{
+ if (gb.Seen('P'))
+ {
+ int8_t toolNumber = gb.GetIValue();
+ toolNumber += gb.GetToolNumberAdjust();
+ Tool* tool = reprap.GetTool(toolNumber);
+ if (tool == NULL)
+ {
+ reply.printf("Attempt to set/report offsets and temperatures for non-existent tool: %d", toolNumber);
+ return true;
+ }
+
+ // Deal with setting offsets
+ float offset[MAX_AXES];
+ for (size_t i = 0; i < MAX_AXES; ++i)
+ {
+ offset[i] = tool->GetOffset()[i];
+ }
+
+ bool settingOffset = false;
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ gb.TryGetFValue(axisLetters[axis], offset[axis], settingOffset);
+ }
+ if (settingOffset)
+ {
+ if (!LockMovement(gb))
+ {
+ return false;
+ }
+ tool->SetOffset(offset);
+ }
+
+ // Deal with setting temperatures
+ bool settingTemps = false;
+ size_t hCount = tool->HeaterCount();
+ float standby[HEATERS];
+ float active[HEATERS];
+ if (hCount > 0)
+ {
+ tool->GetVariables(standby, active);
+ if (gb.Seen('R'))
+ {
+ gb.GetFloatArray(standby, hCount, true);
+ settingTemps = true;
+ }
+ if (gb.Seen('S'))
+ {
+ gb.GetFloatArray(active, hCount, true);
+ settingTemps = true;
+ }
+
+ if (settingTemps && simulationMode == 0)
+ {
+ tool->SetVariables(standby, active);
+ }
+ }
+
+ if (!settingOffset && !settingTemps)
+ {
+ // Print offsets and temperatures
+ reply.printf("Tool %d offsets:", toolNumber);
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ reply.catf(" %c%.2f", axisLetters[axis], offset[axis]);
+ }
+ if (hCount != 0)
+ {
+ reply.cat(", active/standby temperature(s):");
+ for (size_t heater = 0; heater < hCount; heater++)
+ {
+ reply.catf(" %.1f/%.1f", active[heater], standby[heater]);
+ }
+ }
+ }
+ }
+ return true;
+}
+
+void GCodes::ManageTool(GCodeBuffer& gb, StringRef& reply)
+{
+ if (!gb.Seen('P'))
+ {
+ // DC temporary code to allow tool numbers to be adjusted so that we don't need to edit multi-media files generated by slic3r
+ if (gb.Seen('S'))
+ {
+ int adjust = gb.GetIValue();
+ gb.SetToolNumberAdjust(adjust);
+ }
+ return;
+ }
+
+ // Check tool number
+ bool seen = false;
+ const int toolNumber = gb.GetIValue();
+ if (toolNumber < 0)
+ {
+ platform->Message(GENERIC_MESSAGE, "Tool number must be positive!\n");
+ return;
+ }
+
+ // Check drives
+ long drives[MaxExtruders]; // There can never be more than we have...
+ size_t dCount = numExtruders; // Sets the limit and returns the count
+ if (gb.Seen('D'))
+ {
+ gb.GetLongArray(drives, dCount);
+ seen = true;
+ }
+ else
+ {
+ dCount = 0;
+ }
+
+ // Check heaters
+ long heaters[HEATERS];
+ size_t hCount = HEATERS;
+ if (gb.Seen('H'))
+ {
+ gb.GetLongArray(heaters, hCount);
+ seen = true;
+ }
+ else
+ {
+ hCount = 0;
+ }
+
+ // Check X axis mapping
+ uint32_t xMap;
+ if (gb.Seen('X'))
+ {
+ long xMapping[MAX_AXES];
+ size_t xCount = numAxes;
+ gb.GetLongArray(xMapping, xCount);
+ xMap = LongArrayToBitMap(xMapping, xCount) & ((1u << numAxes) - 1);
+ seen = true;
+ }
+ else
+ {
+ xMap = 1; // by default map X axis straight through
+ }
+
+ // Check for fan mapping
+ uint32_t fanMap;
+ if (gb.Seen('F'))
+ {
+ long fanMapping[NUM_FANS];
+ size_t fanCount = NUM_FANS;
+ gb.GetLongArray(fanMapping, fanCount);
+ fanMap = LongArrayToBitMap(fanMapping, fanCount) & ((1u << NUM_FANS) - 1);
+ seen = true;
+ }
+ else
+ {
+ fanMap = 1; // by default map fan 0 to fan 0
+ }
+
+ if (seen)
+ {
+ // Add or delete tool, so start by deleting the old one with this number, if any
+ reprap.DeleteTool(reprap.GetTool(toolNumber));
+
+ // M563 P# D-1 H-1 removes an existing tool
+ if (dCount == 1 && hCount == 1 && drives[0] == -1 && heaters[0] == -1)
+ {
+ // nothing more to do
+ }
+ else
+ {
+ Tool* tool = Tool::Create(toolNumber, drives, dCount, heaters, hCount, xMap, fanMap);
+ if (tool != nullptr)
+ {
+ reprap.AddTool(tool);
+ }
+ }
+ }
+ else
+ {
+ reprap.PrintTool(toolNumber, reply);
+ }
+}
+
+// Does what it says.
+void GCodes::DisableDrives()
+{
+ for (size_t drive = 0; drive < DRIVES; drive++)
+ {
+ platform->DisableDrive(drive);
+ }
+ SetAllAxesNotHomed();
+}
+
+// Does what it says.
+void GCodes::SetEthernetAddress(GCodeBuffer& gb, int mCode)
+{
+ byte eth[4];
+ const char* ipString = gb.GetString();
+ uint8_t sp = 0;
+ uint8_t spp = 0;
+ uint8_t ipp = 0;
+ while (ipString[sp])
+ {
+ if (ipString[sp] == '.')
+ {
+ eth[ipp] = atoi(&ipString[spp]);
+ ipp++;
+ if (ipp > 3)
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Dud IP address: %s\n", gb.Buffer());
+ return;
+ }
+ sp++;
+ spp = sp;
+ }
+ else
+ {
+ sp++;
+ }
+ }
+ eth[ipp] = atoi(&ipString[spp]);
+ if (ipp == 3)
+ {
+ switch (mCode)
+ {
+ case 552:
+ platform->SetIPAddress(eth);
+ break;
+ case 553:
+ platform->SetNetMask(eth);
+ break;
+ case 554:
+ platform->SetGateWay(eth);
+ break;
+
+ default:
+ platform->Message(GENERIC_MESSAGE, "Setting ether parameter - dud code.\n");
+ }
+ }
+ else
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Dud IP address: %s\n", gb.Buffer());
+ }
+}
+
+void GCodes::SetMACAddress(GCodeBuffer& gb)
+{
+ uint8_t mac[6];
+ const char* ipString = gb.GetString();
+ uint8_t sp = 0;
+ uint8_t spp = 0;
+ uint8_t ipp = 0;
+ while (ipString[sp])
+ {
+ if (ipString[sp] == ':')
+ {
+ mac[ipp] = strtoul(&ipString[spp], NULL, 16);
+ ipp++;
+ if (ipp > 5)
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Dud MAC address: %s\n", gb.Buffer());
+ return;
+ }
+ sp++;
+ spp = sp;
+ }
+ else
+ {
+ sp++;
+ }
+ }
+ mac[ipp] = strtoul(&ipString[spp], NULL, 16);
+ if (ipp == 5)
+ {
+ platform->SetMACAddress(mac);
+ }
+ else
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Dud MAC address: %s\n", gb.Buffer());
+ }
+}
+
+bool GCodes::ChangeMicrostepping(size_t drive, int microsteps, int mode) const
+{
+ bool dummy;
+ unsigned int oldSteps = platform->GetMicrostepping(drive, dummy);
+ bool success = platform->SetMicrostepping(drive, microsteps, mode);
+ if (success && mode <= 1) // modes higher than 1 are used for special functions
+ {
+ // We changed the microstepping, so adjust the steps/mm to compensate
+ float stepsPerMm = platform->DriveStepsPerUnit(drive);
+ if (stepsPerMm > 0)
+ {
+ platform->SetDriveStepsPerUnit(drive, stepsPerMm * (float)microsteps / (float)oldSteps);
+ }
+ }
+ return success;
+}
+
+// Set the speeds of fans mapped for the current tool to lastDefaultFanSpeed
+void GCodes::SetMappedFanSpeed()
+{
+ if (reprap.GetCurrentTool() == nullptr)
+ {
+ platform->SetFanValue(0, lastDefaultFanSpeed);
+ }
+ else
+ {
+ const uint32_t fanMap = reprap.GetCurrentTool()->GetFanMapping();
+ for (size_t i = 0; i < NUM_FANS; ++i)
+ {
+ if ((fanMap & (1u << i)) != 0)
+ {
+ platform->SetFanValue(i, lastDefaultFanSpeed);
+ }
+ }
+ }
+}
+
+// Handle sending a reply back to the appropriate interface(s).
+// Note that 'reply' may be empty. If it isn't, then we need to append newline when sending it.
+// Also, gb may be null if we were executing a trigger macro.
+void GCodes::HandleReply(GCodeBuffer& gb, bool error, const char* reply)
+{
+ // Don't report "ok" responses if a (macro) file is being processed
+ // Also check that this response was triggered by a gcode
+ if ((gb.MachineState().doingFileMacro || &gb == fileGCode) && reply[0] == 0)
+ {
+ return;
+ }
+
+ // Second UART device, e.g. dc42's PanelDue. Do NOT use emulation for this one!
+ if (&gb == auxGCode)
+ {
+ platform->AppendAuxReply(reply);
+ return;
+ }
+
+ const Compatibility c = (&gb == serialGCode || &gb == telnetGCode) ? platform->Emulating() : me;
+ MessageType type = GENERIC_MESSAGE;
+ if (&gb == httpGCode)
+ {
+ type = HTTP_MESSAGE;
+ }
+ else if (&gb == telnetGCode)
+ {
+ type = TELNET_MESSAGE;
+ }
+ else if (&gb == serialGCode)
+ {
+ type = HOST_MESSAGE;
+ }
+
+ const char* response = (gb.Seen('M') && gb.GetIValue() == 998) ? "rs " : "ok";
+ const char* emulationType = 0;
+
+ switch (c)
+ {
+ case me:
+ case reprapFirmware:
+ if (error)
+ {
+ platform->Message(type, "Error: ");
+ }
+ platform->Message(type, reply);
+ platform->Message(type, "\n");
+ return;
+
+ case marlin:
+ // We don't need to handle M20 here because we always allocate an output buffer for that one
+ if (gb.Seen('M') && gb.GetIValue() == 28)
+ {
+ platform->Message(type, response);
+ platform->Message(type, "\n");
+ platform->Message(type, reply);
+ platform->Message(type, "\n");
+ return;
+ }
+
+ if ((gb.Seen('M') && gb.GetIValue() == 105) || (gb.Seen('M') && gb.GetIValue() == 998))
+ {
+ platform->Message(type, response);
+ platform->Message(type, " ");
+ platform->Message(type, reply);
+ platform->Message(type, "\n");
+ return;
+ }
+
+ if (reply[0] != 0 && !gb.IsDoingFileMacro())
+ {
+ platform->Message(type, reply);
+ platform->Message(type, "\n");
+ platform->Message(type, response);
+ platform->Message(type, "\n");
+ }
+ else if (reply[0] != 0)
+ {
+ platform->Message(type, reply);
+ platform->Message(type, "\n");
+ }
+ else
+ {
+ platform->Message(type, response);
+ platform->Message(type, "\n");
+ }
+ return;
+
+ case teacup:
+ emulationType = "teacup";
+ break;
+ case sprinter:
+ emulationType = "sprinter";
+ break;
+ case repetier:
+ emulationType = "repetier";
+ break;
+ default:
+ emulationType = "unknown";
+ }
+
+ if (emulationType != 0)
+ {
+ platform->MessageF(type, "Emulation of %s is not yet supported.\n", emulationType); // don't send this one to the web as well, it concerns only the USB interface
+ }
+}
+
+void GCodes::HandleReply(GCodeBuffer& gb, bool error, OutputBuffer *reply)
+{
+ // Although unlikely, it's possible that we get a nullptr reply. Don't proceed if this is the case
+ if (reply == nullptr)
+ {
+ return;
+ }
+
+ // Second UART device, e.g. dc42's PanelDue. Do NOT use emulation for this one!
+ if (&gb == auxGCode)
+ {
+ platform->AppendAuxReply(reply);
+ return;
+ }
+
+ const Compatibility c = (&gb == serialGCode || &gb == telnetGCode) ? platform->Emulating() : me;
+ MessageType type = GENERIC_MESSAGE;
+ if (&gb == httpGCode)
+ {
+ type = HTTP_MESSAGE;
+ }
+ else if (&gb == telnetGCode)
+ {
+ type = TELNET_MESSAGE;
+ }
+ else if (&gb == serialGCode)
+ {
+ type = HOST_MESSAGE;
+ }
+
+ const char* response = (gb.Seen('M') && gb.GetIValue() == 998) ? "rs " : "ok";
+ const char* emulationType = nullptr;
+
+ switch (c)
+ {
+ case me:
+ case reprapFirmware:
+ if (error)
+ {
+ platform->Message(type, "Error: ");
+ }
+ platform->Message(type, reply);
+ return;
+
+ case marlin:
+ if (gb.Seen('M') && gb.GetIValue() == 20)
+ {
+ platform->Message(type, "Begin file list\n");
+ platform->Message(type, reply);
+ platform->Message(type, "End file list\n");
+ platform->Message(type, response);
+ platform->Message(type, "\n");
+ return;
+ }
+
+ if (gb.Seen('M') && gb.GetIValue() == 28)
+ {
+ platform->Message(type, response);
+ platform->Message(type, "\n");
+ platform->Message(type, reply);
+ return;
+ }
+
+ if ((gb.Seen('M') && gb.GetIValue() == 105) || (gb.Seen('M') && gb.GetIValue() == 998))
+ {
+ platform->Message(type, response);
+ platform->Message(type, " ");
+ platform->Message(type, reply);
+ return;
+ }
+
+ if (reply->Length() != 0 && !gb.IsDoingFileMacro())
+ {
+ platform->Message(type, reply);
+ platform->Message(type, "\n");
+ platform->Message(type, response);
+ platform->Message(type, "\n");
+ }
+ else if (reply->Length() != 0)
+ {
+ platform->Message(type, reply);
+ }
+ else
+ {
+ OutputBuffer::ReleaseAll(reply);
+ platform->Message(type, response);
+ platform->Message(type, "\n");
+ }
+ return;
+
+ case teacup:
+ emulationType = "teacup";
+ break;
+ case sprinter:
+ emulationType = "sprinter";
+ break;
+ case repetier:
+ emulationType = "repetier";
+ break;
+ default:
+ emulationType = "unknown";
+ }
+
+ // If we get here then we didn't handle the message, so release the buffer(s)
+ OutputBuffer::ReleaseAll(reply);
+ if (emulationType != 0)
+ {
+ platform->MessageF(type, "Emulation of %s is not yet supported.\n", emulationType); // don't send this one to the web as well, it concerns only the USB interface
+ }
+}
+
+// Set PID parameters (M301 or M304 command). 'heater' is the default heater number to use.
+void GCodes::SetPidParameters(GCodeBuffer& gb, int heater, StringRef& reply)
+{
+ if (gb.Seen('H'))
+ {
+ heater = gb.GetIValue();
+ }
+
+ if (heater >= 0 && heater < HEATERS)
+ {
+ PidParameters pp = platform->GetPidParameters(heater);
+ bool seen = false;
+ gb.TryGetFValue('P', pp.kP, seen);
+ gb.TryGetFValue('I', pp.kI, seen);
+ gb.TryGetFValue('D', pp.kD, seen);
+ gb.TryGetFValue('T', pp.kT, seen);
+ gb.TryGetFValue('S', pp.kS, seen);
+
+ if (seen)
+ {
+ platform->SetPidParameters(heater, pp);
+ reprap.GetHeat()->UseModel(heater, false);
+ }
+ else
+ {
+ reply.printf("Heater %d P:%.2f I:%.3f D:%.2f T:%.2f S:%.2f", heater, pp.kP, pp.kI, pp.kD, pp.kT, pp.kS);
+ }
+ }
+}
+
+void GCodes::SetHeaterParameters(GCodeBuffer& gb, StringRef& reply)
+{
+ if (gb.Seen('P'))
+ {
+ int heater = gb.GetIValue();
+ if (heater >= 0 && heater < HEATERS)
+ {
+ Thermistor& th = platform->GetThermistor(heater);
+ bool seen = false;
+
+ // We must set the 25C resistance and beta together in order to calculate Rinf. Check for these first.
+ float r25 = th.GetR25();
+ float beta = th.GetBeta();
+ float shC = th.GetShc();
+ float seriesR = th.GetSeriesR();
+
+ gb.TryGetFValue('T', r25, seen);
+ gb.TryGetFValue('B', beta, seen);
+ gb.TryGetFValue('C', shC, seen);
+ gb.TryGetFValue('R', seriesR, seen);
+ if (seen)
+ {
+ th.SetParameters(r25, beta, shC, seriesR);
+ }
+
+ if (gb.Seen('L'))
+ {
+ th.SetLowOffset((int8_t)constrain<int>(gb.GetIValue(), -100, 100));
+ seen = true;
+ }
+ if (gb.Seen('H'))
+ {
+ th.SetHighOffset((int8_t)constrain<int>(gb.GetIValue(), -100, 100));
+ seen = true;
+ }
+
+ if (gb.Seen('X'))
+ {
+ int thermistor = gb.GetIValue();
+ if ( (0 <= thermistor && thermistor < HEATERS)
+ || ((int)FirstThermocoupleChannel <= thermistor && thermistor < (int)(FirstThermocoupleChannel + MaxSpiTempSensors))
+ || ((int)FirstRtdChannel <= thermistor && thermistor < (int)(FirstRtdChannel + MaxSpiTempSensors))
+ )
+ {
+ platform->SetThermistorNumber(heater, thermistor);
+ }
+ else
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Thermistor number %d is out of range\n", thermistor);
+ }
+ seen = true;
+ }
+
+ if (!seen)
+ {
+ reply.printf("T:%.1f B:%.1f C:%.2e R:%.1f L:%d H:%d X:%d",
+ th.GetR25(), th.GetBeta(), th.GetShc(), th.GetSeriesR(),
+ th.GetLowOffset(), th.GetHighOffset(), platform->GetThermistorNumber(heater));
+ }
+ }
+ else
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Heater number %d is out of range\n", heater);
+ }
+ }
+}
+
+void GCodes::SetToolHeaters(Tool *tool, float temperature)
+{
+ if (tool == NULL)
+ {
+ platform->Message(GENERIC_MESSAGE, "Setting temperature: no tool selected.\n");
+ return;
+ }
+
+ float standby[HEATERS];
+ float active[HEATERS];
+ tool->GetVariables(standby, active);
+ for (size_t h = 0; h < tool->HeaterCount(); h++)
+ {
+ active[h] = temperature;
+ }
+ tool->SetVariables(standby, active);
+}
+
+// Begin the tool change sequence
+void GCodes::StartToolChange(GCodeBuffer& gb, bool inM109)
+{
+ gb.SetState((inM109) ? GCodeState:: m109ToolChange1 : GCodeState::toolChange1);
+ const Tool * const oldTool = reprap.GetCurrentTool();
+ if (oldTool != nullptr && AllAxesAreHomed())
+ {
+ scratchString.printf("tfree%d.g", oldTool->Number());
+ DoFileMacro(gb, scratchString.Pointer(), false);
+ }
+}
+
+// Retract or un-retract filament, returning true if movement has been queued, false if this needs to be called again
+bool GCodes::RetractFilament(bool retract)
+{
+ if (retractLength != 0.0 || retractHop != 0.0 || (!retract && retractExtra != 0.0))
+ {
+ const Tool *tool = reprap.GetCurrentTool();
+ if (tool != nullptr)
+ {
+ size_t nDrives = tool->DriveCount();
+ if (nDrives != 0)
+ {
+ if (segmentsLeft != 0)
+ {
+ return false;
+ }
+
+ reprap.GetMove()->GetCurrentUserPosition(moveBuffer.coords, 0, reprap.GetCurrentXAxes());
+ for (size_t i = numAxes; i < DRIVES; ++i)
+ {
+ moveBuffer.coords[i] = 0.0;
+ }
+ // Set the feed rate. If there is any Z hop then we need to pass the Z speed, else we pass the extrusion speed.
+ const float speedToUse = (retract) ? retractSpeed : unRetractSpeed;
+ moveBuffer.feedRate = (retractHop == 0.0)
+ ? speedToUse * secondsToMinutes
+ : speedToUse * secondsToMinutes * retractHop/retractLength;
+ moveBuffer.coords[Z_AXIS] += (retract) ? retractHop : -retractHop;
+ const float lengthToUse = (retract) ? -retractLength : retractLength + retractExtra;
+ for (size_t i = 0; i < nDrives; ++i)
+ {
+ moveBuffer.coords[E0_AXIS + tool->Drive(i)] = lengthToUse;
+ }
+
+ moveBuffer.isFirmwareRetraction = true;
+ moveBuffer.usePressureAdvance = false;
+ moveBuffer.filePos = filePos;
+ moveBuffer.xAxes = reprap.GetCurrentXAxes();
+ segmentsLeft = 1;
+ }
+ }
+ }
+ return true;
+}
+
+// Return the amount of filament extruded
+float GCodes::GetRawExtruderPosition(size_t extruder) const
+{
+ return (extruder < numExtruders) ? lastRawExtruderPosition[extruder] : 0.0;
+}
+
+float GCodes::GetRawExtruderTotalByDrive(size_t extruder) const
+{
+ return (extruder < numExtruders) ? rawExtruderTotalByDrive[extruder] : 0.0;
+}
+
+// Cancel the current SD card print.
+// This is called from Pid.cpp when there is a heater fault, and from elsewhere in this module.
+void GCodes::CancelPrint()
+{
+ segmentsLeft = 0;
+ isPaused = false;
+
+ fileGCode->Init();
+ FileData& fileBeingPrinted = fileGCode->OriginalMachineState().fileState;
+ if (fileBeingPrinted.IsLive())
+ {
+ fileBeingPrinted.Close();
+ }
+
+ reprap.GetPrintMonitor()->StoppedPrint();
+}
+
+// Return true if all the heaters for the specified tool are at their set temperatures
+bool GCodes::ToolHeatersAtSetTemperatures(const Tool *tool, bool waitWhenCooling) const
+{
+ if (tool != NULL)
+ {
+ for (size_t i = 0; i < tool->HeaterCount(); ++i)
+ {
+ if (!reprap.GetHeat()->HeaterAtSetTemperature(tool->Heater(i), waitWhenCooling))
+ {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+// Set the current position
+void GCodes::SetPositions(float positionNow[DRIVES])
+{
+ // Transform the position so that e.g. if the user does G92 Z0,
+ // the position we report (which gets inverse-transformed) really is Z=0 afterwards
+ reprap.GetMove()->Transform(positionNow, reprap.GetCurrentXAxes());
+ reprap.GetMove()->SetLiveCoordinates(positionNow);
+ reprap.GetMove()->SetPositions(positionNow);
+}
+
+bool GCodes::IsPaused() const
+{
+ return isPaused && !IsPausing() && !IsResuming();
+}
+
+bool GCodes::IsPausing() const
+{
+ const GCodeState topState = fileGCode->OriginalMachineState().state;
+ return topState == GCodeState::pausing1 || topState == GCodeState::pausing2;
+}
+
+bool GCodes::IsResuming() const
+{
+ const GCodeState topState = fileGCode->OriginalMachineState().state;
+ return topState == GCodeState::resuming1 || topState == GCodeState::resuming2 || topState == GCodeState::resuming3;
+}
+
+bool GCodes::IsRunning() const
+{
+ return !IsPaused() && !IsPausing() && !IsResuming();
+}
+
+const char *GCodes::TranslateEndStopResult(EndStopHit es)
+{
+ switch (es)
+ {
+ case EndStopHit::lowHit:
+ return "at min stop";
+
+ case EndStopHit::highHit:
+ return "at max stop";
+
+ case EndStopHit::lowNear:
+ return "near min stop";
+
+ case EndStopHit::noStop:
+ default:
+ return "not stopped";
+ }
+}
+
+// Append a list of trigger endstops to a message
+void GCodes::ListTriggers(StringRef reply, TriggerMask mask)
+{
+ if (mask == 0)
+ {
+ reply.cat("(none)");
+ }
+ else
+ {
+ bool printed = false;
+ for (unsigned int i = 0; i < DRIVES; ++i)
+ {
+ if ((mask & (1u << i)) != 0)
+ {
+ if (printed)
+ {
+ reply.cat(' ');
+ }
+ if (i < numAxes)
+ {
+ reply.cat(axisLetters[i]);
+ }
+ else
+ {
+ reply.catf("E%d", i - numAxes);
+ }
+ printed = true;
+ }
+ }
+ }
+}
+
+// M38 (SHA1 hash of a file) implementation:
+bool GCodes::StartHash(const char* filename)
+{
+ // Get a FileStore object
+ fileBeingHashed = platform->GetFileStore(FS_PREFIX, filename, false);
+ if (fileBeingHashed == nullptr)
+ {
+ return false;
+ }
+
+ // Start hashing
+ SHA1Reset(&hash);
+ return true;
+}
+
+bool GCodes::AdvanceHash(StringRef &reply)
+{
+ // Read and process some more data from the file
+ uint32_t buf32[(FILE_BUFFER_SIZE + 3) / 4];
+ char *buffer = reinterpret_cast<char *>(buf32);
+
+ int bytesRead = fileBeingHashed->Read(buffer, FILE_BUFFER_SIZE);
+ if (bytesRead != -1)
+ {
+ SHA1Input(&hash, reinterpret_cast<const uint8_t *>(buffer), bytesRead);
+
+ if (bytesRead != FILE_BUFFER_SIZE)
+ {
+ // Calculate and report the final result
+ SHA1Result(&hash);
+ for(size_t i = 0; i < 5; i++)
+ {
+ reply.catf("%x", hash.Message_Digest[i]);
+ }
+
+ // Clean up again
+ fileBeingHashed->Close();
+ fileBeingHashed = nullptr;
+ return true;
+ }
+ return false;
+ }
+
+ // Something went wrong, we cannot read any more from the file
+ fileBeingHashed->Close();
+ fileBeingHashed = nullptr;
+ return true;
+}
+
+bool GCodes::AllAxesAreHomed() const
+{
+ const uint32_t allAxes = (1u << numAxes) - 1;
+ return (axesHomed & allAxes) == allAxes;
+}
+
+void GCodes::SetAllAxesNotHomed()
+{
+ axesHomed = 0;
+}
+
+// Resource locking/unlocking
+
+// Lock the resource, returning true if success.
+// Locking the same resource more than once only locks it once, there is no lock count held.
+bool GCodes::LockResource(const GCodeBuffer& gb, Resource r)
+{
+ if (resourceOwners[r] == &gb)
+ {
+ return true;
+ }
+ if (resourceOwners[r] == nullptr)
+ {
+ resourceOwners[r] = &gb;
+ gb.MachineState().lockedResources |= (1u << r);
+ return true;
+ }
+ return false;
+}
+
+bool GCodes::LockHeater(const GCodeBuffer& gb, int heater)
+{
+ if (heater >= 0 && heater < HEATERS)
+ {
+ return LockResource(gb, HeaterResourceBase + heater);
+ }
+ return true;
+}
+
+bool GCodes::LockFan(const GCodeBuffer& gb, int fan)
+{
+ if (fan >= 0 && fan < (int)NUM_FANS)
+ {
+ return LockResource(gb, FanResourceBase + fan);
+ }
+ return true;
+}
+
+// Lock the unshareable parts of the file system
+bool GCodes::LockFileSystem(const GCodeBuffer &gb)
+{
+ return LockResource(gb, FileSystemResource);
+}
+
+// Lock movement
+bool GCodes::LockMovement(const GCodeBuffer& gb)
+{
+ return LockResource(gb, MoveResource);
+}
+
+// Release all locks, except those that were owned when the current macro was started
+void GCodes::UnlockAll(const GCodeBuffer& gb)
+{
+ const GCodeMachineState * const mc = gb.MachineState().previous;
+ const uint32_t resourcesToKeep = (mc == nullptr) ? 0 : mc->lockedResources;
+ for (size_t i = 0; i < NumResources; ++i)
+ {
+ if (resourceOwners[i] == &gb && ((1u << i) & resourcesToKeep) == 0)
+ {
+ resourceOwners[i] = nullptr;
+ gb.MachineState().lockedResources &= ~(1u << i);
+ }
+ }
+}
+
+// Convert an array of longs to a bit map
+/*static*/ uint32_t GCodes::LongArrayToBitMap(const long *arr, size_t numEntries)
+{
+ uint32_t res = 0;
+ for (size_t i = 0; i < numEntries; ++i)
+ {
+ const long f = arr[i];
+ if (f >= 0 && f < 32)
+ {
+ res |= 1u << (unsigned int)f;
+ }
+ }
+ return res;
+}
+
+// End
diff --git a/src/GCodes/GCodes2.cpp b/src/GCodes/GCodes2.cpp
new file mode 100644
index 00000000..5c7ef148
--- /dev/null
+++ b/src/GCodes/GCodes2.cpp
@@ -0,0 +1,3481 @@
+/*
+ * GCodes2.cpp
+ *
+ * Created on: 3 Dec 2016
+ * Author: David
+ *
+ * This file contains the code to see what G, M or T command we have and start processing it.
+ */
+
+#include "RepRapFirmware.h"
+
+#ifdef DUET_NG
+#include "FirmwareUpdater.h"
+#endif
+
+const char* BED_EQUATION_G = "bed.g";
+const char* RESUME_G = "resume.g";
+const char* CANCEL_G = "cancel.g";
+const char* STOP_G = "stop.g";
+const char* SLEEP_G = "sleep.g";
+
+const float MinServoPulseWidth = 544.0, MaxServoPulseWidth = 2400.0;
+const uint16_t ServoRefreshFrequency = 50;
+
+// If the code to act on is completed, this returns true,
+// otherwise false. It is called repeatedly for a given
+// code until it returns true for that code.
+bool GCodes::ActOnCode(GCodeBuffer& gb, StringRef& reply)
+{
+ // Discard empty buffers right away
+ if (gb.IsEmpty())
+ {
+ return true;
+ }
+
+ // M-code parameters might contain letters T and G, e.g. in filenames.
+ // dc42 assumes that G-and T-code parameters never contain the letter M.
+ // Therefore we must check for an M-code first.
+ if (gb.Seen('M'))
+ {
+ return HandleMcode(gb, reply);
+ }
+ // dc42 doesn't think a G-code parameter ever contains letter T, or a T-code ever contains letter G.
+ // So it doesn't matter in which order we look for them.
+ if (gb.Seen('G'))
+ {
+ return HandleGcode(gb, reply);
+ }
+ if (gb.Seen('T'))
+ {
+ return HandleTcode(gb, reply);
+ }
+
+ // An invalid or queued buffer gets discarded
+ HandleReply(gb, false, "");
+ return true;
+}
+
+bool GCodes::HandleGcode(GCodeBuffer& gb, StringRef& reply)
+{
+ bool result = true;
+ bool error = false;
+
+ int code = gb.GetIValue();
+ if (simulationMode != 0 && code != 0 && code != 1 && code != 4 && code != 10 && code != 20 && code != 21 && code != 90 && code != 91 && code != 92)
+ {
+ return true; // we only simulate some gcodes
+ }
+
+ switch (code)
+ {
+ case 0: // There are no rapid moves...
+ case 1: // Ordinary move
+ if (!LockMovement(gb))
+ {
+ return false;
+ }
+ {
+ // Check for 'R' parameter here to go back to the coordinates at which the print was paused
+ // NOTE: restore point 2 (tool change) won't work when changing tools on dual axis machines because of X axis mapping.
+ // We could possibly fix this by saving the virtual X axis position instead of the physical axis positions.
+ // However, slicers normally command the tool to the correct place after a tool change, so we don't need this feature anyway.
+ int rParam = (gb.Seen('R')) ? gb.GetIValue() : 0;
+ RestorePoint *rp = (rParam == 1) ? &pauseRestorePoint : (rParam == 2) ? &toolChangeRestorePoint : nullptr;
+ if (rp != nullptr)
+ {
+ if (segmentsLeft != 0)
+ {
+ return false;
+ }
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ float offset = gb.Seen(axisLetters[axis]) ? gb.GetFValue() * distanceScale : 0.0;
+ moveBuffer.coords[axis] = rp->moveCoords[axis] + offset;
+ }
+ // For now we don't handle extrusion at the same time
+ for (size_t drive = numAxes; drive < DRIVES; ++drive)
+ {
+ moveBuffer.coords[drive] = 0.0;
+ }
+ moveBuffer.feedRate = (gb.Seen(feedrateLetter)) ? gb.GetFValue() : gb.MachineState().feedrate;
+ moveBuffer.filePos = noFilePosition;
+ moveBuffer.usePressureAdvance = false;
+ segmentsLeft = 1;
+ }
+ else
+ {
+ int res = SetUpMove(gb, reply);
+ if (res == 2)
+ {
+ gb.SetState(GCodeState::waitingForMoveToComplete);
+ }
+ result = (res != 0);
+ }
+ }
+ break;
+
+ case 4: // Dwell
+ result = DoDwell(gb);
+ break;
+
+ case 10: // Set/report offsets and temperatures, or retract
+ if (gb.Seen('P'))
+ {
+ if (!SetOrReportOffsets(gb, reply))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ if (!LockMovement(gb))
+ {
+ return false;
+ }
+ result = RetractFilament(true);
+ }
+ break;
+
+ case 11: // Un-retract
+ if (!LockMovement(gb))
+ {
+ return false;
+ }
+ result = RetractFilament(false);
+ break;
+
+ case 20: // Inches (which century are we living in, here?)
+ distanceScale = INCH_TO_MM;
+ break;
+
+ case 21: // mm
+ distanceScale = 1.0;
+ break;
+
+ case 28: // Home
+ result = DoHome(gb, reply, error);
+ break;
+
+ case 29: // Grid-based bed probing
+ if (!LockMovementAndWaitForStandstill(gb)) // do this first to make sure that a new grid isn't being defined
+ {
+ return false;
+ }
+ error = ProbeGrid(gb, reply);
+ break;
+
+ case 30: // Z probe/manually set at a position and set that as point P
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ if (reprap.GetMove()->IsDeltaMode() && !AllAxesAreHomed())
+ {
+ reply.copy("Must home a delta printer before bed probing");
+ error = true;
+ }
+ else
+ {
+ result = SetSingleZProbeAtAPosition(gb, reply);
+ }
+ break;
+
+ case 31: // Return the probe value, or set probe variables
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ result = SetPrintZProbe(gb, reply);
+ break;
+
+ case 32: // Probe Z at multiple positions and generate the bed transform
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+
+ // Try to execute bed.g
+ if (!DoFileMacro(gb, BED_EQUATION_G, reprap.GetMove()->IsDeltaMode()))
+ {
+ // If we get here then we are not on a delta printer and there is no bed.g file
+ if (GetAxisIsHomed(X_AXIS) && GetAxisIsHomed(Y_AXIS))
+ {
+ gb.SetState(GCodeState::setBed1); // no bed.g file, so use the coordinates specified by M557
+ }
+ else
+ {
+ // We can only do bed levelling if X and Y have already been homed
+ reply.copy("Must home X and Y before bed probing");
+ error = true;
+ }
+ }
+ break;
+
+ case 90: // Absolute coordinates
+ gb.MachineState().axesRelative = false;
+ break;
+
+ case 91: // Relative coordinates
+ gb.MachineState().axesRelative = true; // Axis movements (i.e. X, Y and Z)
+ break;
+
+ case 92: // Set position
+ result = SetPositions(gb);
+ break;
+
+ default:
+ error = true;
+ reply.printf("invalid G Code: %s", gb.Buffer());
+ }
+
+ if (result && gb.GetState() == GCodeState::normal)
+ {
+ UnlockAll(gb);
+ HandleReply(gb, error, reply.Pointer());
+ }
+ return result;
+}
+
+bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply)
+{
+ bool result = true;
+ bool error = false;
+
+ const int code = gb.GetIValue();
+ if (simulationMode != 0 && (code < 20 || code > 37) && code != 0 && code != 1 && code != 82 && code != 83 && code != 105 && code != 111 && code != 112 && code != 122 && code != 408 && code != 999)
+ {
+ return true; // we don't yet simulate most M codes
+ }
+
+ switch (code)
+ {
+ case 0: // Stop
+ case 1: // Sleep
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ {
+ bool wasPaused = isPaused; // isPaused gets cleared by CancelPrint
+ CancelPrint();
+ if (wasPaused)
+ {
+ reply.copy("Print cancelled");
+ // If we are cancelling a paused print with M0 and cancel.g exists then run it and do nothing else
+ if (code == 0)
+ {
+ if (DoFileMacro(gb, CANCEL_G, false))
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ gb.SetState((code == 0) ? GCodeState::stopping : GCodeState::sleeping);
+ DoFileMacro(gb, (code == 0) ? STOP_G : SLEEP_G, false);
+ break;
+
+#if SUPPORT_ROLAND
+ case 3: // Spin spindle
+ if (reprap.GetRoland()->Active())
+ {
+ if (gb.Seen('S'))
+ {
+ result = reprap.GetRoland()->ProcessSpindle(gb.GetFValue());
+ }
+ }
+ break;
+#endif
+
+ case 18: // Motors off
+ case 84:
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ {
+ bool seen = false;
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ SetAxisNotHomed(axis);
+ platform->DisableDrive(axis);
+ seen = true;
+ }
+ }
+
+ if (gb.Seen(extrudeLetter))
+ {
+ long int eDrive[MaxExtruders];
+ size_t eCount = numExtruders;
+ gb.GetLongArray(eDrive, eCount);
+ for (size_t i = 0; i < eCount; i++)
+ {
+ seen = true;
+ if (eDrive[i] < 0 || (size_t)eDrive[i] >= numExtruders)
+ {
+ reply.printf("Invalid extruder number specified: %ld", eDrive[i]);
+ error = true;
+ break;
+ }
+ platform->DisableDrive(numAxes + eDrive[i]);
+ }
+ }
+
+ if (gb.Seen('S'))
+ {
+ seen = true;
+
+ float idleTimeout = gb.GetFValue();
+ if (idleTimeout < 0.0)
+ {
+ reply.copy("Idle timeouts cannot be negative!");
+ error = true;
+ }
+ else
+ {
+ reprap.GetMove()->SetIdleTimeout(idleTimeout);
+ }
+ }
+
+ if (!seen)
+ {
+ DisableDrives();
+ }
+ }
+ break;
+
+ case 20: // List files on SD card
+ if (!LockFileSystem(gb)) // don't allow more than one at a time to avoid contention on output buffers
+ {
+ return false;
+ }
+ {
+ OutputBuffer *fileResponse;
+ const int sparam = (gb.Seen('S')) ? gb.GetIValue() : 0;
+ const char* dir = (gb.Seen('P')) ? gb.GetString() : platform->GetGCodeDir();
+
+ if (sparam == 2)
+ {
+ fileResponse = reprap.GetFilesResponse(dir, true); // Send the file list in JSON format
+ fileResponse->cat('\n');
+ }
+ else
+ {
+ if (!OutputBuffer::Allocate(fileResponse))
+ {
+ // Cannot allocate an output buffer, try again later
+ return false;
+ }
+
+ // To mimic the behaviour of the official RepRapPro firmware:
+ // If we are emulating RepRap then we print "GCode files:\n" at the start, otherwise we don't.
+ // If we are emulating Marlin and the code came via the serial/USB interface, then we don't put quotes around the names and we separate them with newline;
+ // otherwise we put quotes around them and separate them with comma.
+ if (platform->Emulating() == me || platform->Emulating() == reprapFirmware)
+ {
+ fileResponse->copy("GCode files:\n");
+ }
+
+ bool encapsulateList = ((&gb != serialGCode && &gb != telnetGCode) || platform->Emulating() != marlin);
+ FileInfo fileInfo;
+ if (platform->GetMassStorage()->FindFirst(dir, fileInfo))
+ {
+ // iterate through all entries and append each file name
+ do {
+ if (encapsulateList)
+ {
+ fileResponse->catf("%c%s%c%c", FILE_LIST_BRACKET, fileInfo.fileName, FILE_LIST_BRACKET, FILE_LIST_SEPARATOR);
+ }
+ else
+ {
+ fileResponse->catf("%s\n", fileInfo.fileName);
+ }
+ } while (platform->GetMassStorage()->FindNext(fileInfo));
+
+ if (encapsulateList)
+ {
+ // remove the last separator
+ (*fileResponse)[fileResponse->Length() - 1] = 0;
+ }
+ }
+ else
+ {
+ fileResponse->cat("NONE\n");
+ }
+ }
+
+ UnlockAll(gb);
+ HandleReply(gb, false, fileResponse);
+ return true;
+ }
+
+ case 21: // Initialise SD card
+ if (!LockFileSystem(gb)) // don't allow more than one at a time to avoid contention on output buffers
+ {
+ return false;
+ }
+ {
+ size_t card = (gb.Seen('P')) ? gb.GetIValue() : 0;
+ result = platform->GetMassStorage()->Mount(card, reply, true);
+ }
+ break;
+
+ case 22: // Release SD card
+ if (!LockFileSystem(gb)) // don't allow more than one at a time to avoid contention on output buffers
+ {
+ return false;
+ }
+ {
+ size_t card = (gb.Seen('P')) ? gb.GetIValue() : 0;
+ result = platform->GetMassStorage()->Unmount(card, reply);
+ }
+ break;
+
+ case 23: // Set file to print
+ case 32: // Select file and start SD print
+ if (fileGCode->OriginalMachineState().fileState.IsLive())
+ {
+ reply.copy("Cannot set file to print, because a file is already being printed");
+ error = true;
+ break;
+ }
+
+ if (code == 32 && !LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ {
+ const char* filename = gb.GetUnprecedentedString();
+ if (filename != nullptr)
+ {
+ QueueFileToPrint(filename);
+ if (fileToPrint.IsLive())
+ {
+ reprap.GetPrintMonitor()->StartingPrint(filename);
+ if (platform->Emulating() == marlin && (&gb == serialGCode || &gb == telnetGCode))
+ {
+ reply.copy("File opened\nFile selected");
+ }
+ else
+ {
+ // Command came from web interface or PanelDue, or not emulating Marlin, so send a nicer response
+ reply.printf("File %s selected for printing", filename);
+ }
+
+ if (code == 32)
+ {
+ fileGCode->OriginalMachineState().fileState.MoveFrom(fileToPrint);
+ reprap.GetPrintMonitor()->StartedPrint();
+ }
+ }
+ else
+ {
+ reply.printf("Failed to open file %s", filename);
+ }
+ }
+ }
+ break;
+
+ case 24: // Print/resume-printing the selected file
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+
+ if (isPaused)
+ {
+ gb.SetState(GCodeState::resuming1);
+ DoFileMacro(gb, RESUME_G);
+ }
+ else if (!fileToPrint.IsLive())
+ {
+ reply.copy("Cannot print, because no file is selected!");
+ error = true;
+ }
+ else
+ {
+ fileGCode->OriginalMachineState().fileState.MoveFrom(fileToPrint);
+ reprap.GetPrintMonitor()->StartedPrint();
+ }
+ break;
+
+ case 226: // Gcode Initiated Pause
+ if (&gb == fileGCode) // ignore M226 if it did't come from within a file being printed
+ {
+ if (!LockMovement(gb)) // lock movement before calling DoPause
+ {
+ return false;
+ }
+ DoPause(gb);
+ }
+ break;
+
+ case 25: // Pause the print
+ if (isPaused)
+ {
+ reply.copy("Printing is already paused!!");
+ error = true;
+ }
+ else if (!reprap.GetPrintMonitor()->IsPrinting())
+ {
+ reply.copy("Cannot pause print, because no file is being printed!");
+ error = true;
+ }
+ else
+ {
+ if (!LockMovement(gb)) // lock movement before calling DoPause
+ {
+ return false;
+ }
+ DoPause(gb);
+ }
+ break;
+
+ case 26: // Set SD position
+ if (gb.Seen('S'))
+ {
+ const FilePosition value = gb.GetIValue();
+ if (value < 0)
+ {
+ reply.copy("SD positions can't be negative!");
+ error = true;
+ }
+ else if (fileGCode->OriginalMachineState().fileState.IsLive())
+ {
+ if (!fileGCode->OriginalMachineState().fileState.Seek(value))
+ {
+ reply.copy("The specified SD position is invalid!");
+ error = true;
+ }
+ }
+ else if (fileToPrint.IsLive())
+ {
+ if (!fileToPrint.Seek(value))
+ {
+ reply.copy("The specified SD position is invalid!");
+ error = true;
+ }
+ }
+ else
+ {
+ reply.copy("Cannot set SD file position, because no print is in progress!");
+ error = true;
+ }
+ }
+ else
+ {
+ reply.copy("You must specify the SD position in bytes using the S parameter.");
+ error = true;
+ }
+ break;
+
+ case 27: // Report print status - Deprecated
+ if (reprap.GetPrintMonitor()->IsPrinting())
+ {
+ // Pronterface keeps sending M27 commands if "Monitor status" is checked, and it specifically expects the following response syntax
+ FileData& fileBeingPrinted = fileGCode->OriginalMachineState().fileState;
+ reply.printf("SD printing byte %lu/%lu", fileBeingPrinted.GetPosition(), fileBeingPrinted.Length());
+ }
+ else
+ {
+ reply.copy("Not SD printing.");
+ }
+ break;
+
+ case 28: // Write to file
+ {
+ const char* str = gb.GetUnprecedentedString();
+ if (str != nullptr)
+ {
+ bool ok = OpenFileToWrite(gb, platform->GetGCodeDir(), str);
+ if (ok)
+ {
+ reply.printf("Writing to file: %s", str);
+ }
+ else
+ {
+ reply.printf("Can't open file %s for writing.", str);
+ error = true;
+ }
+ }
+ }
+ break;
+
+ case 29: // End of file being written; should be intercepted before getting here
+ reply.copy("GCode end-of-file being interpreted.");
+ break;
+
+ case 30: // Delete file
+ {
+ const char *filename = gb.GetUnprecedentedString();
+ if (filename != nullptr)
+ {
+ DeleteFile(filename);
+ }
+ }
+ break;
+
+ // For case 32, see case 24
+
+ case 36: // Return file information
+ if (!LockFileSystem(gb)) // getting file info takes several calls and isn't reentrant
+ {
+ return false;
+ }
+ {
+ const char* filename = gb.GetUnprecedentedString(true); // get filename, or nullptr if none provided
+ OutputBuffer *fileInfoResponse;
+ result = reprap.GetPrintMonitor()->GetFileInfoResponse(filename, fileInfoResponse);
+ if (result)
+ {
+ fileInfoResponse->cat('\n');
+ UnlockAll(gb);
+ HandleReply(gb, false, fileInfoResponse);
+ return true;
+ }
+ }
+ break;
+
+ case 37: // Simulation mode on/off
+ if (gb.Seen('S'))
+ {
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+
+ bool wasSimulating = (simulationMode != 0);
+ simulationMode = (uint8_t)gb.GetIValue();
+ reprap.GetMove()->Simulate(simulationMode);
+
+ if (simulationMode != 0)
+ {
+ simulationTime = 0.0;
+ if (!wasSimulating)
+ {
+ // Starting a new simulation, so save the current position
+ reprap.GetMove()->GetCurrentUserPosition(simulationRestorePoint.moveCoords, 0, reprap.GetCurrentXAxes());
+ simulationRestorePoint.feedRate = gb.MachineState().feedrate;
+ }
+ }
+ else if (wasSimulating)
+ {
+ // Ending a simulation, so restore the position
+ SetPositions(simulationRestorePoint.moveCoords);
+ for (size_t i = 0; i < DRIVES; ++i)
+ {
+ moveBuffer.coords[i] = simulationRestorePoint.moveCoords[i];
+ }
+ gb.MachineState().feedrate = simulationRestorePoint.feedRate;
+ }
+ }
+ else
+ {
+ reply.printf("Simulation mode: %s, move time: %.1f sec, other time: %.1f sec",
+ (simulationMode != 0) ? "on" : "off", reprap.GetMove()->GetSimulationTime(), simulationTime);
+ }
+ break;
+
+ case 38: // Report SHA1 of file
+ if (!LockFileSystem(gb)) // getting file hash takes several calls and isn't reentrant
+ {
+ return false;
+ }
+ if (fileBeingHashed == nullptr)
+ {
+ // See if we can open the file and start hashing
+ const char* filename = gb.GetUnprecedentedString(true);
+ if (StartHash(filename))
+ {
+ // Hashing is now in progress...
+ result = false;
+ }
+ else
+ {
+ error = true;
+ reply.printf("Cannot open file: %s", filename);
+ }
+ }
+ else
+ {
+ // This can take some time. All the actual heavy lifting is in dedicated methods
+ result = AdvanceHash(reply);
+ }
+ break;
+
+ case 42: // Turn an output pin on or off
+ if (gb.Seen('P'))
+ {
+ const int logicalPin = gb.GetIValue();
+ Pin pin;
+ bool invert;
+ if (platform->GetFirmwarePin(logicalPin, PinAccess::pwm, pin, invert))
+ {
+ if (gb.Seen('S'))
+ {
+ float val = gb.GetFValue();
+ if (val > 1.0)
+ {
+ val /= 255.0;
+ }
+ val = constrain<float>(val, 0.0, 1.0);
+ if (invert)
+ {
+ val = 1.0 - val;
+ }
+ Platform::WriteAnalog(pin, val, DefaultPinWritePwmFreq);
+ }
+ // Ignore the command if no S parameter provided
+ }
+ else
+ {
+ reply.printf("Logical pin %d is not available for writing", logicalPin);
+ error = true;
+ }
+ }
+ break;
+
+ case 80: // ATX power on
+ platform->SetAtxPower(true);
+ break;
+
+ case 81: // ATX power off
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ platform->SetAtxPower(false);
+ break;
+
+ case 82: // Use absolute extruder positioning
+ if (gb.MachineState().drivesRelative) // don't reset the absolute extruder position if it was already absolute
+ {
+ for (size_t extruder = 0; extruder < MaxExtruders; extruder++)
+ {
+ lastRawExtruderPosition[extruder] = 0.0;
+ }
+ gb.MachineState().drivesRelative = false;
+ }
+ break;
+
+ case 83: // Use relative extruder positioning
+ if (!gb.MachineState().drivesRelative) // don't reset the absolute extruder position if it was already relative
+ {
+ for (size_t extruder = 0; extruder < MaxExtruders; extruder++)
+ {
+ lastRawExtruderPosition[extruder] = 0.0;
+ }
+ gb.MachineState().drivesRelative = true;
+ }
+ break;
+
+ // For case 84, see case 18
+
+ case 85: // Set inactive time
+ break;
+
+ case 92: // Set/report steps/mm for some axes
+ {
+ // Save the current positions as we may need them later
+ float positionNow[DRIVES];
+ Move *move = reprap.GetMove();
+ move->GetCurrentUserPosition(positionNow, 0, reprap.GetCurrentXAxes());
+
+ bool seen = false;
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ platform->SetDriveStepsPerUnit(axis, gb.GetFValue());
+ seen = true;
+ }
+ }
+
+ if (gb.Seen(extrudeLetter))
+ {
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ seen = true;
+ float eVals[MaxExtruders];
+ size_t eCount = numExtruders;
+ gb.GetFloatArray(eVals, eCount, true);
+
+ // The user may not have as many extruders as we allow for, so just set the ones for which a value is provided
+ for (size_t e = 0; e < eCount; e++)
+ {
+ platform->SetDriveStepsPerUnit(numAxes + e, eVals[e]);
+ }
+ }
+
+ if (seen)
+ {
+ // On a delta, if we change the drive steps/mm then we need to recalculate the motor positions
+ SetPositions(positionNow);
+ }
+ else
+ {
+ reply.copy("Steps/mm: ");
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ reply.catf("%c: %.3f, ", axisLetters[axis], platform->DriveStepsPerUnit(axis));
+ }
+ reply.catf("E:");
+ char sep = ' ';
+ for (size_t extruder = 0; extruder < numExtruders; extruder++)
+ {
+ reply.catf("%c%.3f", sep, platform->DriveStepsPerUnit(extruder + numAxes));
+ sep = ':';
+ }
+ }
+ }
+ break;
+
+ case 98: // Call Macro/Subprogram
+ if (gb.Seen('P'))
+ {
+ DoFileMacro(gb, gb.GetString());
+ }
+ break;
+
+ case 99: // Return from Macro/Subprogram
+ FileMacroCyclesReturn(gb);
+ break;
+
+ case 104: // Deprecated. This sets the active temperature of every heater of the active tool
+ if (gb.Seen('S'))
+ {
+ float temperature = gb.GetFValue();
+ Tool* tool;
+ if (gb.Seen('T'))
+ {
+ int toolNumber = gb.GetIValue();
+ toolNumber += gb.GetToolNumberAdjust();
+ tool = reprap.GetTool(toolNumber);
+ }
+ else
+ {
+ tool = reprap.GetCurrentOrDefaultTool();
+ }
+ SetToolHeaters(tool, temperature);
+ }
+ break;
+
+ case 105: // Get temperatures
+ {
+ const int8_t bedHeater = reprap.GetHeat()->GetBedHeater();
+ const int8_t chamberHeater = reprap.GetHeat()->GetChamberHeater();
+ reply.copy("T:");
+ for (int8_t heater = 0; heater < HEATERS; heater++)
+ {
+ if (heater != bedHeater && heater != chamberHeater)
+ {
+ Heat::HeaterStatus hs = reprap.GetHeat()->GetStatus(heater);
+ if (hs != Heat::HS_off && hs != Heat::HS_fault)
+ {
+ reply.catf("%.1f ", reprap.GetHeat()->GetTemperature(heater));
+ }
+ }
+ }
+ if (bedHeater >= 0)
+ {
+ reply.catf("B:%.1f", reprap.GetHeat()->GetTemperature(bedHeater));
+ }
+ else
+ {
+ // I'm not sure whether Pronterface etc. can handle a missing bed temperature, so return zero
+ reply.cat("B:0.0");
+ }
+ if (chamberHeater >= 0.0)
+ {
+ reply.catf(" C:%.1f", reprap.GetHeat()->GetTemperature(chamberHeater));
+ }
+ }
+ break;
+
+ case 106: // Set/report fan values
+ {
+ bool seenFanNum = false;
+ int32_t fanNum = 0; // Default to the first fan
+ gb.TryGetIValue('P', fanNum, seenFanNum);
+ if (fanNum < 0 || fanNum > (int)NUM_FANS)
+ {
+ reply.printf("Fan number %d is invalid, must be between 0 and %u", fanNum, NUM_FANS);
+ }
+ else
+ {
+ bool seen = false;
+ Fan& fan = platform->GetFan(fanNum);
+
+ if (gb.Seen('I')) // Invert cooling
+ {
+ const int invert = gb.GetIValue();
+ if (invert < 0)
+ {
+ fan.Disable();
+ }
+ else
+ {
+ fan.SetInverted(invert > 0);
+ }
+ seen = true;
+ }
+
+ if (gb.Seen('F')) // Set PWM frequency
+ {
+ fan.SetPwmFrequency(gb.GetFValue());
+ seen = true;
+ }
+
+ if (gb.Seen('T')) // Set thermostatic trigger temperature
+ {
+ seen = true;
+ fan.SetTriggerTemperature(gb.GetFValue());
+ }
+
+ if (gb.Seen('B')) // Set blip time
+ {
+ seen = true;
+ fan.SetBlipTime(gb.GetFValue());
+ }
+
+ if (gb.Seen('L')) // Set minimum speed
+ {
+ seen = true;
+ fan.SetMinValue(gb.GetFValue());
+ }
+
+ if (gb.Seen('H')) // Set thermostatically-controller heaters
+ {
+ seen = true;
+ long heaters[HEATERS];
+ size_t numH = HEATERS;
+ gb.GetLongArray(heaters, numH);
+ // Note that M106 H-1 disables thermostatic mode. The following code implements that automatically.
+ uint16_t hh = 0;
+ for (size_t h = 0; h < numH; ++h)
+ {
+ const int hnum = heaters[h];
+ if (hnum >= 0 && hnum < HEATERS)
+ {
+ hh |= (1u << (unsigned int)hnum);
+ }
+ }
+ if (hh != 0)
+ {
+ platform->SetFanValue(fanNum, 1.0); // default the fan speed to full for safety
+ }
+ fan.SetHeatersMonitored(hh);
+ }
+
+ if (gb.Seen('S')) // Set new fan value - process this after processing 'H' or it may not be acted on
+ {
+ const float f = constrain<float>(gb.GetFValue(), 0.0, 255.0);
+ if (seen || seenFanNum)
+ {
+ platform->SetFanValue(fanNum, f);
+ }
+ else
+ {
+ // We are processing an M106 S### command with no other recognised parameters and we have a tool selected.
+ // Apply the fan speed setting to the fans in the fan mapping for the current tool.
+ lastDefaultFanSpeed = f;
+ SetMappedFanSpeed();
+ }
+ }
+ else if (gb.Seen('R'))
+ {
+ const int i = gb.GetIValue();
+ switch(i)
+ {
+ case 0:
+ case 1:
+ // Restore fan speed to value when print was paused
+ platform->SetFanValue(fanNum, pausedFanValues[fanNum]);
+ break;
+ case 2:
+ // Set the speeds of mapped fans to the last known value. Fan number is ignored.
+ SetMappedFanSpeed();
+ break;
+ default:
+ break;
+ }
+ }
+ else if (!seen)
+ {
+ reply.printf("Fan%i frequency: %dHz, speed: %d%%, min: %d%%, blip: %.2f, inverted: %s",
+ fanNum,
+ (int)(fan.GetPwmFrequency()),
+ (int)(fan.GetValue() * 100.0),
+ (int)(fan.GetMinValue() * 100.0),
+ fan.GetBlipTime(),
+ (fan.GetInverted()) ? "yes" : "no");
+ uint16_t hh = fan.GetHeatersMonitored();
+ if (hh != 0)
+ {
+ reply.catf(", trigger: %dC, heaters:", (int)fan.GetTriggerTemperature());
+ for (unsigned int i = 0; i < HEATERS; ++i)
+ {
+ if ((hh & (1u << i)) != 0)
+ {
+ reply.catf(" %u", i);
+ }
+ }
+ }
+ }
+ }
+ }
+ break;
+
+ case 107: // Fan off - deprecated
+ platform->SetFanValue(0, 0.0); //T3P3 as deprecated only applies to fan0
+ break;
+
+ case 108: // Cancel waiting for temperature
+ if (isWaiting)
+ {
+ cancelWait = true;
+ }
+ break;
+
+ case 109: // Deprecated in RRF, but widely generated by slicers
+ {
+ float temperature;
+ if (gb.Seen('R'))
+ {
+ gb.MachineState().waitWhileCooling = true;
+ temperature = gb.GetFValue();
+ }
+ else if (gb.Seen('S'))
+ {
+ gb.MachineState().waitWhileCooling = false;
+ temperature = gb.GetFValue();
+ }
+ else
+ {
+ break; // no target temperature given
+ }
+
+ Tool *tool;
+ if (gb.Seen('T'))
+ {
+ int toolNumber = gb.GetIValue();
+ toolNumber += gb.GetToolNumberAdjust();
+ tool = reprap.GetTool(toolNumber);
+ }
+ else
+ {
+ tool = reprap.GetCurrentOrDefaultTool();
+ }
+
+ SetToolHeaters(tool, temperature);
+ if (tool == nullptr)
+ {
+ break; // SetToolHeaters will already have generated an error message
+ }
+
+ // M109 implies waiting for temperature to be reached, so it doesn't make sense unless the tool has been selected.
+ // Sadly, many slicers use M109 without selecting the tool first. So we select it here if necessary.
+ if (tool != reprap.GetCurrentTool())
+ {
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+
+ newToolNumber = tool->Number();
+ StartToolChange(gb, true);
+ }
+ else
+ {
+ gb.SetState(GCodeState::m109ToolChangeComplete);
+ }
+ }
+ break;
+
+ case 110: // Set line numbers - line numbers are dealt with in the GCodeBuffer class
+ break;
+
+ case 111: // Debug level
+ if (gb.Seen('S'))
+ {
+ bool dbv = (gb.GetIValue() != 0);
+ if (gb.Seen('P'))
+ {
+ reprap.SetDebug(static_cast<Module>(gb.GetIValue()), dbv);
+ }
+ else
+ {
+ reprap.SetDebug(dbv);
+ }
+ }
+ else
+ {
+ reprap.PrintDebug();
+ }
+ break;
+
+ case 112: // Emergency stop - acted upon in Webserver, but also here in case it comes from USB etc.
+ DoEmergencyStop();
+ break;
+
+ case 114:
+ GetCurrentCoordinates(reply);
+ break;
+
+ case 115: // Print firmware version or set hardware type
+ if (gb.Seen('P'))
+ {
+ platform->SetBoardType((BoardType)gb.GetIValue());
+ }
+ else
+ {
+ reply.printf("FIRMWARE_NAME: %s FIRMWARE_VERSION: %s ELECTRONICS: %s", NAME, VERSION, platform->GetElectronicsString());
+#ifdef DUET_NG
+ const char* expansionName = DuetExpansion::GetExpansionBoardName();
+ if (expansionName != nullptr)
+ {
+ reply.catf(" + %s", expansionName);
+ }
+#endif
+ reply.catf(" FIRMWARE_DATE: %s", DATE);
+ }
+ break;
+
+ case 116: // Wait for set temperatures
+ {
+ bool seen = false;
+ if (gb.Seen('P'))
+ {
+ // Wait for the heaters associated with the specified tool to be ready
+ int toolNumber = gb.GetIValue();
+ toolNumber += gb.GetToolNumberAdjust();
+ if (!cancelWait && !ToolHeatersAtSetTemperatures(reprap.GetTool(toolNumber), true))
+ {
+ isWaiting = true;
+ return false;
+ }
+ seen = true;
+ }
+
+ if (gb.Seen('H'))
+ {
+ // Wait for specified heaters to be ready
+ long heaters[HEATERS];
+ size_t heaterCount = HEATERS;
+ gb.GetLongArray(heaters, heaterCount);
+ if (!cancelWait)
+ {
+ for (size_t i=0; i<heaterCount; i++)
+ {
+ if (!reprap.GetHeat()->HeaterAtSetTemperature(heaters[i], true))
+ {
+ isWaiting = true;
+ return false;
+ }
+ }
+ }
+ seen = true;
+ }
+
+ if (gb.Seen('C'))
+ {
+ // Wait for chamber heater to be ready
+ const int8_t chamberHeater = reprap.GetHeat()->GetChamberHeater();
+ if (chamberHeater != -1)
+ {
+ if (!cancelWait && !reprap.GetHeat()->HeaterAtSetTemperature(chamberHeater, true))
+ {
+ isWaiting = true;
+ return false;
+ }
+ }
+ seen = true;
+ }
+
+ // Wait for all heaters to be ready
+ if (!seen && !cancelWait && !reprap.GetHeat()->AllHeatersAtSetTemperatures(true))
+ {
+ isWaiting = true;
+ return false;
+ }
+
+ // If we get here, there is nothing more to wait for
+ cancelWait = isWaiting = false;
+ }
+ break;
+
+ case 117: // Display message
+ {
+ const char *msg = gb.GetUnprecedentedString(true);
+ reprap.SetMessage((msg == nullptr) ? "" : msg);
+ }
+ break;
+
+ case 119:
+ reply.copy("Endstops - ");
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ reply.catf("%c: %s, ", axisLetters[axis], TranslateEndStopResult(platform->Stopped(axis)));
+ }
+ reply.catf("Z probe: %s", TranslateEndStopResult(platform->GetZProbeResult()));
+ break;
+
+ case 120:
+ Push(gb);
+ break;
+
+ case 121:
+ Pop(gb);
+ break;
+
+ case 122:
+ {
+ int val = (gb.Seen('P')) ? gb.GetIValue() : 0;
+ if (val == 0)
+ {
+ reprap.Diagnostics(gb.GetResponseMessageType());
+ }
+ else
+ {
+ platform->DiagnosticTest(val);
+ }
+ }
+ break;
+
+ case 135: // Set PID sample interval
+ if (gb.Seen('S'))
+ {
+ platform->SetHeatSampleTime(gb.GetFValue() * 0.001); // Value is in milliseconds; we want seconds
+ }
+ else
+ {
+ reply.printf("Heat sample time is %.3f seconds", platform->GetHeatSampleTime());
+ }
+ break;
+
+ case 140: // Set bed temperature
+ {
+ int8_t bedHeater;
+ if (gb.Seen('H'))
+ {
+ bedHeater = gb.GetIValue();
+ if (bedHeater < 0)
+ {
+ // Make sure we stay within reasonable boundaries...
+ bedHeater = -1;
+
+ // If we're disabling the hot bed, make sure the old heater is turned off
+ reprap.GetHeat()->SwitchOff(reprap.GetHeat()->GetBedHeater());
+ }
+ else if (bedHeater >= HEATERS)
+ {
+ reply.copy("Invalid heater number!");
+ error = true;
+ break;
+ }
+ reprap.GetHeat()->SetBedHeater(bedHeater);
+
+ if (bedHeater < 0)
+ {
+ // Stop here if the hot bed has been disabled
+ break;
+ }
+ }
+ else
+ {
+ bedHeater = reprap.GetHeat()->GetBedHeater();
+ if (bedHeater < 0)
+ {
+ reply.copy("Hot bed is not present!");
+ error = true;
+ break;
+ }
+ }
+
+ if(gb.Seen('S'))
+ {
+ float temperature = gb.GetFValue();
+ if (temperature < NEARLY_ABS_ZERO)
+ {
+ reprap.GetHeat()->SwitchOff(bedHeater);
+ }
+ else
+ {
+ reprap.GetHeat()->SetActiveTemperature(bedHeater, temperature);
+ reprap.GetHeat()->Activate(bedHeater);
+ }
+ }
+ if(gb.Seen('R'))
+ {
+ reprap.GetHeat()->SetStandbyTemperature(bedHeater, gb.GetFValue());
+ }
+ }
+ break;
+
+ case 141: // Chamber temperature
+ {
+ bool seen = false;
+ if (gb.Seen('H'))
+ {
+ seen = true;
+
+ int heater = gb.GetIValue();
+ if (heater < 0)
+ {
+ const int8_t currentHeater = reprap.GetHeat()->GetChamberHeater();
+ if (currentHeater != -1)
+ {
+ reprap.GetHeat()->SwitchOff(currentHeater);
+ }
+
+ reprap.GetHeat()->SetChamberHeater(-1);
+ }
+ else if (heater < HEATERS)
+ {
+ reprap.GetHeat()->SetChamberHeater(heater);
+ }
+ else
+ {
+ reply.copy("Bad heater number specified!");
+ error = true;
+ }
+ }
+
+ if (gb.Seen('S'))
+ {
+ seen = true;
+
+ const int8_t currentHeater = reprap.GetHeat()->GetChamberHeater();
+ if (currentHeater != -1)
+ {
+ float temperature = gb.GetFValue();
+
+ if (temperature < NEARLY_ABS_ZERO)
+ {
+ reprap.GetHeat()->SwitchOff(currentHeater);
+ }
+ else
+ {
+ reprap.GetHeat()->SetActiveTemperature(currentHeater, temperature);
+ reprap.GetHeat()->Activate(currentHeater);
+ }
+ }
+ else
+ {
+ reply.copy("No chamber heater has been set up yet!");
+ error = true;
+ }
+ }
+
+ if (!seen)
+ {
+ const int8_t currentHeater = reprap.GetHeat()->GetChamberHeater();
+ if (currentHeater != -1)
+ {
+ reply.printf("Chamber heater %d is currently at %.1fC", currentHeater, reprap.GetHeat()->GetTemperature(currentHeater));
+ }
+ else
+ {
+ reply.copy("No chamber heater has been configured yet.");
+ }
+ }
+ }
+ break;
+
+ case 143: // Set temperature limit
+ {
+ const int heater = (gb.Seen('H')) ? gb.GetIValue() : 1; // default to extruder 1 if no heater number provided
+ if (heater < 0 || heater >= HEATERS)
+ {
+ reply.copy("Invalid heater number");
+ error = true;
+ }
+ else if (gb.Seen('S'))
+ {
+ const float limit = gb.GetFValue();
+ if (limit > BAD_LOW_TEMPERATURE && limit < BAD_ERROR_TEMPERATURE)
+ {
+ reprap.GetHeat()->SetTemperatureLimit(heater, limit);
+ }
+ else
+ {
+ reply.copy("Invalid temperature limit");
+ error = true;
+ }
+ }
+ else
+ {
+ reply.printf("Temperature limit for heater %d is %.1fC", heater, reprap.GetHeat()->GetTemperatureLimit(heater));
+ }
+ }
+ break;
+
+ case 144: // Set bed to standby
+ {
+ const int8_t bedHeater = reprap.GetHeat()->GetBedHeater();
+ if (bedHeater >= 0)
+ {
+ reprap.GetHeat()->Standby(bedHeater);
+ }
+ }
+ break;
+
+ case 190: // Set bed temperature and wait
+ case 191: // Set chamber temperature and wait
+ {
+ const int8_t heater = (code == 191) ? reprap.GetHeat()->GetChamberHeater() : reprap.GetHeat()->GetBedHeater();
+ if (heater >= 0)
+ {
+ float temperature;
+ bool waitWhenCooling;
+ if (gb.Seen('R'))
+ {
+ waitWhenCooling = true;
+ temperature = gb.GetFValue();
+ }
+ else if (gb.Seen('S'))
+ {
+ waitWhenCooling = false;
+ temperature = gb.GetFValue();
+ }
+ else
+ {
+ break; // no target temperature given
+ }
+
+ reprap.GetHeat()->SetActiveTemperature(heater, temperature);
+ reprap.GetHeat()->Activate(heater);
+ if (cancelWait || reprap.GetHeat()->HeaterAtSetTemperature(heater, waitWhenCooling))
+ {
+ cancelWait = isWaiting = false;
+ break;
+ }
+ // In Marlin emulation mode we should return some sort of (undocumented) message here every second...
+ isWaiting = true;
+ return false;
+ }
+ }
+ break;
+
+ case 201: // Set/print axis accelerations
+ {
+ bool seen = false;
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ platform->SetAcceleration(axis, gb.GetFValue() * distanceScale);
+ seen = true;
+ }
+ }
+
+ if (gb.Seen(extrudeLetter))
+ {
+ seen = true;
+ float eVals[MaxExtruders];
+ size_t eCount = numExtruders;
+ gb.GetFloatArray(eVals, eCount, true);
+ for (size_t e = 0; e < eCount; e++)
+ {
+ platform->SetAcceleration(numAxes + e, eVals[e] * distanceScale);
+ }
+ }
+
+ if (gb.Seen('P'))
+ {
+ // Set max average printing acceleration
+ platform->SetMaxAverageAcceleration(gb.GetFValue() * distanceScale);
+ seen = true;
+ }
+
+ if (!seen)
+ {
+ reply.printf("Accelerations: ");
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ reply.catf("%c: %.1f, ", axisLetters[axis], platform->Acceleration(axis) / distanceScale);
+ }
+ reply.cat("E:");
+ char sep = ' ';
+ for (size_t extruder = 0; extruder < numExtruders; extruder++)
+ {
+ reply.catf("%c%.1f", sep, platform->Acceleration(extruder + numAxes) / distanceScale);
+ sep = ':';
+ }
+ reply.catf(", avg. printing: %.1f", platform->GetMaxAverageAcceleration());
+ }
+ }
+ break;
+
+ case 203: // Set/print maximum feedrates
+ {
+ bool seen = false;
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ platform->SetMaxFeedrate(axis, gb.GetFValue() * distanceScale * secondsToMinutes); // G Code feedrates are in mm/minute; we need mm/sec
+ seen = true;
+ }
+ }
+
+ if (gb.Seen(extrudeLetter))
+ {
+ seen = true;
+ float eVals[MaxExtruders];
+ size_t eCount = numExtruders;
+ gb.GetFloatArray(eVals, eCount, true);
+ for (size_t e = 0; e < eCount; e++)
+ {
+ platform->SetMaxFeedrate(numAxes + e, eVals[e] * distanceScale * secondsToMinutes);
+ }
+ }
+
+ if (!seen)
+ {
+ reply.copy("Maximum feedrates: ");
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ reply.catf("%c: %.1f, ", axisLetters[axis], platform->MaxFeedrate(axis) / (distanceScale * secondsToMinutes));
+ }
+ reply.cat("E:");
+ char sep = ' ';
+ for (size_t extruder = 0; extruder < numExtruders; extruder++)
+ {
+ reply.catf("%c%.1f", sep, platform->MaxFeedrate(extruder + numAxes) / (distanceScale * secondsToMinutes));
+ sep = ':';
+ }
+ }
+ }
+ break;
+
+ case 205: //M205 advanced settings: minimum travel speed S=while printing T=travel only, B=minimum segment time X= maximum xy jerk, Z=maximum Z jerk
+ // This is superseded in this firmware by M codes for the separate types (e.g. M566).
+ break;
+
+ case 206: // Offset axes - Deprecated
+ result = OffsetAxes(gb);
+ break;
+
+ case 207: // Set firmware retraction details
+ {
+ bool seen = false;
+ if (gb.Seen('S'))
+ {
+ retractLength = max<float>(gb.GetFValue(), 0.0);
+ seen = true;
+ }
+ if (gb.Seen('R'))
+ {
+ retractExtra = max<float>(gb.GetFValue(), -retractLength);
+ seen = true;
+ }
+ if (gb.Seen('F'))
+ {
+ unRetractSpeed = retractSpeed = max<float>(gb.GetFValue(), 60.0);
+ seen = true;
+ }
+ if (gb.Seen('T')) // must do this one after 'F'
+ {
+ unRetractSpeed = max<float>(gb.GetFValue(), 60.0);
+ seen = true;
+ }
+ if (gb.Seen('Z'))
+ {
+ retractHop = max<float>(gb.GetFValue(), 0.0);
+ seen = true;
+ }
+ if (!seen)
+ {
+ reply.printf("Retraction settings: length %.2f/%.2fmm, speed %d/%dmm/min, Z hop %.2fmm",
+ retractLength, retractLength + retractExtra, (int)retractSpeed, (int)unRetractSpeed, retractHop);
+ }
+ }
+ break;
+
+ case 208: // Set/print maximum axis lengths. If there is an S parameter with value 1 then we set the min value, else we set the max value.
+ {
+ bool setMin = (gb.Seen('S') ? (gb.GetIValue() == 1) : false);
+ bool seen = false;
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ float value = gb.GetFValue() * distanceScale;
+ if (setMin)
+ {
+ platform->SetAxisMinimum(axis, value);
+ }
+ else
+ {
+ platform->SetAxisMaximum(axis, value);
+ }
+ seen = true;
+ }
+ }
+
+ if (!seen)
+ {
+ reply.copy("Axis limits ");
+ char sep = '-';
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ reply.catf("%c %c: %.1f min, %.1f max", sep, axisLetters[axis], platform->AxisMinimum(axis),
+ platform->AxisMaximum(axis));
+ sep = ',';
+ }
+ }
+ }
+ break;
+
+ case 210: // Set/print homing feed rates
+ // This is no longer used, but for backwards compatibility we don't report an error
+ break;
+
+ case 220: // Set/report speed factor override percentage
+ if (gb.Seen('S'))
+ {
+ float newSpeedFactor = (gb.GetFValue() / 100.0) * secondsToMinutes; // include the conversion from mm/minute to mm/second
+ if (newSpeedFactor > 0.0)
+ {
+ gb.MachineState().feedrate *= newSpeedFactor / speedFactor;
+ if (segmentsLeft != 0 && !moveBuffer.isFirmwareRetraction)
+ {
+ // The last move has not gone yet, so we can update it
+ moveBuffer.feedRate *= newSpeedFactor / speedFactor;
+ }
+ speedFactor = newSpeedFactor;
+ }
+ else
+ {
+ reply.printf("Invalid speed factor specified.");
+ error = true;
+ }
+ }
+ else
+ {
+ reply.printf("Speed factor override: %.1f%%", speedFactor * minutesToSeconds * 100.0);
+ }
+ break;
+
+ case 221: // Set/report extrusion factor override percentage
+ {
+ int32_t extruder = 0;
+ bool dummy;
+ gb.TryGetIValue('D', extruder, dummy);
+
+ if (extruder >= 0 && extruder < (int32_t)numExtruders)
+ {
+ if (gb.Seen('S')) // S parameter sets the override percentage
+ {
+ const float extrusionFactor = gb.GetFValue() / 100.0;
+ if (extrusionFactor >= 0.0)
+ {
+ if (segmentsLeft != 0 && !moveBuffer.isFirmwareRetraction)
+ {
+ moveBuffer.coords[extruder + numAxes] *= extrusionFactor/extrusionFactors[extruder]; // last move not gone, so update it
+ }
+ extrusionFactors[extruder] = extrusionFactor;
+ }
+ }
+ else
+ {
+ reply.printf("Extrusion factor override for extruder %d: %.1f%%", extruder, extrusionFactors[extruder] * 100.0);
+ }
+ }
+ }
+ break;
+
+ // For case 226, see case 25
+
+ case 280: // Servos
+ if (gb.Seen('P'))
+ {
+ const int servoIndex = gb.GetIValue();
+ Pin servoPin;
+ bool invert;
+ if (platform->GetFirmwarePin(servoIndex, PinAccess::servo, servoPin, invert))
+ {
+ if (gb.Seen('I') && gb.GetIValue() > 0)
+ {
+ invert = !invert;
+ }
+
+ if (gb.Seen('S'))
+ {
+ float angleOrWidth = gb.GetFValue();
+ if (angleOrWidth < 0.0)
+ {
+ // Disable the servo by setting the pulse width to zero
+ Platform::WriteAnalog(servoPin, (invert) ? 1.0 : 0.0, ServoRefreshFrequency);
+ }
+ else
+ {
+ if (angleOrWidth < MinServoPulseWidth)
+ {
+ // User gave an angle so convert it to a pulse width in microseconds
+ angleOrWidth = (min<float>(angleOrWidth, 180.0) * ((MaxServoPulseWidth - MinServoPulseWidth) / 180.0)) + MinServoPulseWidth;
+ }
+ else if (angleOrWidth > MaxServoPulseWidth)
+ {
+ angleOrWidth = MaxServoPulseWidth;
+ }
+ float pwm = angleOrWidth * (ServoRefreshFrequency/1e6);
+ if (invert)
+ {
+ pwm = 1.0 - pwm;
+ }
+ Platform::WriteAnalog(servoPin, pwm, ServoRefreshFrequency);
+ }
+ }
+ // We don't currently allow the servo position to be read back
+ }
+ else
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Error: Invalid servo index %d in M280 command\n", servoIndex);
+ }
+ }
+ break;
+
+ case 300: // Beep
+ {
+ const int ms = (gb.Seen('P')) ? gb.GetIValue() : 1000; // time in milliseconds
+ const int freq = (gb.Seen('S')) ? gb.GetIValue() : 4600; // 4600Hz produces the loudest sound on a PanelDue
+ reprap.Beep(freq, ms);
+ }
+ break;
+
+ case 301: // Set/report hot end PID values
+ SetPidParameters(gb, 1, reply);
+ break;
+
+ case 302: // Allow, deny or report cold extrudes
+ if (gb.Seen('P'))
+ {
+ reprap.GetHeat()->AllowColdExtrude(gb.GetIValue() > 0);
+ }
+ else
+ {
+ reply.printf("Cold extrusion is %s, use M302 P[1/0] to allow/deny it",
+ reprap.GetHeat()->ColdExtrude() ? "allowed" : "denied");
+ }
+ break;
+
+ case 303: // Run PID tuning
+ if (gb.Seen('H'))
+ {
+ const size_t heater = gb.GetIValue();
+ const float temperature = (gb.Seen('S')) ? gb.GetFValue() : 225.0;
+ const float maxPwm = (gb.Seen('P')) ? gb.GetFValue() : 0.5;
+ if (heater < HEATERS && maxPwm >= 0.1 && maxPwm <= 1.0 && temperature >= 55.0 && temperature <= reprap.GetHeat()->GetTemperatureLimit(heater))
+ {
+ reprap.GetHeat()->StartAutoTune(heater, temperature, maxPwm, reply);
+ }
+ else
+ {
+ reply.printf("Bad parameter in M303 command");
+ }
+ }
+ else
+ {
+ reprap.GetHeat()->GetAutoTuneStatus(reply);
+ }
+ break;
+
+ case 304: // Set/report heated bed PID values
+ {
+ const int8_t bedHeater = reprap.GetHeat()->GetBedHeater();
+ if (bedHeater >= 0)
+ {
+ SetPidParameters(gb, bedHeater, reply);
+ }
+ }
+ break;
+
+ case 305: // Set/report specific heater parameters
+ SetHeaterParameters(gb, reply);
+ break;
+
+ case 307: // Set heater process model parameters
+ if (gb.Seen('H'))
+ {
+ size_t heater = gb.GetIValue();
+ if (heater < HEATERS)
+ {
+ const FopDt& model = reprap.GetHeat()->GetHeaterModel(heater);
+ bool seen = false;
+ float gain = model.GetGain(),
+ tc = model.GetTimeConstant(),
+ td = model.GetDeadTime(),
+ maxPwm = model.GetMaxPwm();
+ int32_t dontUsePid = model.UsePid() ? 0 : 1;
+
+ gb.TryGetFValue('A', gain, seen);
+ gb.TryGetFValue('C', tc, seen);
+ gb.TryGetFValue('D', td, seen);
+ gb.TryGetIValue('B', dontUsePid, seen);
+ gb.TryGetFValue('S', maxPwm, seen);
+
+ if (seen)
+ {
+ if (reprap.GetHeat()->SetHeaterModel(heater, gain, tc, td, maxPwm, dontUsePid == 0))
+ {
+ reprap.GetHeat()->UseModel(heater, true);
+ }
+ else
+ {
+ reply.copy("Error: bad model parameters");
+ }
+ }
+ else if (!model.IsEnabled())
+ {
+ reply.printf("Heater %u is disabled", heater);
+ }
+ else
+ {
+ reply.printf("Heater %u model: gain %.1f, time constant %.1f, dead time %.1f, max PWM %.2f, in use: %s, mode: %s",
+ heater, model.GetGain(), model.GetTimeConstant(), model.GetDeadTime(), model.GetMaxPwm(),
+ (reprap.GetHeat()->IsModelUsed(heater)) ? "yes" : "no",
+ (model.UsePid()) ? "PID" : "bang-bang");
+ if (model.UsePid())
+ {
+ // When reporting the PID parameters, we scale them by 255 for compatibility with older firmware and other firmware
+ const PidParams& spParams = model.GetPidParameters(false);
+ const float scaledSpKp = 255.0 * spParams.kP;
+ reply.catf("\nSetpoint change: P%.1f, I%.2f, D%.1f",
+ scaledSpKp, scaledSpKp * spParams.recipTi, scaledSpKp * spParams.tD);
+ const PidParams& ldParams = model.GetPidParameters(true);
+ const float scaledLoadKp = 255.0 * ldParams.kP;
+ reply.catf("\nLoad change: P%.1f, I%.2f, D%.1f",
+ scaledLoadKp, scaledLoadKp * ldParams.recipTi, scaledLoadKp * ldParams.tD);
+ }
+ }
+ }
+ }
+ break;
+
+ case 350: // Set/report microstepping
+ {
+ // interp is currently an int not a bool, because we use special values of interp to set the chopper control register
+ int32_t interp = 0;
+ bool dummy;
+ gb.TryGetIValue('I', interp, dummy);
+
+ bool seen = false;
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ seen = true;
+ const int microsteps = gb.GetIValue();
+ if (ChangeMicrostepping(axis, microsteps, interp))
+ {
+ SetAxisNotHomed(axis);
+ }
+ else
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Drive %c does not support %dx microstepping%s\n",
+ axisLetters[axis], microsteps, (interp) ? " with interpolation" : "");
+ }
+ }
+ }
+
+ if (gb.Seen(extrudeLetter))
+ {
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ seen = true;
+ long eVals[MaxExtruders];
+ size_t eCount = numExtruders;
+ gb.GetLongArray(eVals, eCount);
+ for (size_t e = 0; e < eCount; e++)
+ {
+ if (!ChangeMicrostepping(numAxes + e, (int)eVals[e], interp))
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Drive E%u does not support %dx microstepping%s\n",
+ e, (int)eVals[e], (interp) ? " with interpolation" : "");
+ }
+ }
+ }
+
+ if (!seen)
+ {
+ reply.copy("Microstepping - ");
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ bool interp;
+ const int microsteps = platform->GetMicrostepping(axis, interp);
+ reply.catf("%c:%d%s, ", axisLetters[axis], microsteps, (interp) ? "(on)" : "");
+ }
+ reply.cat("E");
+ for (size_t extruder = 0; extruder < numExtruders; extruder++)
+ {
+ bool interp;
+ const int microsteps = platform->GetMicrostepping(extruder + numAxes, interp);
+ reply.catf(":%d%s", microsteps, (interp) ? "(on)" : "");
+ }
+ }
+ }
+ break;
+
+ case 400: // Wait for current moves to finish
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ break;
+
+ case 404: // Filament width and nozzle diameter
+ {
+ bool seen = false;
+
+ if (gb.Seen('N'))
+ {
+ platform->SetFilamentWidth(gb.GetFValue());
+ seen = true;
+ }
+ if (gb.Seen('D'))
+ {
+ platform->SetNozzleDiameter(gb.GetFValue());
+ seen = true;
+ }
+
+ if (!seen)
+ {
+ reply.printf("Filament width: %.2fmm, nozzle diameter: %.2fmm", platform->GetFilamentWidth(), platform->GetNozzleDiameter());
+ }
+ }
+ break;
+
+ case 408: // Get status in JSON format
+ {
+ int type = gb.Seen('S') ? gb.GetIValue() : 0;
+ int seq = gb.Seen('R') ? gb.GetIValue() : -1;
+
+ OutputBuffer *statusResponse = nullptr;
+ switch (type)
+ {
+ case 0:
+ case 1:
+ statusResponse = reprap.GetLegacyStatusResponse(type + 2, seq);
+ break;
+
+ case 2:
+ case 3:
+ case 4:
+ statusResponse = reprap.GetStatusResponse(type - 1, (&gb == auxGCode) ? ResponseSource::AUX : ResponseSource::Generic);
+ break;
+
+ case 5:
+ statusResponse = reprap.GetConfigResponse();
+ break;
+ }
+
+ if (statusResponse != nullptr)
+ {
+ UnlockAll(gb);
+ statusResponse->cat('\n');
+ HandleReply(gb, false, statusResponse);
+ return true;
+ }
+ }
+ break;
+
+ case 500: // Store parameters in EEPROM
+ reprap.GetPlatform()->WriteNvData();
+ break;
+
+ case 501: // Load parameters from EEPROM
+ reprap.GetPlatform()->ReadNvData();
+ if (gb.Seen('S'))
+ {
+ reprap.GetPlatform()->SetAutoSave(gb.GetIValue() > 0);
+ }
+ break;
+
+ case 502: // Revert to default "factory settings"
+ reprap.GetPlatform()->ResetNvData();
+ break;
+
+ case 503: // List variable settings
+ {
+ if (!LockFileSystem(gb))
+ {
+ return false;
+ }
+
+ // Need a valid output buffer to continue...
+ OutputBuffer *configResponse;
+ if (!OutputBuffer::Allocate(configResponse))
+ {
+ // No buffer available, try again later
+ return false;
+ }
+
+ // Read the entire file
+ FileStore * const f = platform->GetFileStore(platform->GetSysDir(), platform->GetConfigFile(), false);
+ if (f == nullptr)
+ {
+ error = true;
+ reply.copy("Configuration file not found!");
+ }
+ else
+ {
+ char fileBuffer[FILE_BUFFER_SIZE];
+ size_t bytesRead,
+ bytesLeftForWriting = OutputBuffer::GetBytesLeft(configResponse);
+ while ((bytesRead = f->Read(fileBuffer, FILE_BUFFER_SIZE)) > 0 && bytesLeftForWriting > 0)
+ {
+ // Don't write more data than we can process
+ if (bytesRead < bytesLeftForWriting)
+ {
+ bytesLeftForWriting -= bytesRead;
+ }
+ else
+ {
+ bytesRead = bytesLeftForWriting;
+ bytesLeftForWriting = 0;
+ }
+
+ // Write it
+ configResponse->cat(fileBuffer, bytesRead);
+ }
+ f->Close();
+
+ UnlockAll(gb);
+ HandleReply(gb, false, configResponse);
+ return true;
+ }
+ }
+ break;
+
+ case 540: // Set/report MAC address
+ if (gb.Seen('P'))
+ {
+ SetMACAddress(gb);
+ }
+ else
+ {
+ const byte* mac = platform->MACAddress();
+ reply.printf("MAC: %x:%x:%x:%x:%x:%x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+ }
+ break;
+
+ case 550: // Set/report machine name
+ if (gb.Seen('P'))
+ {
+ reprap.SetName(gb.GetString());
+ }
+ else
+ {
+ reply.printf("RepRap name: %s", reprap.GetName());
+ }
+ break;
+
+ case 551: // Set password (no option to report it)
+ if (gb.Seen('P'))
+ {
+ reprap.SetPassword(gb.GetString());
+ }
+ break;
+
+ case 552: // Enable/Disable network and/or Set/Get IP address
+ {
+ bool seen = false;
+ if (gb.Seen('P'))
+ {
+ seen = true;
+ SetEthernetAddress(gb, code);
+ }
+
+ if (gb.Seen('R'))
+ {
+ reprap.GetNetwork()->SetHttpPort(gb.GetIValue());
+ seen = true;
+ }
+
+ // Process this one last in case the IP address is changed and the network enabled in the same command
+ if (gb.Seen('S')) // Has the user turned the network on or off?
+ {
+ seen = true;
+ if (gb.GetIValue() != 0)
+ {
+ reprap.GetNetwork()->Enable();
+ }
+ else
+ {
+ reprap.GetNetwork()->Disable();
+ }
+ }
+
+ if (!seen)
+ {
+ const byte *config_ip = platform->IPAddress();
+ const byte *actual_ip = reprap.GetNetwork()->IPAddress();
+ reply.printf("Network is %s, configured IP address: %d.%d.%d.%d, actual IP address: %d.%d.%d.%d, HTTP port: %d",
+ reprap.GetNetwork()->IsEnabled() ? "enabled" : "disabled",
+ config_ip[0], config_ip[1], config_ip[2], config_ip[3], actual_ip[0], actual_ip[1], actual_ip[2], actual_ip[3],
+ reprap.GetNetwork()->GetHttpPort());
+ }
+ }
+ break;
+
+ case 553: // Set/Get netmask
+ if (gb.Seen('P'))
+ {
+ SetEthernetAddress(gb, code);
+ }
+ else
+ {
+ const byte *nm = platform->NetMask();
+ reply.printf("Net mask: %d.%d.%d.%d ", nm[0], nm[1], nm[2], nm[3]);
+ }
+ break;
+
+ case 554: // Set/Get gateway
+ if (gb.Seen('P'))
+ {
+ SetEthernetAddress(gb, code);
+ }
+ else
+ {
+ const byte *gw = platform->GateWay();
+ reply.printf("Gateway: %d.%d.%d.%d ", gw[0], gw[1], gw[2], gw[3]);
+ }
+ break;
+
+ case 555: // Set/report firmware type to emulate
+ if (gb.Seen('P'))
+ {
+ platform->SetEmulating((Compatibility) gb.GetIValue());
+ }
+ else
+ {
+ reply.copy("Emulating ");
+ switch (platform->Emulating())
+ {
+ case me:
+ case reprapFirmware:
+ reply.cat("RepRap Firmware (i.e. in native mode)");
+ break;
+
+ case marlin:
+ reply.cat("Marlin");
+ break;
+
+ case teacup:
+ reply.cat("Teacup");
+ break;
+
+ case sprinter:
+ reply.cat("Sprinter");
+ break;
+
+ case repetier:
+ reply.cat("Repetier");
+ break;
+
+ default:
+ reply.catf("Unknown: (%d)", platform->Emulating());
+ }
+ }
+ break;
+
+ case 556: // Axis compensation (we support only X, Y, Z)
+ if (gb.Seen('S'))
+ {
+ float value = gb.GetFValue();
+ for (size_t axis = 0; axis <= Z_AXIS; axis++)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ reprap.GetMove()->SetAxisCompensation(axis, gb.GetFValue() / value);
+ }
+ }
+ }
+ else
+ {
+ reply.printf("Axis compensations - XY: %.5f, YZ: %.5f, ZX: %.5f",
+ reprap.GetMove()->AxisCompensation(X_AXIS), reprap.GetMove()->AxisCompensation(Y_AXIS),
+ reprap.GetMove()->AxisCompensation(Z_AXIS));
+ }
+ break;
+
+ case 557: // Set/report Z probe point coordinates
+ if (gb.Seen('P'))
+ {
+ const int point = gb.GetIValue();
+ if (point < 0 || (unsigned int)point >= MaxProbePoints)
+ {
+ reprap.GetPlatform()->Message(GENERIC_MESSAGE, "Z probe point index out of range.\n");
+ }
+ else
+ {
+ bool seen = false;
+ if (gb.Seen(axisLetters[X_AXIS]))
+ {
+ reprap.GetMove()->SetXBedProbePoint(point, gb.GetFValue());
+ seen = true;
+ }
+ if (gb.Seen(axisLetters[Y_AXIS]))
+ {
+ reprap.GetMove()->SetYBedProbePoint(point, gb.GetFValue());
+ seen = true;
+ }
+
+ if (!seen)
+ {
+ reply.printf("Probe point %d - [%.1f, %.1f]", point, reprap.GetMove()->XBedProbePoint(point), reprap.GetMove()->YBedProbePoint(point));
+ }
+ }
+ }
+ else
+ {
+ LockMovement(gb); // to ensure that probing is not already in progress
+ error = DefineGrid(gb, reply);
+ }
+ break;
+
+ case 558: // Set or report Z probe type and for which axes it is used
+ {
+ bool seenAxes = false, seenType = false, seenParam = false;
+ uint32_t zProbeAxes = platform->GetZProbeAxes();
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ if (gb.GetIValue() > 0)
+ {
+ zProbeAxes |= (1u << axis);
+ }
+ else
+ {
+ zProbeAxes &= ~(1u << axis);
+ }
+ seenAxes = true;
+ }
+ }
+ if (seenAxes)
+ {
+ platform->SetZProbeAxes(zProbeAxes);
+ }
+
+ // We must get and set the Z probe type first before setting the dive height etc., because different probe types may have different parameters
+ if (gb.Seen('P')) // probe type
+ {
+ platform->SetZProbeType(gb.GetIValue());
+ seenType = true;
+ }
+
+ ZProbeParameters params = platform->GetZProbeParameters();
+ gb.TryGetFValue('H', params.diveHeight, seenParam); // dive height
+
+ if (gb.Seen('F')) // feed rate i.e. probing speed
+ {
+ params.probeSpeed = gb.GetFValue() * secondsToMinutes;
+ seenParam = true;
+ }
+
+ if (gb.Seen('T')) // travel speed to probe point
+ {
+ params.travelSpeed = gb.GetFValue() * secondsToMinutes;
+ seenParam = true;
+ }
+
+ if (gb.Seen('I'))
+ {
+ params.invertReading = (gb.GetIValue() != 0);
+ seenParam = true;
+ }
+
+ gb.TryGetFValue('R', params.recoveryTime, seenParam); // Z probe recovery time
+ gb.TryGetFValue('S', params.extraParam, seenParam); // extra parameter for experimentation
+
+ if (seenParam)
+ {
+ platform->SetZProbeParameters(params);
+ }
+
+ if (!(seenAxes || seenType || seenParam))
+ {
+ reply.printf("Z Probe type %d, invert %s, dive height %.1fmm, probe speed %dmm/min, travel speed %dmm/min, recovery time %.2f sec",
+ platform->GetZProbeType(), (params.invertReading) ? "yes" : "no", params.diveHeight,
+ (int)(params.probeSpeed * minutesToSeconds), (int)(params.travelSpeed * minutesToSeconds), params.recoveryTime);
+ if (platform->GetZProbeType() == ZProbeTypeDelta)
+ {
+ reply.catf(", extra parameter %.2f", params.extraParam);
+ }
+ reply.cat(", used for axes:");
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ if ((zProbeAxes & (1u << axis)) != 0)
+ {
+ reply.catf(" %c", axisLetters[axis]);
+ }
+ }
+ }
+ }
+ break;
+
+ case 559: // Upload config.g or another gcode file to put in the sys directory
+ {
+ const char* str = (gb.Seen('P') ? gb.GetString() : platform->GetConfigFile());
+ const bool ok = OpenFileToWrite(gb, platform->GetSysDir(), str);
+ if (ok)
+ {
+ reply.printf("Writing to file: %s", str);
+ }
+ else
+ {
+ reply.printf("Can't open file %s for writing.", str);
+ error = true;
+ }
+ }
+ break;
+
+ case 560: // Upload reprap.htm or another web interface file
+ {
+ const char* str = (gb.Seen('P') ? gb.GetString() : INDEX_PAGE_FILE);
+ const bool ok = OpenFileToWrite(gb, platform->GetWebDir(), str);
+ if (ok)
+ {
+ reply.printf("Writing to file: %s", str);
+ }
+ else
+ {
+ reply.printf("Can't open file %s for writing.", str);
+ error = true;
+ }
+ }
+ break;
+
+ case 561:
+ reprap.GetMove()->SetIdentityTransform();
+ break;
+
+ case 562: // Reset temperature fault - use with great caution
+ if (gb.Seen('P'))
+ {
+ const int heater = gb.GetIValue();
+ if (heater >= 0 && heater < HEATERS)
+ {
+ reprap.ClearTemperatureFault(heater);
+ }
+ else
+ {
+ reply.copy("Invalid heater number.\n");
+ error = true;
+ }
+ }
+ break;
+
+ case 563: // Define tool
+ ManageTool(gb, reply);
+ break;
+
+ case 564: // Think outside the box?
+ if (gb.Seen('S'))
+ {
+ limitAxes = (gb.GetIValue() != 0);
+ }
+ else
+ {
+ reply.printf("Movement outside the bed is %spermitted", (limitAxes) ? "not " : "");
+ }
+ break;
+
+ case 566: // Set/print maximum jerk speeds
+ {
+ bool seen = false;
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ platform->SetInstantDv(axis, gb.GetFValue() * distanceScale * secondsToMinutes); // G Code feedrates are in mm/minute; we need mm/sec
+ seen = true;
+ }
+ }
+
+ if (gb.Seen(extrudeLetter))
+ {
+ seen = true;
+ float eVals[MaxExtruders];
+ size_t eCount = numExtruders;
+ gb.GetFloatArray(eVals, eCount, true);
+ for (size_t e = 0; e < eCount; e++)
+ {
+ platform->SetInstantDv(numAxes + e, eVals[e] * distanceScale * secondsToMinutes);
+ }
+ }
+ else if (!seen)
+ {
+ reply.copy("Maximum jerk rates: ");
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ reply.catf("%c: %.1f, ", axisLetters[axis], platform->ConfiguredInstantDv(axis) / (distanceScale * secondsToMinutes));
+ }
+ reply.cat("E:");
+ char sep = ' ';
+ for (size_t extruder = 0; extruder < numExtruders; extruder++)
+ {
+ reply.catf("%c%.1f", sep, platform->ConfiguredInstantDv(extruder + numAxes) / (distanceScale * secondsToMinutes));
+ sep = ':';
+ }
+ }
+ }
+ break;
+
+ case 567: // Set/report tool mix ratios
+ if (gb.Seen('P'))
+ {
+ int8_t tNumber = gb.GetIValue();
+ Tool* const tool = reprap.GetTool(tNumber);
+ if (tool != NULL)
+ {
+ if (gb.Seen(extrudeLetter))
+ {
+ float eVals[MaxExtruders];
+ size_t eCount = tool->DriveCount();
+ gb.GetFloatArray(eVals, eCount, false);
+ if (eCount != tool->DriveCount())
+ {
+ reply.printf("Setting mix ratios - wrong number of E drives: %s", gb.Buffer());
+ }
+ else
+ {
+ tool->DefineMix(eVals);
+ }
+ }
+ else
+ {
+ reply.printf("Tool %d mix ratios:", tNumber);
+ char sep = ' ';
+ for (size_t drive = 0; drive < tool->DriveCount(); drive++)
+ {
+ reply.catf("%c%.3f", sep, tool->GetMix()[drive]);
+ sep = ':';
+ }
+ }
+ }
+ }
+ break;
+
+ case 568: // Turn on/off automatic tool mixing
+ if (gb.Seen('P'))
+ {
+ Tool* const tool = reprap.GetTool(gb.GetIValue());
+ if (tool != NULL)
+ {
+ if (gb.Seen('S'))
+ {
+ tool->SetMixing(gb.GetIValue() != 0);
+ }
+ else
+ {
+ reply.printf("Tool %d mixing is %s", tool->Number(), (tool->GetMixing()) ? "enabled" : "disabled");
+ }
+ }
+ }
+ break;
+
+ case 569: // Set/report axis direction
+ if (gb.Seen('P'))
+ {
+ const size_t drive = gb.GetIValue();
+ if (drive < DRIVES)
+ {
+ bool seen = false;
+ if (gb.Seen('S'))
+ {
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ platform->SetDirectionValue(drive, gb.GetIValue() != 0);
+ seen = true;
+ }
+ if (gb.Seen('R'))
+ {
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ platform->SetEnableValue(drive, gb.GetIValue() != 0);
+ seen = true;
+ }
+ if (gb.Seen('T'))
+ {
+ platform->SetDriverStepTiming(drive, gb.GetFValue());
+ seen = true;
+ }
+ bool badParameter = false;
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ badParameter = true;
+ }
+ }
+ if (gb.Seen(extrudeLetter))
+ {
+ badParameter = true;
+ }
+ if (badParameter)
+ {
+ platform->Message(GENERIC_MESSAGE, "Error: M569 no longer accepts XYZE parameters; use M584 instead\n");
+ }
+ else if (!seen)
+ {
+ reply.printf("A %d sends drive %u forwards, a %d enables it, and the minimum pulse width is %.1f microseconds",
+ (int)platform->GetDirectionValue(drive), drive,
+ (int)platform->GetEnableValue(drive),
+ platform->GetDriverStepTiming(drive));
+ }
+ }
+ }
+ break;
+
+ case 570: // Set/report heater timeout
+ if (gb.Seen('H'))
+ {
+ const size_t heater = gb.GetIValue();
+ bool seen = false;
+ if (heater < HEATERS)
+ {
+ float maxTempExcursion, maxFaultTime;
+ reprap.GetHeat()->GetHeaterProtection(heater, maxTempExcursion, maxFaultTime);
+ gb.TryGetFValue('P', maxFaultTime, seen);
+ gb.TryGetFValue('T', maxTempExcursion, seen);
+ if (seen)
+ {
+ reprap.GetHeat()->SetHeaterProtection(heater, maxTempExcursion, maxFaultTime);
+ }
+ else
+ {
+ reply.printf("Heater %u allowed excursion %.1fC, fault trigger time %.1f seconds", heater, maxTempExcursion, maxFaultTime);
+ }
+ }
+ }
+ else if (gb.Seen('S'))
+ {
+ reply.copy("M570 S parameter is no longer required or supported");
+ }
+ break;
+
+ case 571: // Set output on extrude
+ {
+ bool seen = false;
+ if (gb.Seen('P'))
+ {
+ const int pwmPin = gb.GetIValue();
+ if (!platform->SetExtrusionAncilliaryPwmPin(pwmPin))
+ {
+ reply.printf("Logical pin %d is already in use or not available for use by M571", pwmPin);
+ break; // don't process 'S' parameter if the pin was wrong
+ }
+ seen = true;
+ }
+ if (gb.Seen('S'))
+ {
+ platform->SetExtrusionAncilliaryPwmValue(gb.GetFValue());
+ seen = true;
+ }
+ if (!seen)
+ {
+ reply.printf("Extrusion ancillary PWM %.3f on pin %u",
+ platform->GetExtrusionAncilliaryPwmValue(), platform->GetExtrusionAncilliaryPwmPin());
+ }
+ }
+ break;
+
+ case 572: // Set/report elastic compensation
+ if (gb.Seen('D'))
+ {
+ // New usage: specify the extruder drive using the D parameter
+ const size_t extruder = gb.GetIValue();
+ if (gb.Seen('S'))
+ {
+ platform->SetPressureAdvance(extruder, gb.GetFValue());
+ }
+ else
+ {
+ reply.printf("Pressure advance for extruder %u is %.3f seconds", extruder, platform->GetPressureAdvance(extruder));
+ }
+ }
+ break;
+
+ case 573: // Report heater average PWM
+ if (gb.Seen('P'))
+ {
+ const int heater = gb.GetIValue();
+ if (heater >= 0 && heater < HEATERS)
+ {
+ reply.printf("Average heater %d PWM: %.3f", heater, reprap.GetHeat()->GetAveragePWM(heater));
+ }
+ }
+ break;
+
+ case 574: // Set endstop configuration
+ {
+ bool seen = false;
+ const bool logicLevel = (gb.Seen('S')) ? (gb.GetIValue() != 0) : true;
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ const int ival = gb.GetIValue();
+ if (ival >= 0 && ival <= 3)
+ {
+ platform->SetEndStopConfiguration(axis, (EndStopType) ival, logicLevel);
+ seen = true;
+ }
+ }
+ }
+ if (!seen)
+ {
+ reply.copy("Endstop configuration:");
+ EndStopType config;
+ bool logic;
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ platform->GetEndStopConfiguration(axis, config, logic);
+ reply.catf(" %c %s (active %s),", axisLetters[axis],
+ (config == EndStopType::highEndStop) ? "high end" : (config == EndStopType::lowEndStop) ? "low end" : "none",
+ (config == EndStopType::noEndStop) ? "" : (logic) ? "high" : "low");
+ }
+ }
+ }
+ break;
+
+ case 575: // Set communications parameters
+ if (gb.Seen('P'))
+ {
+ size_t chan = gb.GetIValue();
+ if (chan < NUM_SERIAL_CHANNELS)
+ {
+ bool seen = false;
+ if (gb.Seen('B'))
+ {
+ platform->SetBaudRate(chan, gb.GetIValue());
+ seen = true;
+ }
+ if (gb.Seen('S'))
+ {
+ uint32_t val = gb.GetIValue();
+ platform->SetCommsProperties(chan, val);
+ switch (chan)
+ {
+ case 0:
+ serialGCode->SetCommsProperties(val);
+ break;
+ case 1:
+ auxGCode->SetCommsProperties(val);
+ break;
+ default:
+ break;
+ }
+ seen = true;
+ }
+ if (!seen)
+ {
+ uint32_t cp = platform->GetCommsProperties(chan);
+ reply.printf("Channel %d: baud rate %d, %s checksum", chan, platform->GetBaudRate(chan),
+ (cp & 1) ? "requires" : "does not require");
+ }
+ }
+ }
+ break;
+
+ case 577: // Wait until endstop is triggered
+ if (gb.Seen('S'))
+ {
+ // Determine trigger type
+ EndStopHit triggerCondition;
+ switch (gb.GetIValue())
+ {
+ case 1:
+ triggerCondition = EndStopHit::lowHit;
+ break;
+ case 2:
+ triggerCondition = EndStopHit::highHit;
+ break;
+ case 3:
+ triggerCondition = EndStopHit::lowNear;
+ break;
+ default:
+ triggerCondition = EndStopHit::noStop;
+ break;
+ }
+
+ // Axis endstops
+ for (size_t axis=0; axis < numAxes; axis++)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ if (platform->Stopped(axis) != triggerCondition)
+ {
+ result = false;
+ break;
+ }
+ }
+ }
+
+ // Extruder drives
+ size_t eDriveCount = MaxExtruders;
+ long eDrives[MaxExtruders];
+ if (gb.Seen(extrudeLetter))
+ {
+ gb.GetLongArray(eDrives, eDriveCount);
+ for(size_t extruder = 0; extruder < eDriveCount; extruder++)
+ {
+ const size_t eDrive = eDrives[extruder];
+ if (eDrive >= MaxExtruders)
+ {
+ reply.copy("Invalid extruder drive specified!");
+ error = result = true;
+ break;
+ }
+
+ if (platform->Stopped(eDrive + E0_AXIS) != triggerCondition)
+ {
+ result = false;
+ break;
+ }
+ }
+ }
+ }
+ break;
+
+#if SUPPORT_INKJET
+ case 578: // Fire Inkjet bits
+ if (!AllMovesAreFinishedAndMoveBufferIsLoaded())
+ {
+ return false;
+ }
+
+ if (gb.Seen('S')) // Need to handle the 'P' parameter too; see http://reprap.org/wiki/G-code#M578:_Fire_inkjet_bits
+ {
+ platform->Inkjet(gb.GetIValue());
+ }
+ break;
+#endif
+
+ case 579: // Scale Cartesian axes (mostly for Delta configurations)
+ {
+ bool seen = false;
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ gb.TryGetFValue(axisLetters[axis], axisScaleFactors[axis], seen);
+ }
+
+ if (!seen)
+ {
+ char sep = ':';
+ reply.copy("Axis scale factors");
+ for(size_t axis = 0; axis < numAxes; axis++)
+ {
+ reply.catf("%c %c: %.3f", sep, axisLetters[axis], axisScaleFactors[axis]);
+ sep = ',';
+ }
+ }
+ }
+ break;
+
+#if SUPPORT_ROLAND
+ case 580: // (De)Select Roland mill
+ if (gb.Seen('R'))
+ {
+ if (gb.GetIValue())
+ {
+ reprap.GetRoland()->Activate();
+ if (gb.Seen('P'))
+ {
+ result = reprap.GetRoland()->RawWrite(gb.GetString());
+ }
+ }
+ else
+ {
+ result = reprap.GetRoland()->Deactivate();
+ }
+ }
+ else
+ {
+ reply.printf("Roland is %s.", reprap.GetRoland()->Active() ? "active" : "inactive");
+ }
+ break;
+#endif
+
+ case 581: // Configure external trigger
+ case 582: // Check external trigger
+ if (gb.Seen('T'))
+ {
+ unsigned int triggerNumber = gb.GetIValue();
+ if (triggerNumber < MaxTriggers)
+ {
+ if (code == 582)
+ {
+ uint32_t states = platform->GetAllEndstopStates();
+ if ((triggers[triggerNumber].rising & states) != 0 || (triggers[triggerNumber].falling & ~states) != 0)
+ {
+ triggersPending |= (1u << triggerNumber);
+ }
+ }
+ else
+ {
+ bool seen = false;
+ if (gb.Seen('C'))
+ {
+ seen = true;
+ triggers[triggerNumber].condition = gb.GetIValue();
+ }
+ else if (triggers[triggerNumber].IsUnused())
+ {
+ triggers[triggerNumber].condition = 0; // this is a new trigger, so set no condition
+ }
+ if (gb.Seen('S'))
+ {
+ seen = true;
+ int sval = gb.GetIValue();
+ TriggerMask triggerMask = 0;
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ triggerMask |= (1u << axis);
+ }
+ }
+ if (gb.Seen(extrudeLetter))
+ {
+ long eStops[MaxExtruders];
+ size_t numEntries = MaxExtruders;
+ gb.GetLongArray(eStops, numEntries);
+ for (size_t i = 0; i < numEntries; ++i)
+ {
+ if (eStops[i] >= 0 && (unsigned long)eStops[i] < MaxExtruders)
+ {
+ triggerMask |= (1u << (eStops[i] + E0_AXIS));
+ }
+ }
+ }
+ switch(sval)
+ {
+ case -1:
+ if (triggerMask == 0)
+ {
+ triggers[triggerNumber].rising = triggers[triggerNumber].falling = 0;
+ }
+ else
+ {
+ triggers[triggerNumber].rising &= (~triggerMask);
+ triggers[triggerNumber].falling &= (~triggerMask);
+ }
+ break;
+
+ case 0:
+ triggers[triggerNumber].falling |= triggerMask;
+ break;
+
+ case 1:
+ triggers[triggerNumber].rising |= triggerMask;
+ break;
+
+ default:
+ platform->Message(GENERIC_MESSAGE, "Bad S parameter in M581 command\n");
+ }
+ }
+ if (!seen)
+ {
+ reply.printf("Trigger %u fires on a rising edge on ", triggerNumber);
+ ListTriggers(reply, triggers[triggerNumber].rising);
+ reply.cat(" or a falling edge on ");
+ ListTriggers(reply, triggers[triggerNumber].falling);
+ reply.cat(" endstop inputs");
+ if (triggers[triggerNumber].condition == 1)
+ {
+ reply.cat(" when printing from SD card");
+ }
+ }
+ }
+ }
+ else
+ {
+ platform->Message(GENERIC_MESSAGE, "Trigger number out of range\n");
+ }
+ }
+ break;
+
+ case 584: // Set axis/extruder to stepper driver(s) mapping
+ if (!LockMovementAndWaitForStandstill(gb)) // we also rely on this to retrieve the current motor positions to moveBuffer
+ {
+ return false;
+ }
+ {
+ bool seen = false, badDrive = false;
+ for (size_t drive = 0; drive < MAX_AXES; ++drive)
+ {
+ if (gb.Seen(axisLetters[drive]))
+ {
+ seen = true;
+ size_t numValues = MaxDriversPerAxis;
+ long drivers[MaxDriversPerAxis];
+ gb.GetLongArray(drivers, numValues);
+
+ // Check all the driver numbers are in range
+ bool badAxis = false;
+ AxisDriversConfig config;
+ config.numDrivers = numValues;
+ for (size_t i = 0; i < numValues; ++i)
+ {
+ if ((unsigned long)drivers[i] >= DRIVES)
+ {
+ badAxis = true;
+ }
+ else
+ {
+ config.driverNumbers[i] = (uint8_t)drivers[i];
+ }
+ }
+ if (badAxis)
+ {
+ badDrive = true;
+ }
+ else
+ {
+ while (numAxes <= drive)
+ {
+ moveBuffer.coords[numAxes] = 0.0; // user has defined a new axis, so set its position
+ ++numAxes;
+ }
+ SetPositions(moveBuffer.coords); // tell the Move system where any new axes are
+ platform->SetAxisDriversConfig(drive, config);
+ if (numAxes + numExtruders > DRIVES)
+ {
+ numExtruders = DRIVES - numAxes; // if we added axes, we may have fewer extruders now
+ }
+ }
+ }
+ }
+
+ if (gb.Seen(extrudeLetter))
+ {
+ seen = true;
+ size_t numValues = DRIVES - numAxes;
+ long drivers[MaxExtruders];
+ gb.GetLongArray(drivers, numValues);
+ numExtruders = numValues;
+ for (size_t i = 0; i < numValues; ++i)
+ {
+ if ((unsigned long)drivers[i] >= DRIVES)
+ {
+ badDrive = true;
+ }
+ else
+ {
+ platform->SetExtruderDriver(i, (uint8_t)drivers[i]);
+ }
+ }
+ }
+
+ if (badDrive)
+ {
+ platform->Message(GENERIC_MESSAGE, "Error: invalid drive number in M584 command\n");
+ }
+ else if (!seen)
+ {
+ reply.copy("Driver assignments:");
+ for (size_t drive = 0; drive < numAxes; ++ drive)
+ {
+ reply.cat(' ');
+ const AxisDriversConfig& axisConfig = platform->GetAxisDriversConfig(drive);
+ char c = axisLetters[drive];
+ for (size_t i = 0; i < axisConfig.numDrivers; ++i)
+ {
+ reply.catf("%c%u", c, axisConfig.driverNumbers[i]);
+ c = ':';
+ }
+ }
+ reply.cat(' ');
+ char c = extrudeLetter;
+ for (size_t extruder = 0; extruder < numExtruders; ++extruder)
+ {
+ reply.catf("%c%u", c, platform->GetExtruderDriver(extruder));
+ c = ':';
+ }
+ }
+ }
+ break;
+
+ case 665: // Set delta configuration
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ {
+ float positionNow[DRIVES];
+ Move *move = reprap.GetMove();
+ move->GetCurrentUserPosition(positionNow, 0, reprap.GetCurrentXAxes()); // get the current position, we may need it later
+ DeltaParameters& params = move->AccessDeltaParams();
+ const bool wasInDeltaMode = params.IsDeltaMode(); // remember whether we were in delta mode
+ bool seen = false;
+
+ if (gb.Seen('L'))
+ {
+ params.SetDiagonal(gb.GetFValue() * distanceScale);
+ seen = true;
+ }
+ if (gb.Seen('R'))
+ {
+ params.SetRadius(gb.GetFValue() * distanceScale);
+ seen = true;
+ }
+ if (gb.Seen('B'))
+ {
+ params.SetPrintRadius(gb.GetFValue() * distanceScale);
+ seen = true;
+ }
+ if (gb.Seen('X'))
+ {
+ // X tower position correction
+ params.SetXCorrection(gb.GetFValue());
+ seen = true;
+ }
+ if (gb.Seen('Y'))
+ {
+ // Y tower position correction
+ params.SetYCorrection(gb.GetFValue());
+ seen = true;
+ }
+ if (gb.Seen('Z'))
+ {
+ // Y tower position correction
+ params.SetZCorrection(gb.GetFValue());
+ seen = true;
+ }
+
+ // The homed height must be done last, because it gets recalculated when some of the other factors are changed
+ if (gb.Seen('H'))
+ {
+ params.SetHomedHeight(gb.GetFValue() * distanceScale);
+ seen = true;
+ }
+
+ if (seen)
+ {
+ move->SetCoreXYMode(0); // CoreXYMode needs to be zero when executing special moves on a delta
+
+ // If we have changed between Cartesian and Delta mode, we need to reset the motor coordinates to agree with the XYZ coordinates.
+ // This normally happens only when we process the M665 command in config.g. Also flag that the machine is not homed.
+ if (params.IsDeltaMode() != wasInDeltaMode)
+ {
+ SetPositions(positionNow);
+ }
+ SetAllAxesNotHomed();
+ }
+ else
+ {
+ if (params.IsDeltaMode())
+ {
+ reply.printf("Diagonal %.3f, delta radius %.3f, homed height %.3f, bed radius %.1f"
+ ", X %.3f" DEGREE_SYMBOL ", Y %.3f" DEGREE_SYMBOL ", Z %.3f" DEGREE_SYMBOL,
+ params.GetDiagonal() / distanceScale, params.GetRadius() / distanceScale,
+ params.GetHomedHeight() / distanceScale, params.GetPrintRadius() / distanceScale,
+ params.GetXCorrection(), params.GetYCorrection(), params.GetZCorrection());
+ }
+ else
+ {
+ reply.printf("Printer is not in delta mode");
+ }
+ }
+ }
+ break;
+
+ case 666: // Set delta endstop adjustments
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ {
+ DeltaParameters& params = reprap.GetMove()->AccessDeltaParams();
+ bool seen = false;
+ if (gb.Seen('X'))
+ {
+ params.SetEndstopAdjustment(X_AXIS, gb.GetFValue());
+ seen = true;
+ }
+ if (gb.Seen('Y'))
+ {
+ params.SetEndstopAdjustment(Y_AXIS, gb.GetFValue());
+ seen = true;
+ }
+ if (gb.Seen('Z'))
+ {
+ params.SetEndstopAdjustment(Z_AXIS, gb.GetFValue());
+ seen = true;
+ }
+ if (gb.Seen('A'))
+ {
+ params.SetXTilt(gb.GetFValue() * 0.01);
+ seen = true;
+ }
+ if (gb.Seen('B'))
+ {
+ params.SetYTilt(gb.GetFValue() * 0.01);
+ seen = true;
+ }
+
+ if (seen)
+ {
+ SetAllAxesNotHomed();
+ }
+ else
+ {
+ reply.printf("Endstop adjustments X%.2f Y%.2f Z%.2f, tilt X%.2f%% Y%.2f%%",
+ params.GetEndstopAdjustment(X_AXIS), params.GetEndstopAdjustment(Y_AXIS), params.GetEndstopAdjustment(Z_AXIS),
+ params.GetXTilt() * 100.0, params.GetYTilt() * 100.0);
+ }
+ }
+ break;
+
+ case 667: // Set CoreXY mode
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ {
+ Move* const move = reprap.GetMove();
+ bool seen = false;
+ float positionNow[DRIVES];
+ move->GetCurrentUserPosition(positionNow, 0, reprap.GetCurrentXAxes()); // get the current position, we may need it later
+ if (gb.Seen('S'))
+ {
+ move->SetCoreXYMode(gb.GetIValue());
+ seen = true;
+ }
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ move->SetCoreAxisFactor(axis, gb.GetFValue());
+ seen = true;
+ }
+ }
+
+ if (seen)
+ {
+ SetPositions(positionNow);
+ SetAllAxesNotHomed();
+ }
+ else
+ {
+ reply.printf("Printer mode is %s with axis factors", move->GetGeometryString());
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ reply.catf(" %c:%f", axisLetters[axis], move->GetCoreAxisFactor(axis));
+ }
+ }
+ }
+ break;
+
+ case 905: // Set current RTC date and time
+ {
+ const time_t now = platform->GetDateTime();
+ struct tm * const timeInfo = gmtime(&now);
+ bool seen = false;
+
+ if (gb.Seen('P'))
+ {
+ // Set date
+ const char * const dateString = gb.GetString();
+ if (strptime(dateString, "%Y-%m-%d", timeInfo) != nullptr)
+ {
+ if (!platform->SetDate(mktime(timeInfo)))
+ {
+ reply.copy("Could not set date");
+ error = true;
+ break;
+ }
+ }
+ else
+ {
+ reply.copy("Invalid date format");
+ error = true;
+ break;
+ }
+
+ seen = true;
+ }
+
+ if (gb.Seen('S'))
+ {
+ // Set time
+ const char * const timeString = gb.GetString();
+ if (strptime(timeString, "%H:%M:%S", timeInfo) != nullptr)
+ {
+ if (!platform->SetTime(mktime(timeInfo)))
+ {
+ reply.copy("Could not set time");
+ error = true;
+ break;
+ }
+ }
+ else
+ {
+ reply.copy("Invalid time format");
+ error = true;
+ break;
+ }
+ seen = true;
+ }
+
+ // TODO: Add correction parameters for SAM4E
+
+ if (!seen)
+ {
+ // Report current date and time
+ reply.printf("Current date and time: %04u-%02u-%02u %02u:%02u:%02u",
+ timeInfo->tm_year + 1900, timeInfo->tm_mon + 1, timeInfo->tm_mday,
+ timeInfo->tm_hour, timeInfo->tm_min, timeInfo->tm_sec);
+
+ if (!platform->IsDateTimeSet())
+ {
+ reply.cat("\nWarning: RTC has not been configured yet!");
+ }
+ }
+ }
+ break;
+
+ case 906: // Set/report Motor currents
+ case 913: // Set/report motor current percent
+ {
+ bool seen = false;
+ for (size_t axis = 0; axis < numAxes; axis++)
+ {
+ if (gb.Seen(axisLetters[axis]))
+ {
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ platform->SetMotorCurrent(axis, gb.GetFValue(), code == 913);
+ seen = true;
+ }
+ }
+
+ if (gb.Seen(extrudeLetter))
+ {
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+
+ float eVals[MaxExtruders];
+ size_t eCount = numExtruders;
+ gb.GetFloatArray(eVals, eCount, true);
+ // 2014-09-29 DC42: we no longer insist that the user supplies values for all possible extruder drives
+ for (size_t e = 0; e < eCount; e++)
+ {
+ platform->SetMotorCurrent(numAxes + e, eVals[e], code == 913);
+ }
+ seen = true;
+ }
+
+ if (code == 906 && gb.Seen('I'))
+ {
+ const float idleFactor = gb.GetFValue();
+ if (idleFactor >= 0 && idleFactor <= 100.0)
+ {
+ platform->SetIdleCurrentFactor(idleFactor/100.0);
+ seen = true;
+ }
+ }
+
+ if (!seen)
+ {
+ reply.copy((code == 913) ? "Motor current % of normal - " : "Motor current (mA) - ");
+ for (size_t axis = 0; axis < numAxes; ++axis)
+ {
+ reply.catf("%c:%d, ", axisLetters[axis], (int)platform->GetMotorCurrent(axis, code == 913));
+ }
+ reply.cat("E");
+ for (size_t extruder = 0; extruder < numExtruders; extruder++)
+ {
+ reply.catf(":%d", (int)platform->GetMotorCurrent(extruder + numAxes, code == 913));
+ }
+ if (code == 906)
+ {
+ reply.catf(", idle factor %d%%", (int)(platform->GetIdleCurrentFactor() * 100.0));
+ }
+ }
+ }
+ break;
+
+ case 911: // Set power monitor threshold voltages
+ reply.printf("M911 not implemented yet");
+ break;
+
+ case 912: // Set electronics temperature monitor adjustment
+ // Currently we ignore the P parameter (i.e. temperature measurement channel)
+ if (gb.Seen('S'))
+ {
+ platform->SetMcuTemperatureAdjust(gb.GetFValue());
+ }
+ else
+ {
+ reply.printf("MCU temperature calibration adjustment is %.1f" DEGREE_SYMBOL "C", platform->GetMcuTemperatureAdjust());
+ }
+ break;
+
+ // For case 913, see 906
+
+ case 997: // Perform firmware update
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+ reprap.GetHeat()->SwitchOffAll(); // turn all heaters off because the main loop may get suspended
+ DisableDrives(); // all motors off
+
+ if (firmwareUpdateModuleMap == 0) // have we worked out which modules to update?
+ {
+ // Find out which modules we have been asked to update
+ if (gb.Seen('S'))
+ {
+ long modulesToUpdate[3];
+ size_t numUpdateModules = ARRAY_SIZE(modulesToUpdate);
+ gb.GetLongArray(modulesToUpdate, numUpdateModules);
+ for (size_t i = 0; i < numUpdateModules; ++i)
+ {
+ long t = modulesToUpdate[i];
+ if (t < 0 || (unsigned long)t >= NumFirmwareUpdateModules)
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Invalid module number '%ld'\n", t);
+ firmwareUpdateModuleMap = 0;
+ break;
+ }
+ firmwareUpdateModuleMap |= (1u << (unsigned int)t);
+ }
+ }
+ else
+ {
+ firmwareUpdateModuleMap = (1u << 0); // no modules specified, so update module 0 to match old behaviour
+ }
+
+ if (firmwareUpdateModuleMap == 0)
+ {
+ break; // nothing to update
+ }
+
+ // Check prerequisites of all modules to be updated, if any are not met then don't update any of them
+#ifdef DUET_NG
+ if (!FirmwareUpdater::CheckFirmwareUpdatePrerequisites(firmwareUpdateModuleMap))
+ {
+ firmwareUpdateModuleMap = 0;
+ break;
+ }
+#endif
+ if ((firmwareUpdateModuleMap & 1) != 0 && !platform->CheckFirmwareUpdatePrerequisites())
+ {
+ firmwareUpdateModuleMap = 0;
+ break;
+ }
+ }
+
+ // If we get here then we have the module map, and all prerequisites are satisfied
+ isFlashing = true; // this tells the web interface and PanelDue that we are about to flash firmware
+ if (!DoDwellTime(1.0)) // wait a second so all HTTP clients and PanelDue are notified
+ {
+ return false;
+ }
+
+ gb.SetState(GCodeState::flashing1);
+ break;
+
+ case 998:
+ // The input handling code replaces the gcode by this when it detects a checksum error.
+ // Since we have no way of asking for the line to be re-sent, just report an error.
+ if (gb.Seen('P'))
+ {
+ const int val = gb.GetIValue();
+ if (val != 0)
+ {
+ reply.printf("Checksum error on line %d", val);
+ }
+ }
+ break;
+
+ case 999:
+ result = DoDwellTime(0.5); // wait half a second to allow the response to be sent back to the web server, otherwise it may retry
+ if (result)
+ {
+ reprap.EmergencyStop(); // this disables heaters and drives - Duet WiFi pre-production boards need drives disabled here
+ uint16_t reason = (gb.Seen('P') && StringStartsWith(gb.GetString(), "ERASE"))
+ ? (uint16_t)SoftwareResetReason::erase
+ : (uint16_t)SoftwareResetReason::user;
+ platform->SoftwareReset(reason); // doesn't return
+ }
+ break;
+
+ default:
+ error = true;
+ reply.printf("unsupported command: %s", gb.Buffer());
+ }
+
+ if (result && gb.GetState() == GCodeState::normal)
+ {
+ UnlockAll(gb);
+ HandleReply(gb, error, reply.Pointer());
+ }
+ return result;
+}
+
+bool GCodes::HandleTcode(GCodeBuffer& gb, StringRef& reply)
+{
+ if (!LockMovementAndWaitForStandstill(gb))
+ {
+ return false;
+ }
+
+ newToolNumber = gb.GetIValue();
+ newToolNumber += gb.GetToolNumberAdjust();
+
+ // TODO for the tool change restore point to be useful, we should undo any X axis mapping and remove any tool offsets
+ for (size_t drive = 0; drive < DRIVES; ++drive)
+ {
+ toolChangeRestorePoint.moveCoords[drive] = moveBuffer.coords[drive];
+ }
+ toolChangeRestorePoint.feedRate = gb.MachineState().feedrate;
+
+ if (simulationMode == 0) // we don't yet simulate any T codes
+ {
+ const Tool * const oldTool = reprap.GetCurrentTool();
+ // If old and new are the same we no longer follow the sequence. User can deselect and then reselect the tool if he wants the macros run.
+ if (oldTool == nullptr || oldTool->Number() != newToolNumber)
+ {
+ StartToolChange(gb, false);
+ return true; // proceeding with state machine, so don't unlock or send a reply
+ }
+ }
+
+ // If we get here, we have finished
+ UnlockAll(gb);
+ HandleReply(gb, false, "");
+ return true;
+}
+
+// End
diff --git a/src/Heating/Heat.h b/src/Heating/Heat.h
index 6290cd16..ab74e6a9 100644
--- a/src/Heating/Heat.h
+++ b/src/Heating/Heat.h
@@ -39,8 +39,7 @@ public:
void Exit(); // Shut everything down
bool ColdExtrude() const; // Is cold extrusion allowed?
- void AllowColdExtrude(); // Allow cold extrusion
- void DenyColdExtrude(); // Deny cold extrusion
+ void AllowColdExtrude(bool b); // Allow or deny cold extrusion
int8_t GetBedHeater() const // Get hot bed heater number
post(-1 <= result; result < HEATERS);
@@ -132,14 +131,9 @@ inline bool Heat::ColdExtrude() const
return coldExtrude;
}
-inline void Heat::AllowColdExtrude()
+inline void Heat::AllowColdExtrude(bool b)
{
- coldExtrude = true;
-}
-
-inline void Heat::DenyColdExtrude()
-{
- coldExtrude = false;
+ coldExtrude = b;
}
inline int8_t Heat::GetBedHeater() const
diff --git a/src/Movement/DDA.cpp b/src/Movement/DDA.cpp
index 0eeea2b6..a42ac642 100644
--- a/src/Movement/DDA.cpp
+++ b/src/Movement/DDA.cpp
@@ -161,8 +161,8 @@ void DDA::Init()
bool DDA::Init(const GCodes::RawMove &nextMove, bool doMotorMapping)
{
// 1. Compute the new endpoints and the movement vector
- const int32_t *positionNow = prev->DriveCoordinates();
- const Move *move = reprap.GetMove();
+ const int32_t * const positionNow = prev->DriveCoordinates();
+ const Move * const move = reprap.GetMove();
if (doMotorMapping)
{
move->MotorTransform(nextMove.coords, endPoint); // transform the axis coordinates if on a delta or CoreXY printer
diff --git a/src/Movement/Grid.cpp b/src/Movement/Grid.cpp
index ba194537..3cd91e49 100644
--- a/src/Movement/Grid.cpp
+++ b/src/Movement/Grid.cpp
@@ -9,12 +9,11 @@
#include "RepRapFirmware.h"
#include <cmath>
-// Increase the version number in the following string whenever we change the format of the height map file.
-const char *HeightMapComment = "RepRapFirmware height map file v1";
+const char *GridDefinition::HeightMapLabelLine = "xmin,xmax,ymin,ymax,radius,spacing,xnum,ynum";
// Initialise the grid to be invalid
GridDefinition::GridDefinition()
- : xMin(0.0), xMax(-1.0), yMin(0.0), yMax(-1.0), radius(-1.0), spacing(DefaultGridSpacing), gridHeights(nullptr),
+ : xMin(0.0), xMax(-1.0), yMin(0.0), yMax(-1.0), radius(-1.0), spacing(DefaultGridSpacing),
numX(0), numY(0), recipSpacing(1.0/spacing), isValid(false)
{
}
@@ -24,13 +23,13 @@ GridDefinition::GridDefinition(const float xRange[2], const float yRange[2], flo
{
numX = (xMax - xMin >= MinRange && spacing >= MinSpacing) ? (uint32_t)((xMax - xMin) * recipSpacing) + 1 : 0;
numY = (yMax - yMin >= MinRange && spacing >= MinSpacing) ? (uint32_t)((yMax - yMin) * recipSpacing) + 1 : 0;
- isValid = NumPoints() != 0 && NumPoints() <= MaxGridProbePoints && (radius < 0.0 || radius >= 1.0);
+ CheckValidity();
+
}
-void GridDefinition::SetStorage(const float *heightStorage, const uint32_t *heightSetStorage)
+void GridDefinition::CheckValidity()
{
- gridHeights = heightStorage;
- gridHeightSet = heightSetStorage;
+ isValid = NumPoints() != 0 && NumPoints() <= MaxGridProbePoints && (radius < 0.0 || radius >= 1.0);
}
float GridDefinition::GetXCoordinate(unsigned int xIndex) const
@@ -49,27 +48,54 @@ bool GridDefinition::IsInRadius(float x, float y) const
}
// Append the grid parameters to the end of a string
-void GridDefinition::PrintParameters(StringRef& r) const
+void GridDefinition::PrintParameters(StringRef& s) const
+{
+ s.catf("X%.1f:%.1f, Y%.1f:%.1f, radius %.1f, spacing %.1f, %d points", xMin, xMax, yMin, yMax, radius, spacing, NumPoints());
+}
+
+// Write the parameter label line to a string
+void GridDefinition::WriteHeadingAndParameters(StringRef& s) const
+{
+ s.printf("%s\n%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%u,%u\n", HeightMapLabelLine, xMin, xMax, yMin, yMax, radius, spacing, numX, numY);
+}
+
+// Check the parameter label line returning true if correct
+/*static*/ bool GridDefinition::CheckHeading(const StringRef& s)
{
- r.catf("X%.1f:%.1f, Y%.1f:%.1f, radius %.1f, spacing %.1f, %d points", xMin, xMax, yMin, yMax, radius, spacing, NumPoints());
+ return StringStartsWith(s.Pointer(), HeightMapLabelLine);
}
-// Print what is wrong with the grid
+// Read the grid parameters from a string returning true if success
+bool GridDefinition::ReadParameters(const StringRef& s)
+{
+ bool ok = (sscanf(s.Pointer(), "%f,%f,%f,%f,%f,%f,%lu,%lu", &xMin, &xMax, &yMin, &yMax, &radius, &spacing, &numX, &numY) == 8);
+ if (ok)
+ {
+ CheckValidity();
+ }
+ else
+ {
+ isValid = false;
+ }
+ return ok;
+}
+
+// Print what is wrong with the grid, appending it to the existing string
void GridDefinition::PrintError(StringRef& r) const
{
if (spacing < MinSpacing)
{
r.cat("Spacing too small");
}
- else if (NumXpoints() == 0)
+ else if (numX == 0)
{
r.cat("X range too small");
}
- else if (NumYpoints() == 0)
+ else if (numY == 0)
{
r.cat("Y range too small");
}
- else if (NumPoints() > MaxGridProbePoints)
+ else if (numX > MaxGridProbePoints || numY > MaxGridProbePoints || NumPoints() > MaxGridProbePoints) // check X and Y individually in case X*Y overflows
{
r.catf("Too many grid points (maximum %d, needed %d)", MaxGridProbePoints, NumPoints());
}
@@ -80,8 +106,44 @@ void GridDefinition::PrintError(StringRef& r) const
}
}
+// Increase the version number in the following string whenever we change the format of the height map file.
+const char *HeightMap::HeightMapComment = "RepRapFirmware height map file v1";
+
+HeightMap::HeightMap(float *heightStorage) : gridHeights(heightStorage) { }
+
+void HeightMap::SetGrid(const GridDefinition& gd)
+{
+ def = gd;
+ ClearGridHeights();
+}
+
+void HeightMap::ClearGridHeights()
+{
+ for (size_t i = 0; i < MaxGridProbePoints/32; ++i)
+ {
+ gridHeightSet[i] = 0;
+ }
+}
+
+// Set the height of a grid point
+void HeightMap::SetGridHeight(size_t xIndex, size_t yIndex, float height)
+{
+ size_t index = yIndex * def.numX + xIndex;
+ if (index < MaxGridProbePoints)
+ {
+ gridHeights[index] = height;
+ gridHeightSet[index/32] |= 1u << (index & 31u);
+ }
+}
+
+// Return the minimum number of segments for a move by this X or Y amount
+unsigned int HeightMap::GetMinimumSegments(float distance) const
+{
+ return (distance > 0.0) ? (unsigned int)(distance * def.recipSpacing + 0.4) : 1;
+}
+
// Save the grid to file returning true if an error occurred
-bool GridDefinition::SaveToFile(FileStore *f) const
+bool HeightMap::SaveToFile(FileStore *f) const
{
char bufferSpace[500];
StringRef buf(bufferSpace, ARRAY_SIZE(bufferSpace));
@@ -93,7 +155,7 @@ bool GridDefinition::SaveToFile(FileStore *f) const
time_t timeNow = reprap.GetPlatform()->GetDateTime();
const struct tm * const timeInfo = gmtime(&timeNow);
buf.catf(" generated at %04u-%02u-%02u %02u:%02u",
- timeInfo->tm_year, timeInfo->tm_mon, timeInfo->tm_mday, timeInfo->tm_hour, timeInfo->tm_min);
+ timeInfo->tm_year + 1900, timeInfo->tm_mon, timeInfo->tm_mday, timeInfo->tm_hour, timeInfo->tm_min);
}
buf.cat('\n');
if (!f->Write(buf.Pointer()))
@@ -102,10 +164,7 @@ bool GridDefinition::SaveToFile(FileStore *f) const
}
// Write the grid parameters
- buf.printf("xmin,xmax,ymin,ymax,radius,spacing,xnum,ynum\n"
- "%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%u,%u\n",
- xMin, xMax, yMin, yMax, radius, spacing, numX, numY
- );
+ def.WriteHeadingAndParameters(buf);
if (!f->Write(buf.Pointer()))
{
return true;
@@ -113,10 +172,10 @@ bool GridDefinition::SaveToFile(FileStore *f) const
// Write the grid heights
uint32_t index = 0;
- for (uint32_t i = 0; i < numY; ++i)
+ for (uint32_t i = 0; i < def.numY; ++i)
{
buf.Clear();
- for (uint32_t j = 0; j < numX; ++j)
+ for (uint32_t j = 0; j < def.numX; ++j)
{
if (j != 0)
{
@@ -142,20 +201,93 @@ bool GridDefinition::SaveToFile(FileStore *f) const
return false;
}
-// Load the grid from file returning true if an error occurred
-bool GridDefinition::LoadFromFile(FileStore *f)
+// Load the grid from file, returning true if an error occurred with the error reason appended to the buffer
+bool HeightMap::LoadFromFile(FileStore *f, StringRef& r)
{
- //TODO
- return true;
+ const size_t MaxLineLength = 200; // maximum length of a line in the height map file
+ const char* const readFailureText = "failed to read line from file";
+ char buffer[MaxLineLength + 1];
+ StringRef s(buffer, ARRAY_SIZE(buffer));
+
+ ClearGridHeights();
+ GridDefinition newGrid;
+
+ if (f->ReadLine(buffer, sizeof(buffer)) <= 0)
+ {
+ r.cat(readFailureText);
+ }
+ else if (!StringStartsWith(buffer, HeightMapComment)) // check the version line is as expected
+ {
+ r.cat("bad header line or wrong version header");
+ }
+ else if (f->ReadLine(buffer, sizeof(buffer)) <= 0)
+ {
+ r.cat(readFailureText);
+ }
+ else if (!GridDefinition::CheckHeading(s)) // check the label line is as expected
+ {
+ r.cat("bad label line");
+ }
+ else if (f->ReadLine(buffer, sizeof(buffer)) <= 0) // read the height map parameters
+ {
+ r.cat(readFailureText);
+ }
+ else if (!newGrid.ReadParameters(s))
+ {
+ r.cat("failed to parse grid parameters");
+ }
+ else if (!newGrid.IsValid())
+ {
+ r.cat("invalid grid");
+ }
+ else
+ {
+ SetGrid(newGrid);
+ for (uint32_t row = 0; row < def.numY; ++row) // read the grid a row at a time
+ {
+ if (f->ReadLine(buffer, sizeof(buffer)) <= 0)
+ {
+ r.cat(readFailureText);
+ return true; // failed to read a line
+ }
+ const char *p = buffer;
+ for (uint32_t col = 0; col < def.numX; ++col)
+ {
+ if (*p == '0' && (p[1] == ',' || p[1] == 0))
+ {
+ // Values of 0 with no decimal places in un-probed values, so leave the point set as not valid
+ ++p;
+ }
+ else
+ {
+ char* np = nullptr;
+ const float f = strtod(p, &np);
+ if (np == p)
+ {
+ r.catf("number expected at line %u column %d", row + 3, (p - buffer) + 1);
+ return true; // failed to read a number
+ }
+ SetGridHeight(col, row, f);
+ p = np;
+ }
+ if (*p == ',')
+ {
+ ++p;
+ }
+ }
+ }
+ return false; // success!
+ }
+ return true; // an error occurred
}
// Compute the height error at the specified point
-float GridDefinition::ComputeHeightError(float x, float y) const
+float HeightMap::ComputeHeightError(float x, float y) const
{
- const float xf = (x - xMin) * recipSpacing;
+ const float xf = (x - def.xMin) * def.recipSpacing;
const float xFloor = floor(xf);
const int32_t xIndex = (int32_t)xFloor;
- const float yf = (y - yMin) * recipSpacing;
+ const float yf = (y - def.yMin) * def.recipSpacing;
const float yFloor = floor(yf);
const int32_t yIndex = (int32_t)yFloor;
@@ -166,29 +298,29 @@ float GridDefinition::ComputeHeightError(float x, float y) const
// We are off the bottom left corner of the grid
return GetHeightError(0, 0);
}
- else if (yIndex >= (int)NumYpoints())
+ else if (yIndex >= (int)def.numY)
{
- return GetHeightError(0, NumYpoints());
+ return GetHeightError(0, def.numY);
}
else
{
return InterpolateY(0, yIndex, yf - yFloor);
}
}
- else if (xIndex >= (int)NumXpoints())
+ else if (xIndex >= (int)def.numX)
{
if (yIndex < 0)
{
// We are off the bottom left corner of the grid
- return GetHeightError(NumXpoints(), 0);
+ return GetHeightError(def.numX, 0);
}
- else if (yIndex >= (int)NumYpoints())
+ else if (yIndex >= (int)def.numY)
{
- return GetHeightError(NumXpoints(), NumYpoints());
+ return GetHeightError(def.numX, def.numY);
}
else
{
- return InterpolateY(NumXpoints(), yIndex, yf - yFloor);
+ return InterpolateY(def.numX, yIndex, yf - yFloor);
}
}
else
@@ -198,9 +330,9 @@ float GridDefinition::ComputeHeightError(float x, float y) const
// We are off the bottom left corner of the grid
return InterpolateX(xIndex, 0, xf - xFloor);
}
- else if (yIndex >= (int)NumYpoints())
+ else if (yIndex >= (int)def.numY)
{
- return InterpolateX(xIndex, NumYpoints(), xf - xFloor);
+ return InterpolateX(xIndex, def.numY, xf - xFloor);
}
else
{
@@ -209,25 +341,25 @@ float GridDefinition::ComputeHeightError(float x, float y) const
}
}
-float GridDefinition::GetHeightError(uint32_t xIndex, uint32_t yIndex) const
+float HeightMap::GetHeightError(uint32_t xIndex, uint32_t yIndex) const
{
const uint32_t index = GetMapIndex(xIndex, yIndex);
return (IsHeightSet(index)) ? gridHeights[index] : 0.0;
}
-float GridDefinition::InterpolateX(uint32_t xIndex, uint32_t yIndex, float xFrac) const
+float HeightMap::InterpolateX(uint32_t xIndex, uint32_t yIndex, float xFrac) const
{
const uint32_t index1 = GetMapIndex(xIndex, yIndex);
return Interpolate2(index1, index1 + 1, xFrac);
}
-float GridDefinition::InterpolateY(uint32_t xIndex, uint32_t yIndex, float yFrac) const
+float HeightMap::InterpolateY(uint32_t xIndex, uint32_t yIndex, float yFrac) const
{
const uint32_t index1 = GetMapIndex(xIndex, yIndex);
- return Interpolate2(index1, index1 + numX, yFrac);
+ return Interpolate2(index1, index1 + def.numX, yFrac);
}
-float GridDefinition::Interpolate2(uint32_t index1, uint32_t index2, float frac) const
+float HeightMap::Interpolate2(uint32_t index1, uint32_t index2, float frac) const
{
const bool b1 = IsHeightSet(index1);
const bool b2 = IsHeightSet(index2);
@@ -237,11 +369,11 @@ float GridDefinition::Interpolate2(uint32_t index1, uint32_t index2, float frac)
: 0.0;
}
-float GridDefinition::InterpolateXY(uint32_t xIndex, uint32_t yIndex, float xFrac, float yFrac) const
+float HeightMap::InterpolateXY(uint32_t xIndex, uint32_t yIndex, float xFrac, float yFrac) const
{
const uint32_t indexX0Y0 = GetMapIndex(xIndex, yIndex); // (X0,Y0)
const uint32_t indexX1Y0 = indexX0Y0 + 1; // (X1,Y0)
- const uint32_t indexX0Y1 = indexX0Y0 + numX; // (X0 Y1)
+ const uint32_t indexX0Y1 = indexX0Y0 + def.numX; // (X0 Y1)
const uint32_t indexX1Y1 = indexX0Y1 + 1; // (X1,Y1)
const unsigned int cc = ((unsigned int)IsHeightSet(indexX0Y0) << 0)
+ ((unsigned int)IsHeightSet(indexX1Y0) << 1)
@@ -291,7 +423,7 @@ float GridDefinition::InterpolateXY(uint32_t xIndex, uint32_t yIndex, float xFra
}
}
-float GridDefinition::InterpolateCorner(uint32_t cornerIndex, uint32_t indexX, uint32_t indexY, float xFrac, float yFrac) const
+float HeightMap::InterpolateCorner(uint32_t cornerIndex, uint32_t indexX, uint32_t indexY, float xFrac, float yFrac) const
{
return ((xFrac * gridHeights[indexX]) + (yFrac * gridHeights[indexY]) + ((2.0 - xFrac - yFrac) * gridHeights[cornerIndex]))/2;
}
diff --git a/src/Movement/Grid.h b/src/Movement/Grid.h
index 9146d394..cb73b21f 100644
--- a/src/Movement/Grid.h
+++ b/src/Movement/Grid.h
@@ -11,6 +11,7 @@
#include <cstdint>
#include "ecv.h"
#include "Libraries/General/StringRef.h"
+#include "Configuration.h"
class FileStore;
@@ -18,9 +19,10 @@ class FileStore;
class GridDefinition
{
public:
+ friend class HeightMap;
+
GridDefinition();
GridDefinition(const float xRange[2], const float yRange[2], float pRadius, float pSpacing);
- void SetStorage(const float *heightStorage, const uint32_t *heightSetStorage);
uint32_t NumXpoints() const { return numX; }
uint32_t NumYpoints() const { return numY; }
@@ -31,35 +33,62 @@ public:
bool IsValid() const { return isValid; }
void PrintParameters(StringRef& r) const;
+ void WriteHeadingAndParameters(StringRef& r) const;
+ static bool CheckHeading(const StringRef& s);
+ bool ReadParameters(const StringRef& s);
void PrintError(StringRef& r) const
pre(!IsValid());
- bool SaveToFile(FileStore *f) const // Save the grid to file returning true if an error occurred
- pre(IsValid());
-
- bool LoadFromFile(FileStore *f); // Load the grid from file returning true if an error occurred
-
- float ComputeHeightError(float x, float y) const // Compute the height error at the specified point
- pre(IsValid(); gridHeights != nullptr; gridHeights.upb >= NumPoints());
-
private:
- static constexpr float MinSpacing = 0.1; // The minimum point spacing allowed
- static constexpr float MinRange = 1.0; // The minimum X and Y range allowed
+ void CheckValidity();
+
+ static constexpr float MinSpacing = 0.1; // The minimum point spacing allowed
+ static constexpr float MinRange = 1.0; // The minimum X and Y range allowed
+ static const char *HeightMapLabelLine; // The line we write to the height map file listing the parameter names
// Primary parameters
- float xMin, xMax, yMin, yMax; // The edges of the grid for G29 probing
- float radius; // The grid radius to probe
- float spacing; // The spacing of the grid probe points
- const float *gridHeights; // The map of grid heights
- const uint32_t *gridHeightSet; // Bitmap of which heights are set
+ float xMin, xMax, yMin, yMax; // The edges of the grid for G29 probing
+ float radius; // The grid radius to probe
+ float spacing; // The spacing of the grid probe points
// Derived parameters
uint32_t numX, numY;
float recipSpacing;
bool isValid;
- uint32_t GetMapIndex(uint32_t xIndex, uint32_t yIndex) const { return (yIndex * numX) + xIndex; }
+};
+
+// Class to represent the height map
+class HeightMap
+{
+public:
+ HeightMap(float *heightStorage);
+
+ const GridDefinition& GetGrid() const { return def; }
+ void SetGrid(const GridDefinition& gd);
+
+ float ComputeHeightError(float x, float y) const // Compute the height error at the specified point
+ pre(IsValid(); gridHeights != nullptr; gridHeights.upb >= NumPoints());
+
+ void ClearGridHeights(); // Clear all grid height corrections
+ void SetGridHeight(size_t xIndex, size_t yIndex, float height); // Set the height of a grid point
+
+ bool SaveToFile(FileStore *f) const // Save the grid to file returning true if an error occurred
+ pre(IsValid());
+
+ bool LoadFromFile(FileStore *f, StringRef& r); // Load the grid from file returning true if an error occurred
+
+ unsigned int GetMinimumSegments(float distance) const; // Return the minimum number of segments for a move by this X or Y amount
+
+private:
+ static const char *HeightMapComment; // The start of the comment we write at the start of the height map file
+
+ GridDefinition def;
+ float *gridHeights; // The map of grid heights, must have at least MaxGridProbePoints entries
+ uint32_t gridHeightSet[MaxGridProbePoints/32]; // Bitmap of which heights are set
+
+ uint32_t GetMapIndex(uint32_t xIndex, uint32_t yIndex) const { return (yIndex * def.NumXpoints()) + xIndex; }
bool IsHeightSet(uint32_t index) const { return (gridHeightSet[index/32] & (1 << (index & 31))) != 0; }
float GetHeightError(uint32_t xIndex, uint32_t yIndex) const;
float InterpolateX(uint32_t xIndex, uint32_t yIndex, float xFrac) const;
diff --git a/src/Movement/Move.cpp b/src/Movement/Move.cpp
index 0d7ccf78..e9ed842d 100644
--- a/src/Movement/Move.cpp
+++ b/src/Movement/Move.cpp
@@ -7,7 +7,7 @@
#include "RepRapFirmware.h"
-Move::Move(Platform* p, GCodes* g) : currentDda(NULL)
+Move::Move(Platform* p, GCodes* g) : currentDda(NULL), grid(zBedProbePoints)
{
active = false;
@@ -66,18 +66,26 @@ void Move::Init()
SetPositions(move);
// Set up default bed probe points. This is only a guess, because we don't know the bed size yet.
- for (size_t point = 0; point < MaxProbePoints; point++)
+ for (size_t point = 0; point < ARRAY_SIZE(zBedProbePoints); point++)
{
if (point < 4)
{
- xBedProbePoints[point] = (0.3 + 0.6*(float)(point%2))*reprap.GetPlatform()->AxisMaximum(X_AXIS);
- yBedProbePoints[point] = (0.0 + 0.9*(float)(point/2))*reprap.GetPlatform()->AxisMaximum(Y_AXIS);
+ xBedProbePoints[point] = (0.1 + 0.8 * (float)(point%2)) * reprap.GetPlatform()->AxisMaximum(X_AXIS);
+ yBedProbePoints[point] = (0.1 + 0.8 * (float)(point/2)) * reprap.GetPlatform()->AxisMaximum(Y_AXIS);
+ }
+ else if (point == 4)
+ {
+ xBedProbePoints[point] = 0.5 * reprap.GetPlatform()->AxisMaximum(X_AXIS);
+ yBedProbePoints[point] = 0.5 * reprap.GetPlatform()->AxisMaximum(Y_AXIS);
}
zBedProbePoints[point] = 0.0;
- probePointSet[point] = unset;
+ if (point < ARRAY_SIZE(probePointSet))
+ {
+ probePointSet[point] = unset;
+ }
}
- xRectangle = 1.0/(0.8*reprap.GetPlatform()->AxisMaximum(X_AXIS));
+ xRectangle = 1.0/(0.8 * reprap.GetPlatform()->AxisMaximum(X_AXIS));
yRectangle = xRectangle;
longWait = reprap.GetPlatform()->Time();
@@ -174,38 +182,6 @@ void Move::Spin()
if (simulationMode < 2) // in simulation mode 2 and higher, we don't process incoming moves beyond this point
{
-#if 0 //*** This code is not finished yet ***
- // If we are doing bed compensation and the move crosses a compensation boundary by a significant amount,
- // segment it so that we can apply proper bed compensation
- // Issues here:
- // 1. Are there enough DDAs? need to make nextMove static and remember whether we have the remains of a move in there.
- // 2. Pause/restart: if we restart a segmented move when we have already executed part of it, we will extrude too much.
- // Perhaps remember how much of the last move we executed? Or always insist on completing all the segments in a move?
- bool isSegmented;
- do
- {
- GCodes::RawMove tempMove = nextMove;
- isSegmented = SegmentMove(tempMove);
- if (isSegmented)
- {
- // Extruder moves are relative, so we need to adjust the extrusion amounts in the original move
- for (size_t drive = AXES; drive < DRIVES; ++drive)
- {
- nextMove.coords[drive] -= tempMove.coords[drive];
- }
- }
- bool doMotorMapping = (moveType == 0) || (moveType == 1 && !IsDeltaMode());
- if (doMotorMapping)
- {
- Transform(tempMove);
- }
- if (ddaRingAddPointer->Init(tempMove.coords, nextMove.feedRate, nextMove.endStopsToCheck, doMotorMapping, nextMove.filePos))
- {
- ddaRingAddPointer = ddaRingAddPointer->GetNext();
- idleCount = 0;
- }
- } while (isSegmented);
-#else // Use old code
bool doMotorMapping = (nextMove.moveType == 0) || (nextMove.moveType == 1 && !IsDeltaMode());
if (doMotorMapping)
{
@@ -216,7 +192,6 @@ void Move::Spin()
ddaRingAddPointer = ddaRingAddPointer->GetNext();
idleCount = 0;
}
-#endif
}
}
else
@@ -432,6 +407,14 @@ void Move::Diagnostics(MessageType mtype)
numLookaheadUnderruns = numPrepareUnderruns = 0;
longestGcodeWaitInterval = 0;
+ // Show the current probe position heights
+ p->Message(mtype, "Bed probe heights:");
+ for (size_t i = 0; i < MaxProbePoints; ++i)
+ {
+ p->MessageF(mtype, " %.3f", ZBedProbePoint(i));
+ }
+ p->Message(mtype, "\n");
+
#if DDA_LOG_PROBE_CHANGES
// Temporary code to print Z probe trigger positions
p->Message(mtype, "Probe change coordinates:");
@@ -1529,88 +1512,6 @@ size_t Move::NumberOfXYProbePoints() const
return MaxProbePoints;
}
-// Set a new grid
-void Move::SetBedProbeGrid(const GridDefinition& newGrid)
-{
- useGridHeights = false;
- grid = newGrid;
- grid.SetStorage(zBedProbePoints, gridHeightSet);
-}
-
-void Move::ClearGridHeights()
-{
- useGridHeights = false;
- for (size_t i = 0; i < ARRAY_SIZE(gridHeightSet); ++i)
- {
- gridHeightSet[i] = 0;
- }
-}
-
-// Set the height of a grid point
-void Move::SetGridHeight(size_t xIndex, size_t yIndex, float height)
-{
- size_t index = yIndex * grid.NumXpoints() + xIndex;
- if (index < MaxGridProbePoints)
- {
- zBedProbePoints[index] = height;
- gridHeightSet[index/32] |= 1u << (index & 31u);
- }
-}
-
-// Load the height map
-bool Move::LoadHeightMapFromFile(const char *fname, StringRef& reply)
-{
- Platform *platform = reprap.GetPlatform();
- FileStore * const f = platform->GetFileStore(platform->GetSysDir(), fname, false);
- bool err;
- if (f == nullptr)
- {
- reply.printf("Height map file %s not found", fname);
- err = true;
- }
- else
- {
- //TODO
- err = grid.LoadFromFile(f);
- f->Close();
- }
-
- if (err)
- {
- ClearGridHeights(); // make sure we don't end up with a partial height map
- }
- return err;
-}
-
-// Save the height map and write the success or error message to 'reply'
-// Returning true if an error occurred
-bool Move::SaveHeightMapToFile(const char *fname, StringRef& reply) const
-{
- Platform *platform = reprap.GetPlatform();
- FileStore * const f = platform->GetFileStore(platform->GetSysDir(), fname, true);
- bool err;
- if (f == nullptr)
- {
- reply.printf("Failed to create height map file %s", fname);
- err = true;
- }
- else
- {
- err = grid.SaveToFile(f);
- f->Close();
- if (err)
- {
- platform->GetMassStorage()->Delete(platform->GetSysDir(), fname);
- reply.printf("Failed to save height map to file %s", fname);
- }
- else
- {
- reply.printf("Height map saved to file %s", fname);
- }
- }
- return err;
-}
-
// Enter or leave simulation mode
void Move::Simulate(uint8_t simMode)
{
diff --git a/src/Movement/Move.h b/src/Movement/Move.h
index 401f0c1d..698fae2f 100644
--- a/src/Movement/Move.h
+++ b/src/Movement/Move.h
@@ -113,16 +113,10 @@ public:
bool IsExtruding() const; // Is filament being extruded?
- const GridDefinition& GetBedProbeGrid() const { return grid; } // Access the bed probing grid
+ HeightMap& AccessBedProbeGrid() { return grid; } // Access the bed probing grid
- void SetBedProbeGrid(const GridDefinition& newGrid) // Set a new grid
- pre(newGrid.IsValid());
-
- void ClearGridHeights(); // Clear all grid height corrections
- void SetGridHeight(size_t xIndex, size_t yIndex, float height); // Set the height of a grid point
- void UseHeightMap() { useGridHeights = true; } // Start using the height map
- bool LoadHeightMapFromFile(const char *fname, StringRef& reply); // Load the height map and *append* any error message to 'reply'
- bool SaveHeightMapToFile(const char *fname, StringRef& reply) const; // Save the height map
+ void UseHeightMap(bool b) { useGridHeights = b; } // Start or stop using the height map
+ bool UsingHeightMap() const { return useGridHeights; } // Are we doing grid bed compensation?
private:
@@ -183,8 +177,7 @@ private:
int numBedCompensationPoints; // The number of points we are actually using for bed compensation, 0 means identity bed transform
float xRectangle, yRectangle; // The side lengths of the rectangle used for second-degree bed compensation
- GridDefinition grid; // Grid definition for G29 bed probing. The probe heights are stored in zBedProbePoints, see above.
- uint32_t gridHeightSet[MaxGridProbePoints/32]; // Bitmap of which points have been probed
+ HeightMap grid; // Grid definition and height map for G29 bed probing. The probe heights are stored in zBedProbePoints, see above.
bool useGridHeights; // True if the zBedProbePoints came from valid bed probing and relate to the current grid
float idleTimeout; // How long we wait with no activity before we reduce motor currents to idle
diff --git a/src/Pins.h b/src/Pins.h
index de9738e0..46d06333 100644
--- a/src/Pins.h
+++ b/src/Pins.h
@@ -5,7 +5,11 @@
#if !defined(PLATFORM)
# if defined(__SAM3X8E__)
-# define PLATFORM Duet
+# if defined(__RADDS__)
+# define PLATFORM RADDS
+# else
+# define PLATFORM Duet
+# endif
# elif defined(__SAM4E8E__)
# define PLATFORM DuetNG
# else
diff --git a/src/Platform.cpp b/src/Platform.cpp
index 72ddd3f4..3fb43cf9 100644
--- a/src/Platform.cpp
+++ b/src/Platform.cpp
@@ -21,7 +21,6 @@
#include "RepRapFirmware.h"
#include "DueFlashStorage.h"
-#include "RTCDue.h"
#include "sam/drivers/tc/tc.h"
#include "sam/drivers/hsmci/hsmci.h"
@@ -54,6 +53,12 @@ static volatile uint32_t fanInterval = 0; // written by ISR, read outside the
const float minStepPulseTiming = 0.2; // we assume that we always generate step high and low times at least this wide without special action
+const int Heater0LogicalPin = 0;
+const int Fan0LogicalPin = 20;
+const int EndstopXLogicalPin = 40;
+const int Special0LogicalPin = 60;
+const int DueX5Gpio0LogicalPin = 100;
+
//#define MOVE_DEBUG
#ifdef MOVE_DEBUG
@@ -157,8 +162,7 @@ void Platform::Init()
SetBoardType(BoardType::Auto);
// Real-time clock
-
- RTCDue::Init();
+ realTime = 0;
// Comms
@@ -202,22 +206,12 @@ void Platform::Init()
fileStructureInitialised = true;
-#if !defined(DUET_NG)
+#if !defined(DUET_NG) && !defined(__RADDS__)
mcpDuet.begin(); // only call begin once in the entire execution, this begins the I2C comms on that channel for all objects
mcpExpansion.setMCP4461Address(0x2E); // not required for mcpDuet, as this uses the default address
#endif
- // Directories
-
- sysDir = SYS_DIR;
- macroDir = MACRO_DIR;
- webDir = WEB_DIR;
- gcodeDir = GCODE_DIR;
- configFile = CONFIG_FILE;
- defaultFile = DEFAULT_FILE;
-
// DRIVES
-
ARRAY_INIT(endStopPins, END_STOP_PINS);
ARRAY_INIT(maxFeedrates, MAX_FEEDRATES);
ARRAY_INIT(accelerations, ACCELERATIONS);
@@ -236,13 +230,11 @@ void Platform::Init()
maxAverageAcceleration = 10000.0; // high enough to have no effect until it is changed
// Z PROBE
-
zProbePin = Z_PROBE_PIN;
zProbeAdcChannel = PinToAdcChannel(zProbePin);
InitZProbe(); // this also sets up zProbeModulationPin
// AXES
-
ARRAY_INIT(axisMaxima, AXIS_MAXIMA);
ARRAY_INIT(axisMinima, AXIS_MINIMA);
@@ -334,7 +326,11 @@ void Platform::Init()
TMC2660::Init(ENABLE_PINS, numTMC2660Drivers);
#endif
- extrusionAncilliaryPWM = 0.0;
+ extrusionAncilliaryPwmValue = 0.0;
+ extrusionAncilliaryPwmLogicalPin = -1;
+ extrusionAncilliaryPwmFirmwarePin = NoPin;
+ extrusionAncilliaryPwmInvert = false;
+ SetExtrusionAncilliaryPwmPin(Fan0LogicalPin);
ARRAY_INIT(tempSensePins, TEMP_SENSE_PINS);
ARRAY_INIT(heatOnPins, HEAT_ON_PINS);
@@ -1204,6 +1200,16 @@ void Platform::Spin()
TMC2660::SetDriversPowered(driversPowered);
#endif
+ // Update the time
+ if (realTime != 0)
+ {
+ if (millis() - timeLastUpdatedMillis >= 1000)
+ {
+ ++realTime; // this assumes that time_t is a seconds-since-epoch counter, which is not guaranteed by the C standard
+ timeLastUpdatedMillis += 1000;
+ }
+ }
+
ClassReport(longWait);
}
@@ -1372,7 +1378,7 @@ void Platform::Diagnostics(MessageType mtype)
(unsigned int)(now/3600), (unsigned int)((now % 3600)/60), (unsigned int)(now % 60),
resetReasons[(REG_RSTC_SR & RSTC_SR_RSTTYP_Msk) >> RSTC_SR_RSTTYP_Pos]);
- // Show the error code stored at the last software reset
+ // Show the reset code stored at the last software reset
{
SoftwareResetData temp;
temp.magic = 0;
@@ -1387,14 +1393,6 @@ void Platform::Diagnostics(MessageType mtype)
// Show the current error codes
MessageF(mtype, "Error status: %u\n", errorCodeBits);
- // Show the current probe position heights
- Message(mtype, "Bed probe heights:");
- for (size_t i = 0; i < MaxProbePoints; ++i)
- {
- MessageF(mtype, " %.3f", reprap.GetMove()->ZBedProbePoint(i));
- }
- Message(mtype, "\n");
-
// Show the number of free entries in the file table
unsigned int numFreeFiles = 0;
for (size_t i = 0; i < MAX_FILES; i++)
@@ -1449,11 +1447,13 @@ void Platform::Diagnostics(MessageType mtype)
#endif
// Show current RTC time
- const time_t timeNow = RTCDue::GetDateTime();
- const struct tm * const timeInfo = gmtime(&timeNow);
- MessageF(mtype, "Current date and time: %04u-%02u-%02u %02u:%02u:%02u\n",
- timeInfo->tm_year + 1900, timeInfo->tm_mon + 1, timeInfo->tm_mday,
- timeInfo->tm_hour, timeInfo->tm_min, timeInfo->tm_sec);
+ struct tm timeInfo;
+ if (gmtime_r(&realTime, &timeInfo) != nullptr)
+ {
+ MessageF(mtype, "Current date and time: %04u-%02u-%02u %02u:%02u:%02u\n",
+ timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday,
+ timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);
+ }
// Debug
//MessageF(mtype, "TC_FMR = %08x, PWM_FPE = %08x, PWM_FSR = %08x\n", TC2->TC_FMR, PWM->PWM_FPE, PWM->PWM_FSR);
@@ -2608,50 +2608,50 @@ bool Platform::GetFirmwarePin(int logicalPin, PinAccess access, Pin& firmwarePin
{
// Pin number out of range, so nothing to do here
}
- else if (logicalPin < HEATERS) // pins 0-9 correspond to heater channels
+ else if (logicalPin >= Heater0LogicalPin && logicalPin < Heater0LogicalPin + HEATERS) // pins 0-9 correspond to heater channels
{
// For safety, we don't allow a heater channel to be used for servos until the heater has been disabled
- if (!reprap.GetHeat()->IsHeaterEnabled(logicalPin))
+ if (!reprap.GetHeat()->IsHeaterEnabled(logicalPin - Heater0LogicalPin))
{
- firmwarePin = heatOnPins[logicalPin];
+ firmwarePin = heatOnPins[logicalPin - Heater0LogicalPin];
invert = !HEAT_ON;
}
}
- else if (logicalPin >= 20 && logicalPin < 20 + (int)NUM_FANS) // pins 100-107 correspond to fan channels
+ else if (logicalPin >= Fan0LogicalPin && logicalPin < Fan0LogicalPin + (int)NUM_FANS) // pins 20- correspond to fan channels
{
// Don't allow a fan channel to be used unless the fan has been disabled
- if (!fans[logicalPin - 20].IsEnabled()
+ if (!fans[logicalPin - Fan0LogicalPin].IsEnabled()
#ifdef DUET_NG
// Fan pins on the DueX2/DueX5 cannot be used to control servos because the frequency is not well-defined.
- && (logicalPin <= 22 || access != PinAccess::servo)
+ && (logicalPin <= Fan0LogicalPin + 2 || access != PinAccess::servo)
#endif
)
{
- firmwarePin = COOLING_FAN_PINS[logicalPin - 20];
+ firmwarePin = COOLING_FAN_PINS[logicalPin - Fan0LogicalPin];
}
}
- else if (logicalPin >= 40 && logicalPin < 40 + (int)ARRAY_SIZE(endStopPins)) // pins 40-49 correspond to endstop pins
+ else if (logicalPin >= EndstopXLogicalPin && logicalPin < EndstopXLogicalPin + (int)ARRAY_SIZE(endStopPins)) // pins 40-49 correspond to endstop pins
{
if (access == PinAccess::read
#ifdef DUET_NG
// Endstop pins on the DueX2/DueX5 can be used as digital outputs too
- || (access == PinAccess::write && logicalPin >= 45)
+ || (access == PinAccess::write && logicalPin >= EndstopXLogicalPin + 5)
#endif
)
{
- firmwarePin = endStopPins[logicalPin - 40];
+ firmwarePin = endStopPins[logicalPin - EndstopXLogicalPin];
}
}
- else if (logicalPin >= 60 && logicalPin < 60 + (int)ARRAY_SIZE(SpecialPinMap))
+ else if (logicalPin >= Special0LogicalPin && logicalPin < Special0LogicalPin + (int)ARRAY_SIZE(SpecialPinMap))
{
- firmwarePin = SpecialPinMap[logicalPin - 60];
+ firmwarePin = SpecialPinMap[logicalPin - Special0LogicalPin];
}
#ifdef DUET_NG
- else if (logicalPin >= 100 && logicalPin < 100 + (int)ARRAY_SIZE(DueX5GpioPinMap)) // Pins 100-103 are the GPIO pins on the DueX2/X5
+ else if (logicalPin >= DueX5Gpio0LogicalPin && logicalPin < DueX5Gpio0LogicalPin + (int)ARRAY_SIZE(DueX5GpioPinMap)) // Pins 100-103 are the GPIO pins on the DueX2/X5
{
if (access != PinAccess::servo)
{
- firmwarePin = DueX5GpioPinMap[logicalPin - 100];
+ firmwarePin = DueX5GpioPinMap[logicalPin - DueX5Gpio0LogicalPin];
}
}
#endif
@@ -2682,6 +2682,11 @@ bool Platform::GetFirmwarePin(int logicalPin, PinAccess access, Pin& firmwarePin
return false;
}
+bool Platform::SetExtrusionAncilliaryPwmPin(int logicalPin)
+{
+ return GetFirmwarePin(logicalPin, PinAccess::pwm, extrusionAncilliaryPwmFirmwarePin, extrusionAncilliaryPwmInvert);
+}
+
#if SUPPORT_INKJET
// Fire the inkjet (if any) in the given pattern
@@ -2791,27 +2796,76 @@ void Platform::GetPowerVoltages(float& minV, float& currV, float& maxV) const
bool Platform::IsDateTimeSet() const
{
- return RTCDue::IsDateTimeSet();
+ return realTime != 0;
}
time_t Platform::GetDateTime() const
{
- return RTCDue::GetDateTime();
+ return realTime;
}
bool Platform::SetDateTime(time_t time)
{
- return RTCDue::SetDateTime(time);
+ struct tm brokenDateTime;
+ const bool ok = (gmtime_r(&time, &brokenDateTime) != nullptr);
+ if (ok)
+ {
+ realTime = time;
+ timeLastUpdatedMillis = millis();
+ }
+ return ok;
}
bool Platform::SetDate(time_t date)
{
- return RTCDue::SetDate(date);
+ // Check the validity of the date passed in
+ struct tm brokenNewDate;
+ const bool ok = (gmtime_r(&date, &brokenNewDate) != nullptr);
+ if (ok)
+ {
+ struct tm brokenTimeNow;
+ if (realTime == 0 || gmtime_r(&realTime, &brokenTimeNow) == nullptr)
+ {
+ // We didn't have a valid date/time set, so set the date and time to the value passed in
+ realTime = date;
+ timeLastUpdatedMillis = millis();
+ }
+ else
+ {
+ // Merge the existing time into the date passed in
+ brokenNewDate.tm_hour = brokenTimeNow.tm_hour;
+ brokenNewDate.tm_min = brokenTimeNow.tm_min;
+ brokenNewDate.tm_sec = brokenTimeNow.tm_sec;
+ realTime = mktime(&brokenNewDate);
+ }
+ }
+ return ok;
}
bool Platform::SetTime(time_t time)
{
- return RTCDue::SetTime(time);
+ // Check the validity of the date passed in
+ struct tm brokenNewTime;
+ const bool ok = (gmtime_r(&time, &brokenNewTime) != nullptr);
+ if (ok)
+ {
+ struct tm brokenTimeNow;
+ if (realTime == 0 || gmtime_r(&realTime, &brokenTimeNow) == nullptr)
+ {
+ // We didn't have a valid date/time set, so set the date and time to the value passed in
+ realTime = time;
+ }
+ else
+ {
+ // Merge the new time into the current date/time
+ brokenTimeNow.tm_hour = brokenNewTime.tm_hour;
+ brokenTimeNow.tm_min = brokenNewTime.tm_min;
+ brokenTimeNow.tm_sec = brokenNewTime.tm_sec;
+ realTime = mktime(&brokenTimeNow);
+ }
+ timeLastUpdatedMillis = millis();
+ }
+ return ok;
}
// Pragma pop_options is not supported on this platform, so we put this time-critical code right at the end of the file
diff --git a/src/Platform.h b/src/Platform.h
index 036084f9..8c9af3cb 100644
--- a/src/Platform.h
+++ b/src/Platform.h
@@ -236,7 +236,8 @@ struct ZProbeParameters
float diveHeight; // the dive height we use when probing
float probeSpeed; // the initial speed of probing
float travelSpeed; // the speed at which we travel to the probe point
- float param1, param2; // extra parameters used by some types of probe e.g. Delta probe
+ float recoveryTime; // Z probe recovery time
+ float extraParam; // extra parameters used by some types of probe e.g. Delta probe
bool invertReading; // true if we need to invert the reading
void Init(float h)
@@ -249,7 +250,7 @@ struct ZProbeParameters
diveHeight = DEFAULT_Z_DIVE;
probeSpeed = DEFAULT_PROBE_SPEED;
travelSpeed = DEFAULT_TRAVEL_SPEED;
- param1 = param2 = 0.0;
+ recoveryTime = extraParam = 0.0;
invertReading = false;
}
@@ -269,8 +270,8 @@ struct ZProbeParameters
&& diveHeight == other.diveHeight
&& probeSpeed == other.probeSpeed
&& travelSpeed == other.travelSpeed
- && param1 == other.param1
- && param2 == other.param2
+ && recoveryTime == other.recoveryTime
+ && extraParam == other.extraParam
&& invertReading == other.invertReading;
}
@@ -527,8 +528,13 @@ public:
bool IsAccessibleProbePoint(float x, float y) const;
float GetPressureAdvance(size_t drive) const;
void SetPressureAdvance(size_t extruder, float factor);
- void SetEndStopConfiguration(size_t axis, EndStopType endstopType, bool logicLevel);
- void GetEndStopConfiguration(size_t axis, EndStopType& endstopType, bool& logicLevel) const;
+
+ void SetEndStopConfiguration(size_t axis, EndStopType endstopType, bool logicLevel)
+ pre(axis < MAX_AXES);
+
+ void GetEndStopConfiguration(size_t axis, EndStopType& endstopType, bool& logicLevel) const
+ pre(axis < MAX_AXES);
+
uint32_t GetAllEndstopStates() const;
void SetAxisDriversConfig(size_t drive, const AxisDriversConfig& config);
const AxisDriversConfig& GetAxisDriversConfig(size_t drive) const
@@ -559,8 +565,10 @@ public:
void SetZProbeParameters(const struct ZProbeParameters& params);
bool MustHomeXYBeforeZ() const;
- void SetExtrusionAncilliaryPWM(float v);
- float GetExtrusionAncilliaryPWM() const;
+ void SetExtrusionAncilliaryPwmValue(float v);
+ float GetExtrusionAncilliaryPwmValue() const;
+ bool SetExtrusionAncilliaryPwmPin(int logicalPin);
+ int GetExtrusionAncilliaryPwmPin() const { return extrusionAncilliaryPwmLogicalPin; }
void ExtrudeOn();
void ExtrudeOff();
@@ -766,7 +774,7 @@ private:
#if defined(DUET_NG)
size_t numTMC2660Drivers; // the number of TMC2660 drivers we have, the remaining are simple enable/step/dir drivers
-#else
+#elif !defined(__RADDS__)
// Digipots
MCP4461 mcpDuet;
MCP4461 mcpExpansion;
@@ -784,7 +792,10 @@ private:
volatile ZProbeAveragingFilter zProbeOffFilter; // Z probe readings we took with the IR turned off
volatile ThermistorAveragingFilter thermistorFilters[HEATERS]; // bed and extruder thermistor readings
- float extrusionAncilliaryPWM;
+ float extrusionAncilliaryPwmValue;
+ int extrusionAncilliaryPwmLogicalPin;
+ Pin extrusionAncilliaryPwmFirmwarePin;
+ bool extrusionAncilliaryPwmInvert;
void InitZProbe();
uint16_t GetRawZProbeReading() const;
@@ -831,12 +842,6 @@ private:
MassStorage* massStorage;
FileStore* files[MAX_FILES];
bool fileStructureInitialised;
- const char* webDir;
- const char* gcodeDir;
- const char* sysDir;
- const char* macroDir;
- const char* configFile;
- const char* defaultFile;
// Data used by the tick interrupt handler
@@ -896,6 +901,10 @@ private:
bool driversPowered;
#endif
+ // RTC
+ time_t realTime; // the current date/time, or zero if never set
+ uint32_t timeLastUpdatedMillis; // the milliseconds counter when we last incremented the time
+
// Direct pin manipulation
int8_t logicalPinModes[HighestLogicalPin + 1]; // what mode each logical pin is set to - would ideally be class PinMode not int8_t
};
@@ -968,51 +977,50 @@ private:
inline const char* Platform::GetWebDir() const
{
- return webDir;
+ return WEB_DIR;
}
// Where the gcodes are
inline const char* Platform::GetGCodeDir() const
{
- return gcodeDir;
+ return GCODE_DIR;
}
// Where the system files are
inline const char* Platform::GetSysDir() const
{
- return sysDir;
+ return SYS_DIR;
}
inline const char* Platform::GetMacroDir() const
{
- return macroDir;
+ return MACRO_DIR;
}
inline const char* Platform::GetConfigFile() const
{
- return configFile;
+ return CONFIG_FILE;
}
inline const char* Platform::GetDefaultFile() const
{
- return defaultFile;
+ return DEFAULT_FILE;
}
-
//*****************************************************************************************************************
// Drive the RepRap machine - Movement
inline float Platform::DriveStepsPerUnit(size_t drive) const
{
- return driveStepsPerUnit[drive];
+ return driveStepsPerUnit[drive];
}
inline void Platform::SetDriveStepsPerUnit(size_t drive, float value)
{
- driveStepsPerUnit[drive] = value;
+ driveStepsPerUnit[drive] = value;
}
inline float Platform::Acceleration(size_t drive) const
@@ -1032,7 +1040,7 @@ inline void Platform::SetAcceleration(size_t drive, float value)
inline float Platform::MaxFeedrate(size_t drive) const
{
- return maxFeedrates[drive];
+ return maxFeedrates[drive];
}
inline const float* Platform::MaxFeedrates() const
@@ -1067,7 +1075,7 @@ inline bool Platform::GetDirectionValue(size_t drive) const
inline void Platform::SetDriverDirection(uint8_t driver, bool direction)
{
- bool d = (direction == FORWARDS) ? directions[driver] : !directions[driver];
+ const bool d = (direction == FORWARDS) ? directions[driver] : !directions[driver];
digitalWrite(DIRECTION_PINS[driver], d);
}
@@ -1107,14 +1115,14 @@ inline float Platform::AxisTotalLength(size_t axis) const
return axisMaxima[axis] - axisMinima[axis];
}
-inline void Platform::SetExtrusionAncilliaryPWM(float v)
+inline void Platform::SetExtrusionAncilliaryPwmValue(float v)
{
- extrusionAncilliaryPWM = v;
+ extrusionAncilliaryPwmValue = v;
}
-inline float Platform::GetExtrusionAncilliaryPWM() const
+inline float Platform::GetExtrusionAncilliaryPwmValue() const
{
- return extrusionAncilliaryPWM;
+ return extrusionAncilliaryPwmValue;
}
// For the Duet we use the fan output for this
@@ -1123,9 +1131,9 @@ inline float Platform::GetExtrusionAncilliaryPWM() const
// Caution: this is often called from an ISR, or with interrupts disabled!
inline void Platform::ExtrudeOn()
{
- if (extrusionAncilliaryPWM > 0.0)
+ if (extrusionAncilliaryPwmValue > 0.0)
{
- SetFanValue(0,extrusionAncilliaryPWM);
+ WriteAnalog(extrusionAncilliaryPwmFirmwarePin, extrusionAncilliaryPwmValue, DefaultPinWritePwmFreq);
}
}
@@ -1134,9 +1142,9 @@ inline void Platform::ExtrudeOn()
// Caution: this is often called from an ISR, or with interrupts disabled!
inline void Platform::ExtrudeOff()
{
- if (extrusionAncilliaryPWM > 0.0)
+ if (extrusionAncilliaryPwmValue > 0.0)
{
- SetFanValue(0,0.0);
+ WriteAnalog(extrusionAncilliaryPwmFirmwarePin, 0.0, DefaultPinWritePwmFreq);
}
}
@@ -1204,14 +1212,12 @@ inline float Platform::GetPressureAdvance(size_t extruder) const
}
inline void Platform::SetEndStopConfiguration(size_t axis, EndStopType esType, bool logicLevel)
-pre(axis < MAX_AXES)
{
endStopType[axis] = esType;
endStopLogicLevel[axis] = logicLevel;
}
inline void Platform::GetEndStopConfiguration(size_t axis, EndStopType& esType, bool& logicLevel) const
-pre(axis < MAX_AXES)
{
esType = endStopType[axis];
logicLevel = endStopLogicLevel[axis];
@@ -1325,7 +1331,7 @@ inline float Platform::AdcReadingToPowerVoltage(uint16_t adcVal)
#if defined(DUET_NG)
return pinDesc.ulPin;
#elif defined(__RADDS__)
-# error needs writing
+ return (pinDesc.pPort == PIOC) ? pinDesc.ulPin << 1 : pinDesc.ulPin;
#else
return (pinDesc.pPort == PIOA) ? pinDesc.ulPin << 1 : pinDesc.ulPin;
#endif
@@ -1339,7 +1345,10 @@ inline float Platform::AdcReadingToPowerVoltage(uint16_t adcVal)
#if defined(DUET_NG)
PIOD->PIO_ODSR = driverMap; // on Duet WiFi all step pins are on port D
#elif defined(__RADDS__)
-# error need to write this
+ PIOA->PIO_ODSR = driverMap;
+ PIOB->PIO_ODSR = driverMap;
+ PIOD->PIO_ODSR = driverMap;
+ PIOC->PIO_ODSR = driverMap >> 1; // do this last, it means the processor doesn't need to preserve the register containing driverMap
#else // Duet
PIOD->PIO_ODSR = driverMap;
PIOC->PIO_ODSR = driverMap;
@@ -1355,7 +1364,10 @@ inline float Platform::AdcReadingToPowerVoltage(uint16_t adcVal)
#if defined(DUET_NG)
PIOD->PIO_ODSR = 0; // on Duet WiFi all step pins are on port D
#elif defined(__RADDS__)
-# error need to write this
+ PIOD->PIO_ODSR = 0;
+ PIOC->PIO_ODSR = 0;
+ PIOB->PIO_ODSR = 0;
+ PIOA->PIO_ODSR = 0;
#else // Duet
PIOD->PIO_ODSR = 0;
PIOC->PIO_ODSR = 0;
diff --git a/src/RADDS/Network.cpp b/src/RADDS/Network.cpp
new file mode 100644
index 00000000..901f3b45
--- /dev/null
+++ b/src/RADDS/Network.cpp
@@ -0,0 +1,8 @@
+#include "Network.h"
+
+static const uint8_t dummy_ipv4[4] = { 0, 0, 0, 0 };
+
+const uint8_t *Network::IPAddress() const
+{
+ return dummy_ipv4;
+}
diff --git a/src/RADDS/Network.h b/src/RADDS/Network.h
new file mode 100644
index 00000000..0cc3db50
--- /dev/null
+++ b/src/RADDS/Network.h
@@ -0,0 +1,34 @@
+#ifndef NETWORK_H
+#define NETWORK_H
+
+#include <inttypes.h>
+#include "Platform.h"
+
+const uint8_t MAC_ADDRESS[6] = { 0, 0, 0, 0, 0, 0 };
+const uint8_t IP_ADDRESS[4] = { 0, 0, 0, 0 };
+const uint8_t NET_MASK[4] = { 0, 0, 0, 0 };
+const uint8_t GATE_WAY[4] = { 0, 0, 0, 0 };
+
+// The main network class that drives the network.
+class Network
+{
+public:
+ Network(Platform* p) { };
+ void Init() const { };
+ void Activate() const { };
+ void Disable() const { };
+ void Enable() const { };
+ void Exit() const { }
+ void Spin() const { };
+ void Interrupt() const { };
+ void Diagnostics(MessageType mtype) const { };
+
+ boolean IsEnabled() const { return false; }
+ boolean InLwip() const { return false; }
+ void SetHostname(const char *name) const { };
+ void SetHttpPort(uint16_t port) const { };
+ uint16_t GetHttpPort() const { return (uint16_t)0; }
+ const uint8_t *IPAddress() const;
+};
+
+#endif
diff --git a/src/RADDS/Pins_radds.h b/src/RADDS/Pins_radds.h
new file mode 100644
index 00000000..09beff9c
--- /dev/null
+++ b/src/RADDS/Pins_radds.h
@@ -0,0 +1,248 @@
+#ifndef PINS_DUET_H__
+#define PINS_DUET_H__
+
+#define NAME "RepRapFirmware for Duet"
+
+const size_t NumFirmwareUpdateModules = 1;
+#define IAP_UPDATE_FILE "iapradds.bin"
+#define IAP_FIRMWARE_FILE "RepRapFirmware.bin"
+
+// Default board type
+#define DEFAULT_BOARD_TYPE BoardType::RADDS_15
+#define ELECTRONICS "RADDS (+ Extension)"
+
+#define SUPPORT_INKJET 0 // set nonzero to support inkjet control
+#define SUPPORT_ROLAND 0 // set nonzero to support Roland mill
+
+// The physical capabilities of the machine
+
+// The number of drives in the machine, including X, Y, and Z plus extruder drives
+const size_t DRIVES = 8;
+
+// Initialization macro used in statements needing to initialize values in arrays of size DRIVES. E.g.,
+// max_feed_rates[DRIVES] = {DRIVES_(1, 1, 1, 1, 1, 1, 1, 1, 1)}
+#define DRIVES_(a,b,c,d,e,f,g,h,i,j) { a,b,c,d,e,f,g,h }
+const size_t MaxDriversPerAxis = 4; // The maximum number of stepper drivers assigned to one axis
+
+// The number of heaters in the machine
+// 0 is the heated bed even if there isn't one.
+const int8_t HEATERS = 4;
+
+// Initialization macro used in statements needing to initialize values in arrays of size HEATERS. E.g.,
+// defaultPidKis[HEATERS] = {HEATERS_(5.0, 0.1, 0.1, 0.1, 0.1, 0.1)};
+#define HEATERS_(a,b,c,d,e,f,g,h) { a,b,c,d }
+
+const size_t MAX_AXES = 6; // FIXME The maximum number of movement axes in the machine, usually just X, Y and Z, <= DRIVES
+const size_t MIN_AXES = 3; // The minimum and default number of axes
+const size_t DELTA_AXES = 3; // The number of axis involved in delta movement
+const size_t CART_AXES = 3; // The number of Cartesian axes
+const size_t MaxExtruders = DRIVES - MIN_AXES; // The maximum number of extruders
+
+const size_t NUM_SERIAL_CHANNELS = 2;
+// Use TX0/RX0 for the auxiliary serial line
+#define SERIAL_MAIN_DEVICE SerialUSB
+#define SERIAL_AUX_DEVICE Serial1
+
+// The numbers of entries in each array must correspond with the values of DRIVES, AXES, or HEATERS. Set values to -1 to flag unavailability.
+// DRIVES
+// X Y Z E1 E2 E3 E4 E5
+const Pin ENABLE_PINS[DRIVES] = { 26, 22, 15, 62, 65, 49, 37, 31 };
+const bool ENABLE_VALUES[DRIVES] = { false, false, false, false, false, false, false, false };
+// A15 D04 B25 A02 B19 C12 C03 D06
+const Pin STEP_PINS[DRIVES] = { 24, 17, 2, 61, 64, 51, 35, 29 };
+const Pin DIRECTION_PINS[DRIVES] = { 23, 16, 3, 60, 63, 53, 33, 27 };
+
+// Endstops
+// E Stops not currently used
+// Note: RepRapFirmware only as a single endstop per axis
+// gcode defines if it is a max ("high end") or min ("low end")
+// endstop. gcode also sets if it is active HIGH or LOW
+//
+// 28 = RADDS X min
+// 30 = RADDS Y min
+// 32 = RADDS Z min
+// 39 = RADDS PWM3
+//
+// This leaves 34, 36, and 38 as spare pins (X, Y, Z max)
+
+const Pin END_STOP_PINS[DRIVES] = { 28, 30, 32, 39, NoPin, NoPin, NoPin, NoPin };
+
+// HEATERS - The bed is assumed to be the at index 0
+
+// 0 for inverted heater (e.g. Duet v0.6)
+// 1 for not (e.g. Duet v0.4; RADDS)
+const bool HEAT_ON = true;
+
+// Analogue pin numbers
+const Pin TEMP_SENSE_PINS[HEATERS] = HEATERS_(4, 0, 1, 2, e, f, g, h);
+
+// Heater outputs
+// Bed PMW: D7 has hardware PWM so bed has PWM
+// h0, h1 PMW: D13 & D12 are on TIOB0 & B8 which are both TC B channels, so they get PWM
+// h2 bang-bang: D11 is on TIOA8 which is a TC A channel shared with h1, it gets bang-bang control
+
+const Pin HEAT_ON_PINS[HEATERS] = HEATERS_(7, 13, 12, 11, e, f, g, h); // bed, h0, h1, h2
+
+// Default thermistor betas
+const float BED_R25 = 10000.0;
+const float BED_BETA = 4066.0;
+const float EXT_R25 = 100000.0;
+const float EXT_BETA = 4066.0;
+
+// Thermistor series resistor value in Ohms
+const float THERMISTOR_SERIES_RS = 4700.0;
+
+const size_t MaxSpiTempSensors = 2;
+
+// Digital pins the 31855s have their select lines tied to
+const Pin SpiTempSensorCsPins[MaxSpiTempSensors] = { 38, 36 };
+
+// Digital pin number that controls the ATX power on/off
+const Pin ATX_POWER_PIN = 40;
+
+// Z Probe pin
+// Must be an ADC capable pin. Can be any of the ARM's A/D capable
+// pins even a non-Arduino pin.
+const Pin Z_PROBE_PIN = 5; // RADDS "ADC" pin
+
+// Digital pin number to turn the IR LED on (high) or off (low)
+// channel 1: Z_PROBE_MOD_PIN07
+// channel 0: Z_PROBE_MOD_PIN
+// Make channel 0 == channel 1 for RADDS. Difference in channels
+// is an artifact of difference in different versions of Duet electronics
+//
+// D34 -- unused X-max on RADDS
+const Pin Z_PROBE_MOD_PIN = 34;
+const Pin Z_PROBE_MOD_PIN07 = 34;
+
+// Use a PWM capable pin
+// Firmware uses SamNonDue so feel free to use a non-Arduino pin
+const size_t NUM_FANS = 2;
+const Pin COOLING_FAN_PINS[NUM_FANS] = { 9, 8 }; // Fan 0, Fan 1
+
+// Firmware will attach a FALLING interrupt to this pin
+// see FanInterrupt() in Platform.cpp
+//
+// D25 -- Unused GPIO on AUX1
+const Pin COOLING_FAN_RPM_PIN = 25;
+
+// SD cards
+const size_t NumSdCards = 1;
+const Pin SdCardDetectPins[NumSdCards] = {NoPin};
+const Pin SdWriteProtectPins[NumSdCards] = { NoPin};
+const Pin SdSpiCSPins[1] = {zz}; //FIXME
+
+// Definition of which pins we allow to be controlled using M42
+//
+// Spare pins on the Arduino Due are
+//
+// D5 / TIOA6 / C.25
+// D6 / PWML7 / C.24
+// ### Removed: now E0_AXIS endstop D39 / PWMH2 / C.7
+// D58 / AD3 / A.6
+// D59 / AD2 / A.4
+// D66 / DAC0 / B.15
+// D67 / DAC1 / B.16
+// D68 / CANRX0 / A.1
+// D69 / CANTX0 / A.0
+// D70 / SDA1 / A.17
+// D71 / SCL1 / A.18
+// D72 / RX LED / C.30
+// D73 / TX LED / A.21
+
+const size_t NUM_PINS_ALLOWED = 80;
+#define PINS_ALLOWED { \
+ /* pins 00-07 */ 0b01100000, \
+ /* pins 08-15 */ 0, \
+ /* pins 16-23 */ 0, \
+ /* pins 24-31 */ 0, \
+ /* pins 32-39 */ 0b00000000, \
+ /* pins 40-47 */ 0, \
+ /* pins 48-55 */ 0, \
+ /* pins 56-63 */ 0b00001100, \
+ /* pins 64-71 */ 0b11111100, \
+ /* pins 72-79 */ 0b00000011 \
+}
+
+
+// SAM3X Flash locations (may be expanded in the future)
+const uint32_t IAP_FLASH_START = 0x000F0000;
+const uint32_t IAP_FLASH_END = 0x000FFBFF; // don't touch the last 1KB, it's used for NvData
+
+// Timer allocation
+#define NETWORK_TC (TC1)
+#define NETWORK_TC_CHAN (1)
+#define NETWORK_TC_IRQN TC4_IRQn
+#define NETWORK_TC_HANDLER TC4_Handler
+
+#define STEP_TC (TC1)
+#define STEP_TC_CHAN (0)
+#define STEP_TC_IRQN TC3_IRQn
+#define STEP_TC_HANDLER TC3_Handler
+
+// Hardware SPI support for SD cards
+
+// SD select
+#define SD_SS 4
+#define SD_DETECT_PIN 14 // card detect switch; needs pullup asserted
+#define SD_DETECT_VAL 0 // detect switch active low
+#define SD_DETECT_PIO_ID ID_PIOD
+
+#ifdef LCD_UI
+
+// Hardware I2C support for LCD
+#define TWI_ID ID_TWI1
+
+#define FEATURE_CONTROLLER 7
+#define UI_PAGES_DURATION 4000
+#define UI_ANIMATION 0
+#define UI_SPEEDDEPENDENT_POSITIONING 0
+#define UI_DISABLE_AUTO_PAGESWITCH 1
+#define UI_AUTORETURN_TO_MENU_AFTER 30000
+#define UI_ENCODER_SPEED 1
+#define UI_KEY_BOUNCETIME 10
+#define UI_KEY_FIRST_REPEAT 500
+#define UI_KEY_REDUCE_REPEAT 50
+#define UI_KEY_MIN_REPEAT 50
+#define FEATURE_BEEPER 1
+#define UI_START_SCREEN_DELAY 1000
+
+#define CASE_LIGHTS_PIN -1
+#define SPI_PIN 77
+#define SPI_CHAN 0
+#define UI_HAS_KEYS 1
+#define UI_HAS_BACK_KEY 1
+#define UI_DISPLAY_TYPE 1
+#define UI_DISPLAY_CHARSET 1
+#define BEEPER_TYPE 1
+#define UI_COLS 20
+#define UI_ROWS 4
+#define BEEPER_PIN 41
+#define UI_DISPLAY_RS_PIN 42
+#define UI_DISPLAY_RW_PIN -1
+#define UI_DISPLAY_ENABLE_PIN 43
+#define UI_DISPLAY_D0_PIN 44
+#define UI_DISPLAY_D1_PIN 45
+#define UI_DISPLAY_D2_PIN 46
+#define UI_DISPLAY_D3_PIN 47
+#define UI_DISPLAY_D4_PIN 44
+#define UI_DISPLAY_D5_PIN 45
+#define UI_DISPLAY_D6_PIN 46
+#define UI_DISPLAY_D7_PIN 47
+#define UI_ENCODER_A 52
+#define UI_ENCODER_B 50
+#define UI_ENCODER_CLICK 48
+#define UI_RESET_PIN -1
+#define UI_DELAYPERCHAR 40
+#define UI_INVERT_MENU_DIRECTION 0
+#define UI_BUTTON_BACK 71
+
+// Beeper sound definitions for short beeps during key actions and longer
+// beeps for important actions. Parameters are the delay in microseconds
+// followed by the number of repetitions. Values must be in range 1..255
+#define BEEPER_SHORT_SEQUENCE 2,2
+#define BEEPER_LONG_SEQUENCE 8,8
+
+#endif // LCD_UI
+
+#endif
diff --git a/src/RADDS/Webserver.h b/src/RADDS/Webserver.h
new file mode 100644
index 00000000..0c8accb0
--- /dev/null
+++ b/src/RADDS/Webserver.h
@@ -0,0 +1,38 @@
+#ifndef WEBSERVER_H
+#define WEBSERVER_H
+
+#include <inttypes.h>
+#include "OutputMemory.h"
+
+// List of protocols that can execute G-Codes
+enum class WebSource
+{
+ HTTP,
+ Telnet
+};
+
+class Webserver
+{
+public:
+ Webserver(Platform* p, Network *n) { };
+ void Init() const { };
+ void Spin() const { };
+ void Exit() const { };
+ void Diagnostics(MessageType mtype) const { };
+
+ bool GCodeAvailable(const WebSource source) const { return false; }
+ char ReadGCode(const WebSource source) const { return '\0'; }
+ uint32_t GetReplySeq() const { return (uint32_t)0; }
+ uint16_t GetGCodeBufferSpace(const WebSource source) const { return 0; }
+
+ void HandleGCodeReply(const WebSource source, OutputBuffer *reply) const;
+ void HandleGCodeReply(const WebSource source, const char *reply) const { };
+};
+
+inline void Webserver::HandleGCodeReply(const WebSource source, OutputBuffer *reply) const
+{
+ if (reply != (OutputBuffer *)0)
+ OutputBuffer::ReleaseAll(reply);
+}
+
+#endif
diff --git a/src/Reprap.cpp b/src/Reprap.cpp
index d3cd3288..aa372a27 100644
--- a/src/Reprap.cpp
+++ b/src/Reprap.cpp
@@ -166,8 +166,8 @@ void RepRap::Spin()
// Keep track of the loop time
- float t = platform->Time();
- float dt = t - lastTime;
+ const float t = platform->Time();
+ const float dt = t - lastTime;
if(dt < fastLoop)
{
fastLoop = dt;
@@ -279,6 +279,7 @@ void RepRap::PrintDebug()
// Add a tool.
// Prior to calling this, delete any existing tool with the same number
+// The tool list is maintained in tool number order.
void RepRap::AddTool(Tool* tool)
{
Tool** t = &toolList;
@@ -401,9 +402,12 @@ Tool* RepRap::GetTool(int toolNumber) const
return nullptr; // Not an error
}
-Tool* RepRap::GetOnlyTool() const
+// Get the current tool, or failing that the default tool. May return nullptr if we can't
+// Called when a M104 or M109 command doesn't specify a tool number.
+Tool* RepRap::GetCurrentOrDefaultTool() const
{
- return (toolList != nullptr && toolList->Next() == nullptr) ? toolList : nullptr;
+ // If a tool is already selected, use that one, else use the lowest-numbered tool which is the one at the start of the tool list
+ return (currentTool != nullptr) ? currentTool : toolList;
}
void RepRap::SetToolVariables(int toolNumber, const float* standbyTemperatures, const float* activeTemperatures)
@@ -449,6 +453,16 @@ bool RepRap::IsHeaterAssignedToTool(int8_t heater) const
return false;
}
+unsigned int RepRap::GetNumberOfContiguousTools() const
+{
+ unsigned int numTools = 0;
+ while (GetTool(numTools) != nullptr)
+ {
+ ++numTools;
+ }
+ return numTools;
+}
+
void RepRap::Tick()
{
if (active)
@@ -561,7 +575,7 @@ OutputBuffer *RepRap::GetStatusResponse(uint8_t type, ResponseSource source)
}
// Current tool number
- int toolNumber = (currentTool == nullptr) ? -1 : currentTool->Number();
+ const int toolNumber = (currentTool == nullptr) ? -1 : currentTool->Number();
response->catf("]},\"currentTool\":%d", toolNumber);
// Output - only reported once
@@ -633,7 +647,7 @@ OutputBuffer *RepRap::GetStatusResponse(uint8_t type, ResponseSource source)
response->cat(",\"sensors\":{");
// Probe
- int v0 = platform->ZProbe();
+ const int v0 = platform->ZProbe();
int v1, v2;
switch (platform->GetZProbeSecondaryValues(v1, v2))
{
@@ -1117,7 +1131,7 @@ OutputBuffer *RepRap::GetLegacyStatusResponse(uint8_t type, int seq)
const size_t numAxes = reprap.GetGCodes()->GetNumAxes();
float liveCoordinates[DRIVES];
reprap.GetMove()->LiveCoordinates(liveCoordinates, GetCurrentXAxes());
- const Tool* currentTool = reprap.GetCurrentTool();
+ const Tool* const currentTool = reprap.GetCurrentTool();
if (currentTool != nullptr)
{
const float *offset = currentTool->GetOffset();
@@ -1155,11 +1169,11 @@ OutputBuffer *RepRap::GetLegacyStatusResponse(uint8_t type, int seq)
response->cat((ch == '[') ? "[]" : "]");
// Send the current tool number
- int toolNumber = (currentTool == nullptr) ? 0 : currentTool->Number();
+ const int toolNumber = (currentTool == nullptr) ? 0 : currentTool->Number();
response->catf(",\"tool\":%d", toolNumber);
// Send the Z probe value
- int v0 = platform->ZProbe();
+ const int v0 = platform->ZProbe();
int v1, v2;
switch (platform->GetZProbeSecondaryValues(v1, v2))
{
@@ -1218,24 +1232,24 @@ OutputBuffer *RepRap::GetLegacyStatusResponse(uint8_t type, int seq)
}
else if (type == 3)
{
- // Add the static fields. For now this is just geometry and the machine name, but other fields could be added e.g. axis lengths.
- response->catf(",\"geometry\":\"%s\",\"axes\":%u,\"volumes\":%u,\"myName\":", move->GetGeometryString(), numAxes, NumSdCards);
+ // Add the static fields
+ response->catf(",\"geometry\":\"%s\",\"axes\":%u,\"volumes\":%u,\"numTools\":%u,\"myName\":",
+ move->GetGeometryString(), numAxes, NumSdCards, GetNumberOfContiguousTools());
response->EncodeString(myName, ARRAY_SIZE(myName), false);
}
- int auxSeq = (int)platform->GetAuxSeq();
- if (type < 2 || (seq != -1 && (int)auxSeq != seq))
+ const int auxSeq = (int)platform->GetAuxSeq();
+ if (type < 2 || (seq != -1 && auxSeq != seq))
{
// Send the response to the last command. Do this last because it can be long and may need to be truncated.
- response->catf(",\"seq\":%u,\"resp\":", auxSeq); // send the response sequence number
+ response->catf(",\"seq\":%d,\"resp\":", auxSeq); // send the response sequence number
// Send the JSON response
response->EncodeReply(platform->GetAuxGCodeReply(), true); // also releases the OutputBuffer chain
}
response->cat("}");
-
return response;
}
diff --git a/src/Reprap.h b/src/Reprap.h
index f7408bdd..8b80d527 100644
--- a/src/Reprap.h
+++ b/src/Reprap.h
@@ -58,12 +58,12 @@ public:
void StandbyTool(int toolNumber);
Tool* GetCurrentTool() const;
Tool* GetTool(int toolNumber) const;
- Tool* GetOnlyTool() const;
+ Tool* GetCurrentOrDefaultTool() const;
uint32_t GetCurrentXAxes() const; // Get the current axes used as X axes
- //Tool* GetToolByDrive(int driveNumber);
void SetToolVariables(int toolNumber, const float* standbyTemperatures, const float* activeTemperatures);
bool ToolWarningsAllowed();
bool IsHeaterAssignedToTool(int8_t heater) const;
+ unsigned int GetNumberOfContiguousTools() const;
unsigned int GetProhibitedExtruderMovements(unsigned int extrusions, unsigned int retractions);
void PrintTool(int toolNumber, StringRef& reply) const;
diff --git a/src/Storage/FileStore.cpp b/src/Storage/FileStore.cpp
index e62c8f1d..680032f8 100644
--- a/src/Storage/FileStore.cpp
+++ b/src/Storage/FileStore.cpp
@@ -265,6 +265,40 @@ int FileStore::Read(char* extBuf, size_t nBytes)
return (int)bytes_read;
}
+// As Read but stop after '\n' or '\r\n' and null-terminate the string.
+// If the next line is too long to fit in the buffer then the line will be split.
+int FileStore::ReadLine(char* buf, size_t nBytes)
+{
+ const FilePosition lineStart = Position();
+ const int r = Read(buf, nBytes);
+ if (r < 0)
+ {
+ return r;
+ }
+
+ int i = 0;
+ while (i < r && buf[i] != '\r' && buf[i] != '\n')
+ {
+ ++i;
+ }
+
+ if (i + 1 < r && buf[i] == '\r' && buf[i + 1] == '\n') // if stopped at CRLF (Windows-style line end)
+ {
+ Seek(lineStart + i + 2); // seek to just after the CRLF
+ }
+ else if (i < r) // if stopped at CR or LF
+ {
+ Seek(lineStart + i + 1); // seek to just after the CR or LF
+ }
+ else if (i == (int)nBytes)
+ {
+ --i; // make room for the null terminator
+ Seek(lineStart + i);
+ }
+ buf[i] = 0;
+ return i;
+}
+
bool FileStore::WriteBuffer()
{
if (bufferPointer != 0)
diff --git a/src/Storage/FileStore.h b/src/Storage/FileStore.h
index 23e4e97d..e998e3e8 100644
--- a/src/Storage/FileStore.h
+++ b/src/Storage/FileStore.h
@@ -28,6 +28,7 @@ public:
uint8_t Status(); // Returns OR of IOStatus
bool Read(char& b); // Read 1 byte
int Read(char* buf, size_t nBytes); // Read a block of nBytes length
+ int ReadLine(char* buf, size_t nBytes); // As Read but stop after '\n' or '\r\n' and null-terminate
bool Write(char b); // Write 1 byte
bool Write(const char *s, size_t len); // Write a block of len bytes
bool Write(const char* s); // Write a string