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--.cproject128
-rw-r--r--.settings/language.settings.xml11
-rw-r--r--.settings/org.eclipse.cdt.core.prefs8
-rw-r--r--src/Configuration.h10
-rw-r--r--src/DuetNG/FirmwareUpdater.cpp64
-rw-r--r--src/DuetNG/FirmwareUpdater.h20
-rw-r--r--src/DuetNG/Network.cpp1084
-rw-r--r--src/DuetNG/Network.h121
-rw-r--r--src/DuetNG/Pins_DuetNG.h186
-rw-r--r--src/DuetNG/TransactionBuffer.cpp58
-rw-r--r--src/DuetNG/TransactionBuffer.h165
-rw-r--r--src/DuetNG/Webserver.cpp1059
-rw-r--r--src/DuetNG/Webserver.h171
-rw-r--r--src/DuetNG/WifiFirmwareUploader.cpp743
-rw-r--r--src/DuetNG/WifiFirmwareUploader.h96
-rw-r--r--src/ExternalDrivers.cpp72
-rw-r--r--src/GCodeBuffer.cpp23
-rw-r--r--src/GCodes.cpp181
-rw-r--r--src/GCodes.h9
-rw-r--r--src/Heat.cpp13
-rw-r--r--src/Platform.cpp162
-rw-r--r--src/Platform.h15
-rw-r--r--src/Reprap.cpp59
-rw-r--r--src/TemperatureError.cpp5
24 files changed, 4266 insertions, 197 deletions
diff --git a/.cproject b/.cproject
index fdb51e37..def8a038 100644
--- a/.cproject
+++ b/.cproject
@@ -137,7 +137,7 @@
</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" name="SAM4E_CoreNG" 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">
+ <configuration artifactExtension="elf" artifactName="DuetWiFiFirmware" 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" name="SAM4E_CoreNG" parent="cdt.managedbuild.config.gnu.cross.exe.release" postannouncebuildStep="Generating binary file" postbuildStep="arm-none-eabi-objcopy -O binary ${workspace_loc:/${ProjName}/${ConfigName}}/DuetWiFiFirmware.elf ${workspace_loc:/${ProjName}/${ConfigName}}/DuetWiFiFirmware.bin">
<folderInfo id="cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451." name="/" resourcePath="">
<toolChain id="cdt.managedbuild.toolchain.gnu.cross.exe.release.1362047835" name="Cross GCC" superClass="cdt.managedbuild.toolchain.gnu.cross.exe.release">
<option id="cdt.managedbuild.option.gnu.cross.path.1491883811" 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"/>
@@ -206,7 +206,10 @@
<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/matrix}&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/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;"/>
@@ -234,6 +237,129 @@
</toolChain>
</folderInfo>
<sourceEntries>
+ <entry excluding="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">
+ <macros>
+ <stringMacro name="CoreName" type="VALUE_TEXT" value="CoreNG"/>
+ </macros>
+ <externalSettings/>
+ <extensions>
+ <extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+ <extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
+ <extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+ <extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+ <extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+ <extension id="org.eclipse.cdt.core.ELF" point="org.eclipse.cdt.core.BinaryParser"/>
+ </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"/>
+ </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">
+ <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/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/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;"/>
+ </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"/>
+ <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"/>
+ </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;"/>
+ </option>
+ <option id="gnu.cpp.link.option.libs.1868917950" 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">
+ <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">
+ <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/SharedSpi}&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/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/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/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:/${ProjName}/src}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/src/DuetNG}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/src/Libraries/Fatfs}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/src/Libraries/MCP4461}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/src/Libraries/Flash}&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"/>
+ <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"/>
+ </tool>
+ </toolChain>
+ </folderInfo>
+ <sourceEntries>
<entry excluding="src/Duet" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/>
</sourceEntries>
</configuration>
diff --git a/.settings/language.settings.xml b/.settings/language.settings.xml
index 585ff377..e47fc4b9 100644
--- a/.settings/language.settings.xml
+++ b/.settings/language.settings.xml
@@ -22,4 +22,15 @@
</provider>
</extension>
</configuration>
+ <configuration id="cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080" name="SAM4E_PROTO1">
+ <extension point="org.eclipse.cdt.core.LanguageSettingsProvider">
+ <provider copy-of="extension" id="org.eclipse.cdt.ui.UserLanguageSettingsProvider"/>
+ <provider-reference id="org.eclipse.cdt.core.ReferencedProjectsLanguageSettingsProvider" ref="shared-provider"/>
+ <provider-reference id="org.eclipse.cdt.managedbuilder.core.MBSLanguageSettingsProvider" ref="shared-provider"/>
+ <provider class="org.eclipse.cdt.internal.build.crossgcc.CrossGCCBuiltinSpecsDetector" console="false" env-hash="337623625225680352" id="org.eclipse.cdt.build.crossgcc.CrossGCCBuiltinSpecsDetector" keep-relative-paths="false" name="CDT Cross GCC Built-in Compiler Settings" parameter="${COMMAND} ${FLAGS} -E -P -v -dD &quot;${INPUTS}&quot;" prefer-non-shared="true">
+ <language-scope id="org.eclipse.cdt.core.gcc"/>
+ <language-scope id="org.eclipse.cdt.core.g++"/>
+ </provider>
+ </extension>
+ </configuration>
</project>
diff --git a/.settings/org.eclipse.cdt.core.prefs b/.settings/org.eclipse.cdt.core.prefs
index e296b810..0d147d57 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.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
+environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080/LINK_FLAGS_2/delimiter=;
+environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080/LINK_FLAGS_2/operation=append
+environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080/LINK_FLAGS_2/value=-Wl,--end-group -lm -gcc
+environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080/append=true
+environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.286403080/appendContributed=true
environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451/LINK_FLAGS_1/delimiter=;
environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451/LINK_FLAGS_1/operation=append
environment/project/cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451/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/src/Configuration.h b/src/Configuration.h
index e972ca60..32962045 100644
--- a/src/Configuration.h
+++ b/src/Configuration.h
@@ -26,11 +26,11 @@ Licence: GPL
// Firmware name is now defined in the Pins file
#ifndef VERSION
-# define VERSION "1.13beta1"
+# define VERSION "1.13"
#endif
#ifndef DATE
-# define DATE "2016-05-27"
+# define DATE "2016-06-13"
#endif
#define AUTHORS "reprappro, dc42, zpl, t3p3, dnewman"
@@ -68,7 +68,7 @@ const unsigned int MAIN_BAUD_RATE = 115200; // Default communication speed of
const unsigned int AUX_BAUD_RATE = 57600; // Ditto - for auxiliary UART device
const unsigned int AUX2_BAUD_RATE = 115200; // Ditto - for second auxiliary UART device
-const uint32_t SERIAL_MAIN_TIMEOUT = 1000; // timeout in ms for sending dara to the main serial/USB port
+const uint32_t SERIAL_MAIN_TIMEOUT = 1000; // timeout in ms for sending data to the main serial/USB port
// Heater values
@@ -83,7 +83,7 @@ const float TIME_TO_HOT = 150.0; // Seconds
const uint8_t MAX_BAD_TEMPERATURE_COUNT = 4; // Number of bad temperature samples permitted before a heater fault is reported
const float BAD_LOW_TEMPERATURE = -10.0; // Celsius
-const float DEFAULT_TEMPERATURE_LIMIT = 260.0; // Celsius
+const float DEFAULT_TEMPERATURE_LIMIT = 262.0; // Celsius
const float HOT_END_FAN_TEMPERATURE = 45.0; // Temperature at which a thermostatic hot end fan comes on
const float BAD_ERROR_TEMPERATURE = 2000.0; // Must exceed any reasonable 5temperature limit including DEFAULT_TEMPERATURE_LIMIT
@@ -93,7 +93,7 @@ const unsigned int FirstRtdChannel = 200; // Temperature sensor channels 200..
// PWM frequencies
const unsigned int SlowHeaterPwmFreq = 10; // slow PWM frequency for bed and chamber heaters, compatible with DC/AC SSRs
-const unsigned int NormalHeaterPwmFreq = 500; // normal PWM frequency used for hot ends
+const unsigned int NormalHeaterPwmFreq = 250; // normal PWM frequency used for hot ends
const unsigned int DefaultFanPwmFreq = 500; // increase to 25kHz using M106 command to meet Intel 4-wire PWM fan specification
// Default Z probe values
diff --git a/src/DuetNG/FirmwareUpdater.cpp b/src/DuetNG/FirmwareUpdater.cpp
new file mode 100644
index 00000000..aace1bef
--- /dev/null
+++ b/src/DuetNG/FirmwareUpdater.cpp
@@ -0,0 +1,64 @@
+/*
+ * FirmwareUpdater.cpp
+ *
+ * Created on: 21 May 2016
+ * Author: David
+ */
+
+#include "FirmwareUpdater.h"
+#include "RepRapFirmware.h"
+#include "WifiFirmwareUploader.h"
+
+namespace FirmwareUpdater
+{
+ const unsigned int WifiFirmwareModule = 1;
+ const unsigned int WifiFilesModule = 2;
+ const unsigned int WifiExternalFirmwareModule = 3;
+
+ // Check that the prerequisites are satisfied.
+ // Return true if yes, else print a message and return false.
+ bool CheckFirmwareUpdatePrerequisites(uint8_t moduleMap)
+ {
+ if ((moduleMap & (1 << WifiExternalFirmwareModule)) != 0 && (moduleMap & ((1 << WifiFirmwareModule) | (1 << WifiFilesModule))) != 0)
+ {
+ reprap.GetPlatform()->Message(GENERIC_MESSAGE, "Invalid combination of firmware update modules\n");
+ return false;
+ }
+ if ((moduleMap & (1 << WifiFirmwareModule)) != 0 && !reprap.GetPlatform()->GetMassStorage()->FileExists(SYS_DIR, WIFI_FIRMWARE_FILE))
+ {
+ reprap.GetPlatform()->MessageF(GENERIC_MESSAGE, "File %s not found\n", WIFI_FIRMWARE_FILE);
+ return false;
+ }
+ if ((moduleMap & (1 << WifiFilesModule)) != 0 && !reprap.GetPlatform()->GetMassStorage()->FileExists(SYS_DIR, WIFI_WEB_FILE))
+ {
+ reprap.GetPlatform()->MessageF(GENERIC_MESSAGE, "File %s not found\n", WIFI_WEB_FILE);
+ return false;
+ }
+ return true;
+ }
+
+ bool IsReady()
+ {
+ return reprap.GetNetwork()->GetWifiUploader()->IsReady();
+ }
+
+ void UpdateModule(unsigned int module)
+ {
+ switch(module)
+ {
+ case WifiExternalFirmwareModule:
+ Network::ResetWiFiForExternalUpload();
+ break;
+
+ case WifiFirmwareModule:
+ reprap.GetNetwork()->GetWifiUploader()->SendUpdateFile(WIFI_FIRMWARE_FILE, SYS_DIR, WifiFirmwareUploader::FirmwareAddress);
+ break;
+
+ case WifiFilesModule:
+ reprap.GetNetwork()->GetWifiUploader()->SendUpdateFile(WIFI_WEB_FILE, SYS_DIR, WifiFirmwareUploader::WebFilesAddress);
+ break;
+ }
+ }
+}
+
+// End
diff --git a/src/DuetNG/FirmwareUpdater.h b/src/DuetNG/FirmwareUpdater.h
new file mode 100644
index 00000000..67b1b710
--- /dev/null
+++ b/src/DuetNG/FirmwareUpdater.h
@@ -0,0 +1,20 @@
+/*
+ * FirmwareUpdater.h
+ *
+ * Created on: 21 May 2016
+ * Author: David
+ */
+
+#ifndef SRC_DUETNG_FIRMWAREUPDATER_H_
+#define SRC_DUETNG_FIRMWAREUPDATER_H_
+
+#include <cstdint>
+
+namespace FirmwareUpdater
+{
+ bool CheckFirmwareUpdatePrerequisites(uint8_t moduleMap);
+ bool IsReady();
+ void UpdateModule(unsigned int module);
+}
+
+#endif /* SRC_DUETNG_FIRMWAREUPDATER_H_ */
diff --git a/src/DuetNG/Network.cpp b/src/DuetNG/Network.cpp
new file mode 100644
index 00000000..1578dda2
--- /dev/null
+++ b/src/DuetNG/Network.cpp
@@ -0,0 +1,1084 @@
+/****************************************************************************************************
+
+ RepRapFirmware network comms to ESP8266-based device
+
+ ****************************************************************************************************/
+
+#include "WifiFirmwareUploader.h"
+#include "RepRapFirmware.h"
+#include "compiler.h"
+#include "Pins.h"
+#include "WifiFirmwareUploader.h"
+#include "TransactionBuffer.h"
+
+// Define exactly one of the following as 1, thje other as zero
+#define USE_PDC 0 // use peripheral DMA controller
+#define USE_DMAC 1 // use general DMA controller
+
+#if USE_PDC
+#include "pdc.h"
+#endif
+
+#if USE_DMAC
+#include "dmac.h"
+#endif
+
+#include "matrix.h"
+
+// Forward declarations of static functions
+static void spi_dma_disable();
+static bool spi_dma_check_rx_complete();
+
+static TransactionBuffer inBuffer, outBuffer;
+static uint32_t dummyOutBuffer[TransactionBuffer::headerDwords] = {0, 0, 0, 0, 0};
+
+void EspTransferRequestIsr()
+{
+ reprap.GetNetwork()->EspRequestsTransfer();
+}
+
+/*-----------------------------------------------------------------------------------*/
+// WiFi interface class
+
+Network::Network(Platform* p) : platform(p), responseCode(0), responseBody(nullptr), responseText(nullptr), responseFile(nullptr),
+ spiTxUnderruns(0), spiRxOverruns(0),
+ state(disabled), activated(false), connectedToAp(false)
+
+{
+ strcpy(hostname, HOSTNAME);
+ ClearIpAddress();
+}
+
+void Network::Init()
+{
+ // Make sure the ESP8266 is held in the reset state
+ pinMode(EspResetPin, OUTPUT);
+ digitalWrite(EspResetPin, LOW);
+ uploader = new WifiFirmwareUploader(Serial1);
+}
+
+void Network::Activate()
+{
+ activated = true;
+ if (state == enabled)
+ {
+ Start();
+ }
+}
+
+void Network::Exit()
+{
+ Stop();
+}
+
+void Network::ClearIpAddress()
+{
+ for (size_t i = 0; i < ARRAY_SIZE(ipAddress); ++i)
+ {
+ ipAddress[i] = 0;
+ }
+}
+
+// Start up the ESP. We assume it is not already started.
+// ESP8266 boot modes:
+// GPIO0 GPIO2 GPIO15
+// 0 1 0 Firmware download from UART
+// 1 1 0 Normal boot from flash memory
+// 0 0 1 SD card boot (not used in on Duet)
+void Network::Start()
+{
+ // The ESP8266 is held in a reset state by a pulldown resistor until we enable it.
+ // Make sure the ESP8266 is in the reset state
+ pinMode(EspResetPin, OUTPUT);
+ digitalWrite(EspResetPin, LOW);
+
+ // Take the ESP8266 out of power down
+ pinMode(EspEnablePin, OUTPUT);
+ digitalWrite(EspEnablePin, HIGH);
+
+ // Set up our transfer request pin (GPIO4) as an output and set it low
+ pinMode(SamTfrReadyPin, OUTPUT);
+ digitalWrite(SamTfrReadyPin, LOW);
+
+ // Set up our data ready pin (ESP GPIO0) as an output and set it high ready to boot the ESP from flash
+ pinMode(EspTransferRequestPin, OUTPUT);
+ digitalWrite(EspTransferRequestPin, HIGH);
+
+ // GPIO2 also needs to be high to boot. It's connected to MISO on the SAM, so set the pullup resistor on that pin
+ pinMode(APIN_SPI_MISO, INPUT_PULLUP);
+
+ // Set our CS input (ESP GPIO15) low ready for booting the ESP. This also clears the transfer ready latch.
+ pinMode(SamCsPin, OUTPUT);
+ digitalWrite(SamCsPin, LOW);
+
+ // Make sure it has time to reset - no idea how long it needs, but 20ms should be plenty
+ delay(50);
+
+ // Release the reset on the ESP8266
+ digitalWrite(EspResetPin, HIGH);
+
+ // Give it time to sample GPIO0 and GPIO15
+ // GPIO0 has to be held high for sufficient time:
+ // - 10ms is not enough
+ // - 18ms after reset is released, an oscillating signal appears on GPIO0 for 55ms
+ // - so 18ms is probably long enough. Use 25ms for safety.
+ delay(50);
+
+ // Relinquish control of our CS pin so that the ESP can take it over
+ pinMode(SamCsPin, INPUT);
+
+ // Set the data request pin to be an input
+ pinMode(EspTransferRequestPin, INPUT_PULLUP);
+ attachInterrupt(EspTransferRequestPin, EspTransferRequestIsr, RISING);
+
+ // The ESP takes about 300ms before it starts talking to use, so don't wait for it here, do that in Spin()
+
+ // Clear the transaction buffers
+ inBuffer.Clear();
+ outBuffer.Clear();
+
+ state = starting;
+
+ (void) SPI->SPI_SR; // clear any pending interrupt
+ NVIC_SetPriority(SPI_IRQn, 10);
+ NVIC_EnableIRQ(SPI_IRQn);
+
+ connectedToAp = false;
+ spiTxUnderruns = spiRxOverruns = 0;
+}
+
+// Stop the ESP
+void Network::Stop()
+{
+ if (state != disabled)
+ {
+ digitalWrite(SamTfrReadyPin, LOW); // tell the ESP we can't receive
+ for (int i = 0; i < 10 && (state == receivePending || state == sendReceivePending); ++i)
+ {
+ delay(1);
+ }
+ digitalWrite(EspResetPin, LOW); // put the ESP back into reset
+ NVIC_DisableIRQ(SPI_IRQn);
+ spi_disable(SPI);
+ spi_dma_check_rx_complete();
+ spi_dma_disable();
+
+ ClearIpAddress();
+ state = disabled;
+ connectedToAp = false;
+ }
+}
+
+void Network::Spin()
+{
+// static float lastTime = 0.0;
+
+ // Main state machine.
+ // Take care with this, because ISRs may cause the following state transitions:
+ // idle -> transferPending
+ // transferPending -> processing
+ switch (state)
+ {
+ case starting:
+ // See if the ESP8266 has set CS high yet
+ if (digitalRead(SamCsPin) == HIGH)
+ {
+ // Setup the SPI controller in slave mode and assign the CS pin to it
+ platform->Message(HOST_MESSAGE, "WiFi server starting up\n");
+ SetupSpi(); // set up the SPI subsystem
+ state = idle;
+ TryStartTransfer();
+ }
+ break;
+
+ case transferDone:
+// platform->Message(HOST_MESSAGE, "Transfer done\n");
+ if (spi_dma_check_rx_complete())
+ {
+ if (inBuffer.IsReady())
+ {
+// platform->MessageF(DEBUG_MESSAGE, "Rec %u\n", inBuffer.GetFragment());
+ if (inBuffer.IsValid())
+ {
+ inBuffer.AppendNull();
+// platform->Message(HOST_MESSAGE, "Got data\n");
+ }
+ else
+ {
+ if (reprap.Debug(moduleNetwork))
+ {
+ platform->MessageF(DEBUG_MESSAGE, "Bad msg in: ip=%u.%u.%u.%u opcode=%04x frag=%u length=%u\n",
+ inBuffer.GetIp() & 255,
+ (inBuffer.GetIp() >> 8) & 255,
+ (inBuffer.GetIp() >> 16) & 255,
+ (inBuffer.GetIp() >> 24) & 255,
+ inBuffer.GetOpcode(),
+ inBuffer.GetFragment(),
+ inBuffer.GetLength()
+ );
+ }
+ inBuffer.Clear();
+ }
+ }
+ else
+ {
+// platform->MessageF(DEBUG_MESSAGE, "Rec null %u %u %u %u %u\n",
+// inBuffer.GetOpcode(), inBuffer.GetIp(), inBuffer.GetSeq(), inBuffer.GetFragment(), inBuffer.GetLength());
+ }
+ state = processing;
+ }
+ else
+ {
+ break;
+ }
+ // no break
+ case processing:
+ // Deal with incoming data, if any
+ if (inBuffer.IsReady())
+ {
+ ProcessIncomingData(inBuffer); // this may or may not clear inBuffer
+ }
+ if (!inBuffer.IsEmpty())
+ {
+ break; // still processing
+ }
+ responseFragment = 0;
+ state = sending;
+ // no break
+ case sending:
+ if (outBuffer.IsEmpty())
+ {
+ // See if we have more of the current response to send
+ if (responseBody != nullptr)
+ {
+ // We have a reply contained in an OutputBuffer
+ outBuffer.SetMessage(trTypeResponse | ttRr, responseIp, responseFragment);
+ if (responseFragment == 0)
+ {
+ // Put the return code and content length at the start of the message
+ outBuffer.AppendU32(responseCode);
+ outBuffer.AppendU32(responseBody->Length());
+ }
+
+ do
+ {
+ const size_t len = responseBody->BytesLeft();
+ const size_t bytesWritten = outBuffer.AppendData(responseBody->Read(0), len);
+ if (bytesWritten < len)
+ {
+ // Output buffer is full so will will need to send another fragment
+ (void)responseBody->Read(bytesWritten); // say how much data we have taken from the buffer
+ break;
+ }
+ responseBody = OutputBuffer::Release(responseBody);
+ }
+ while (responseBody != nullptr);
+
+ if (responseBody == nullptr)
+ {
+ outBuffer.SetLastFragment();
+ DebugPrintResponse();
+ state = idle;
+ }
+ else
+ {
+ ++responseFragment;
+ DebugPrintResponse();
+ }
+ }
+ else if (responseText != nullptr)
+ {
+ // We have a simple text reply to send
+ outBuffer.SetMessage(trTypeResponse | ttRr, responseIp, responseFragment);
+ if (responseFragment == 0)
+ {
+ // Put the return code and content length at the start of the message
+ outBuffer.AppendU32(responseCode);
+ outBuffer.AppendU32(strlen(responseText));
+ }
+
+ const size_t len = strlen(responseText);
+ const size_t lenSent = outBuffer.AppendData(responseText, len);
+ if (lenSent < len)
+ {
+ responseText += lenSent;
+ ++responseFragment;
+ DebugPrintResponse();
+ }
+ else
+ {
+ responseText = nullptr;
+ outBuffer.SetLastFragment();
+ DebugPrintResponse();
+ state = idle;
+ }
+ }
+ else if (responseFile != nullptr)
+ {
+ // We have a file reply to send
+ outBuffer.SetMessage(trTypeResponse | ttRr, responseIp, responseFragment);
+ if (responseFragment == 0)
+ {
+ // Put the return code and content length at the start of the message
+ outBuffer.AppendU32(responseCode);
+ outBuffer.AppendU32(responseFileBytes);
+ }
+
+ size_t spaceLeft;
+ char *p = outBuffer.GetBuffer(spaceLeft);
+ uint32_t bytesToRead = min<uint32_t>(spaceLeft, responseFileBytes);
+ int bytesRead = responseFile->Read(p, bytesToRead);
+ if (bytesRead >= 0 && (uint32_t)bytesRead <= bytesToRead)
+ {
+ outBuffer.DataAppended((uint32_t)bytesRead);
+ }
+
+ bool finished;
+ if (bytesRead == (int)bytesToRead)
+ {
+ responseFileBytes -= (uint32_t)bytesRead;
+ finished = (responseFileBytes == 0);
+ }
+ else
+ {
+ // We have a file read error, however it's too late to signal it unless this is the first fragment
+ finished = true;
+ }
+
+ if (finished)
+ {
+ responseFile->Close();
+ responseFile = nullptr;
+ outBuffer.SetLastFragment();
+ DebugPrintResponse();
+ state = idle;
+ }
+ else
+ {
+ ++responseFragment;
+ DebugPrintResponse();
+ }
+ }
+ else
+ {
+ state = idle;
+ }
+ TryStartTransfer();
+ }
+ break;
+
+ case idle:
+ TryStartTransfer();
+ break;
+
+ case disabled:
+ uploader->Spin();
+ break;
+
+ default:
+ break;
+ }
+
+ platform->ClassReport(longWait);
+}
+
+void Network::DebugPrintResponse()
+{
+ if (reprap.Debug(moduleNetwork))
+ {
+ char buffer[200];
+ StringRef reply(buffer, ARRAY_SIZE(buffer));
+ outBuffer.AppendNull();
+ size_t len;
+ const char* s = (const char*)outBuffer.GetData(len);
+ uint32_t frag = outBuffer.GetFragment() & ~lastFragment;
+
+ reply.printf("Resp %u: ", outBuffer.GetFragment());
+ if (frag == 0 && len >= 8)
+ {
+ // First fragment, so there is a 2-word header
+ reply.catf("%08x %08x ", *(const uint32_t*)s, *(const uint32_t*)(s + 4));
+ s += 8;
+ len -= 8;
+ }
+ if (len < 38)
+ {
+ reply.catf("%s\n", s);
+ }
+ else
+ {
+ reply.catf("%c%c%c%c...s\n", s[0], s[1], s[2], s[3], s + len - 30);
+ }
+
+ platform->Message(HOST_MESSAGE, reply.Pointer());
+ }
+}
+
+void Network::ProcessIncomingData(TransactionBuffer &buf)
+{
+ uint32_t opcode = inBuffer.GetOpcode();
+ size_t length;
+ const void *data = inBuffer.GetData(length);
+ switch(opcode & 0xFF0000FF)
+ {
+ case trTypeInfo | ttNetworkInfo:
+ // Network info received from host
+ // Data is 4 bytes of IP address, 4 bytes of free heap, 4 bytes of reset reason, 64 chars of host name, and 32 bytes of ssid
+ if (length >= 12)
+ {
+ const uint8_t *ip = (const uint8_t*) data;
+ uint32_t freeHeap = *(reinterpret_cast<const uint32_t*>(data) + 1);
+ uint32_t resetReason = *(reinterpret_cast<const uint32_t*>(data) + 2);
+ const char *hostName = reinterpret_cast<const char*>(data) + 12;
+ const char *ssid = reinterpret_cast<const char*>(data) + 76;
+ platform->MessageF(HOST_MESSAGE, "WiFi server connected to access point %s, IP=%u.%u.%u.%u\n", ssid, ip[0], ip[1], ip[2], ip[3]);
+ platform->MessageF(HOST_MESSAGE, "WiFi host name %s, free memory %u bytes, reset reason %u\n", hostName, freeHeap, resetReason);
+ memcpy(ipAddress, ip, 4);
+ connectedToAp = true;
+ }
+ inBuffer.Clear();
+ break;
+
+ case trTypeRequest | ttRr:
+#if 0
+ {
+ size_t length;
+ const char* data = (const char*)inBuffer.GetData(length);
+ if (length > 30) { data += (length - 30); }
+ platform->MessageF(DEBUG_MESSAGE, "IP %u.%u.%u.%u Frag %u %s\n",
+ inBuffer.GetIp() & 255,
+ (inBuffer.GetIp() >> 8) & 255,
+ (inBuffer.GetIp() >> 16) & 255,
+ (inBuffer.GetIp() >> 24) & 255,
+ inBuffer.GetFragment(),
+ data);
+ }
+#else
+ // Do nothing - the webserver module will pick it up
+#endif
+ break;
+
+ default:
+ {
+ size_t length;
+ const char* data = (const char*)inBuffer.GetData(length);
+ platform->MessageF(DEBUG_MESSAGE, "Received opcode %08x length %u data %s\n", opcode, length, data);
+ }
+ inBuffer.Clear();
+ break;
+ }
+}
+
+// Called by the webserver module to get an incoming request
+const char *Network::GetRequest(uint32_t& ip, size_t& length, uint32_t& fragment) const
+{
+ if (state == processing)
+ {
+ uint32_t opcode = inBuffer.GetOpcode();
+ if ((opcode & 0xFF0000FF) == (trTypeRequest | ttRr))
+ {
+ const void *data = inBuffer.GetData(length);
+ if (length > 0)
+ {
+ ip = inBuffer.GetIp();
+ fragment = inBuffer.GetFragment();
+ if ((fragment & 0x7FFFFFFF) == 0)
+ {
+ length += 1; // allow client to read the null at the end too
+ }
+// platform->MessageF(HOST_MESSAGE, "Req: %s\n", (const char*)data);
+ return (const char*)data;
+ }
+ else
+ {
+ platform->Message(DEBUG_MESSAGE, "Bad request\n");
+ inBuffer.Clear(); // bad request
+ }
+ }
+ }
+ return nullptr;
+}
+
+// Send a reply from an OutputBuffer chain. Release the chain when we have finished with it.
+void Network::SendReply(uint32_t ip, unsigned int code, OutputBuffer *body)
+{
+ if (responseBody != nullptr || responseText != nullptr || responseFile != nullptr)
+ {
+ platform->Message(HOST_MESSAGE, "response already being sent in SendReply(ob*)\n");
+ }
+ else
+ {
+ responseIp = ip;
+ responseCode = code;
+ responseBody = body;
+
+#if 0
+ //debug
+ {
+ char buf[101];
+ size_t len = min<size_t>(ARRAY_UPB(buf), responseBody->DataLength());
+ strncpy(buf, responseBody->Data(), len);
+ buf[len] = 0;
+ platform->MessageF(HOST_MESSAGE, "%s %u %s\n", (responseCode & rcJson) ? "JSON reply" : "Reply", responseCode & rcNumber, buf);
+ }
+#endif
+ // Say we have taken the request
+ if (state == processing)
+ {
+ uint32_t opcode = inBuffer.GetOpcode();
+ if ((opcode & 0xFF0000FF) == (trTypeRequest | ttRr))
+ {
+ inBuffer.Clear();
+ }
+ }
+ }
+}
+
+// Send a reply from a null-terminated string
+void Network::SendReply(uint32_t ip, unsigned int code, const char *text)
+{
+ if (responseBody != nullptr || responseText != nullptr || responseFile != nullptr)
+ {
+ platform->Message(HOST_MESSAGE, "response already being sent in SendReply(cc*)\n");
+ }
+ else
+ {
+ responseIp = ip;
+ responseCode = code;
+ responseText = text;
+
+ //debug
+ //platform->MessageF(HOST_MESSAGE, "%s %u %s\n", (responseCode & rcJson) ? "JSON reply" : "Reply", responseCode & rcNumber, text);
+
+ // Say we have taken the request
+ if (state == processing)
+ {
+ uint32_t opcode = inBuffer.GetOpcode();
+ if ((opcode & 0xFF0000FF) == (trTypeRequest | ttRr))
+ {
+ inBuffer.Clear();
+ }
+ }
+ }
+}
+
+// Send a file as the reply. Close the file at the end.
+void Network::SendReply(uint32_t ip, unsigned int code, FileStore *file)
+{
+ if (responseBody != nullptr || responseText != nullptr || responseFile != nullptr)
+ {
+ platform->Message(HOST_MESSAGE, "response already being sent in SendReply(fs*)\n");
+ file->Close();
+ }
+ else
+ {
+ responseIp = ip;
+ responseCode = code;
+ responseFile = file;
+ responseFileBytes = file->Length();
+
+ // Say we have taken the request
+ if (state == processing)
+ {
+ uint32_t opcode = inBuffer.GetOpcode();
+ if ((opcode & 0xFF0000FF) == (trTypeRequest | ttRr))
+ {
+ inBuffer.Clear();
+ }
+ }
+ }
+}
+
+// This is called when we have read a message fragment and there is no reply to send
+void Network::DiscardMessage()
+{
+ if (responseBody != nullptr || responseText != nullptr || responseFile != nullptr)
+ {
+ platform->Message(HOST_MESSAGE, "response being sent in DiscardMessage\n");
+ }
+
+ //debug
+ //platform->MessageF(HOST_MESSAGE, "%s %u %s\n", (responseCode & rcJson) ? "JSON reply" : "Reply", responseCode & rcNumber, text);
+
+ // Say we have taken the request
+ if (state == processing)
+ {
+ uint32_t opcode = inBuffer.GetOpcode();
+ if ((opcode & 0xFF0000FF) == (trTypeRequest | ttRr))
+ {
+ inBuffer.Clear();
+ }
+
+ // For faster response, change the state to idle so we can accept another packet immediately
+ state = idle;
+ TryStartTransfer();
+ }
+}
+
+void Network::Diagnostics(MessageType mtype)
+{
+ platform->Message(mtype, "Network Diagnostics:\n");
+ const char* text = (state == starting) ? "starting"
+ : (state == disabled) ? "disabled"
+ : (state == enabled) ? "enabled but not running"
+ : "running";
+ platform->MessageF(mtype, "WiFiServer is %s\n", text);
+ platform->MessageF(mtype, "SPI underruns %u, overruns %u\n", spiTxUnderruns, spiRxOverruns);
+}
+
+void Network::Enable()
+{
+ if (state == disabled)
+ {
+ state = enabled;
+ if (activated)
+ {
+ Start();
+ }
+ }
+}
+
+void Network::Disable()
+{
+ if (activated && state != disabled)
+ {
+ Stop();
+ platform->Message(GENERIC_MESSAGE, "WiFi server stopped\n");
+ }
+}
+
+bool Network::IsEnabled() const
+{
+ return state != disabled;
+}
+
+const uint8_t *Network::IPAddress() const
+{
+ return ipAddress;
+}
+
+uint16_t Network::GetHttpPort() const
+{
+ return DEFAULT_HTTP_PORT;
+}
+
+void Network::SetHttpPort(uint16_t port)
+{
+ // Not supported
+}
+
+// Set the DHCP hostname. Removes all whitespaces and converts the name to lower-case.
+void Network::SetHostname(const char *name)
+{
+ size_t i = 0;
+ while (*name && i < ARRAY_UPB(hostname))
+ {
+ char c = *name++;
+ if (c >= 'A' && c <= 'Z')
+ {
+ c += 'a' - 'A';
+ }
+
+ if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '-') || (c == '_'))
+ {
+ hostname[i++] = c;
+ }
+ }
+
+ if (i)
+ {
+ hostname[i] = 0;
+ }
+ else
+ {
+ strcpy(hostname, HOSTNAME);
+ }
+}
+
+#if USE_PDC
+static Pdc *spi_pdc;
+#endif
+
+#if USE_DMAC
+
+// Our choice of DMA channels to use
+const uint32_t CONF_SPI_DMAC_TX_CH = 1;
+const uint32_t CONF_SPI_DMAC_RX_CH = 2;
+
+// Hardware IDs of the SPI transmit and receive DMA interfaces. See atsam datasheet.
+const uint32_t DMA_HW_ID_SPI_TX = 1;
+const uint32_t DMA_HW_ID_SPI_RX = 2;
+
+#endif
+
+static inline void spi_rx_dma_enable()
+{
+#if USE_PDC
+ pdc_enable_transfer(spi_pdc, PERIPH_PTCR_RXTEN);
+#endif
+
+#if USE_DMAC
+ dmac_channel_enable(DMAC, CONF_SPI_DMAC_RX_CH);
+#endif
+}
+
+static inline void spi_tx_dma_enable()
+{
+#if USE_PDC
+ pdc_enable_transfer(spi_pdc, PERIPH_PTCR_TXTEN);
+#endif
+
+#if USE_DMAC
+ dmac_channel_enable(DMAC, CONF_SPI_DMAC_TX_CH);
+#endif
+}
+
+static inline void spi_rx_dma_disable()
+{
+#if USE_PDC
+ pdc_disable_transfer(spi_pdc, PERIPH_PTCR_RXTDIS);
+#endif
+
+#if USE_DMAC
+ dmac_channel_disable(DMAC, CONF_SPI_DMAC_RX_CH);
+#endif
+}
+
+static inline void spi_tx_dma_disable()
+{
+#if USE_PDC
+ pdc_disable_transfer(spi_pdc, PERIPH_PTCR_TXTDIS);
+#endif
+
+#if USE_DMAC
+ dmac_channel_disable(DMAC, CONF_SPI_DMAC_TX_CH);
+#endif
+}
+
+static void spi_dma_disable()
+{
+ spi_tx_dma_disable();
+ spi_rx_dma_disable();
+}
+
+static bool spi_dma_check_rx_complete()
+{
+#if USE_PDC
+ return true;
+#endif
+
+#if USE_DMAC
+ uint32_t status = DMAC->DMAC_CHSR;
+ if ( ((status & (DMAC_CHSR_ENA0 << CONF_SPI_DMAC_RX_CH)) == 0) // controller is not enabled, perhaps because it finished a full buffer transfer
+ || ((status & (DMAC_CHSR_EMPT0 << CONF_SPI_DMAC_RX_CH)) != 0) // controller is enabled, probably suspended, and the FIFO is empty
+ )
+ {
+ // Disable the channel.
+ // We also need to set the resume bit, otherwise it remains suspended when we re-enable it.
+ DMAC->DMAC_CHDR = (DMAC_CHDR_DIS0 << CONF_SPI_DMAC_RX_CH) | (DMAC_CHDR_RES0 << CONF_SPI_DMAC_RX_CH);
+ return true;
+ }
+ return false;
+#endif
+}
+
+static void spi_tx_dma_setup(const TransactionBuffer *buf, uint32_t maxTransmitLength)
+{
+#if USE_PDC
+ pdc_packet_t pdc_spi_packet;
+ pdc_spi_packet.ul_addr = reinterpret_cast<uint32_t>(buf);
+ pdc_spi_packet.ul_size = buf->PacketLength() * 4; // need length in bytes
+ pdc_tx_init(spi_pdc, &pdc_spi_packet, NULL);
+#endif
+
+#if USE_DMAC
+ DMAC->DMAC_EBCISR; // clear any pending interrupts
+
+ dmac_channel_set_source_addr(DMAC, CONF_SPI_DMAC_TX_CH, reinterpret_cast<uint32_t>(buf));
+ dmac_channel_set_destination_addr(DMAC, CONF_SPI_DMAC_TX_CH, reinterpret_cast<uint32_t>(& SPI->SPI_TDR));
+ dmac_channel_set_descriptor_addr(DMAC, CONF_SPI_DMAC_TX_CH, 0);
+ dmac_channel_set_ctrlA(DMAC, CONF_SPI_DMAC_TX_CH, maxTransmitLength | DMAC_CTRLA_SRC_WIDTH_WORD | DMAC_CTRLA_DST_WIDTH_BYTE);
+ dmac_channel_set_ctrlB(DMAC, CONF_SPI_DMAC_TX_CH,
+ DMAC_CTRLB_SRC_DSCR | DMAC_CTRLB_DST_DSCR | DMAC_CTRLB_FC_MEM2PER_DMA_FC | DMAC_CTRLB_SRC_INCR_INCREMENTING | DMAC_CTRLB_DST_INCR_FIXED);
+#endif
+}
+
+static void spi_rx_dma_setup(const TransactionBuffer *buf)
+{
+#if USE_PDC
+ pdc_packet_t pdc_spi_packet;
+ pdc_spi_packet.ul_addr = reinterpret_cast<uint32_t>(buf);
+ pdc_spi_packet.ul_size = TransactionBuffer::MaxReceiveBytes;
+ pdc_rx_init(spi_pdc, &pdc_spi_packet, NULL);
+#endif
+
+#if USE_DMAC
+ DMAC->DMAC_EBCISR; // clear any pending interrupts
+
+ dmac_channel_set_source_addr(DMAC, CONF_SPI_DMAC_RX_CH, reinterpret_cast<uint32_t>(& SPI->SPI_RDR));
+ dmac_channel_set_destination_addr(DMAC, CONF_SPI_DMAC_RX_CH, reinterpret_cast<uint32_t>(buf));
+ dmac_channel_set_descriptor_addr(DMAC, CONF_SPI_DMAC_RX_CH, 0);
+ dmac_channel_set_ctrlA(DMAC, CONF_SPI_DMAC_RX_CH, TransactionBuffer::MaxTransferBytes | DMAC_CTRLA_SRC_WIDTH_BYTE | DMAC_CTRLA_DST_WIDTH_WORD);
+ dmac_channel_set_ctrlB(DMAC, CONF_SPI_DMAC_RX_CH,
+ DMAC_CTRLB_SRC_DSCR | DMAC_CTRLB_DST_DSCR | DMAC_CTRLB_FC_PER2MEM_DMA_FC | DMAC_CTRLB_SRC_INCR_FIXED | DMAC_CTRLB_DST_INCR_INCREMENTING);
+#endif
+}
+
+/**
+ * \brief Set SPI slave transfer.
+ */
+static void spi_slave_dma_setup(bool dataToSend, bool allowReceive)
+{
+#if USE_PDC
+ pdc_disable_transfer(spi_pdc, PERIPH_PTCR_TXTDIS | PERIPH_PTCR_RXTDIS);
+
+ TransactionBuffer *outBufPointer = (dataToSend) ? &outBuffer : reinterpret_cast<TransactionBuffer*>(&dummyOutBuffer);
+ spi_tx_dma_setup(outBufPointer);
+ if (allowReceive)
+ {
+ outBufPointer->SetDataTaken();
+ spi_rx_dma_setup(&inBuffer);
+ pdc_enable_transfer(spi_pdc, PERIPH_PTCR_TXTEN | PERIPH_PTCR_RXTEN);
+ }
+ else
+ {
+ outBufPointer->ClearDataTaken();
+ pdc_enable_transfer(spi_pdc, PERIPH_PTCR_TXTEN);
+ }
+#endif
+
+#if USE_DMAC
+ spi_dma_disable();
+
+ TransactionBuffer *outBufPointer = (dataToSend) ? &outBuffer : reinterpret_cast<TransactionBuffer*>(&dummyOutBuffer);
+ if (allowReceive)
+ {
+ spi_rx_dma_setup(&inBuffer);
+ spi_rx_dma_enable();
+ outBufPointer->SetDataTaken();
+ }
+ else
+ {
+ outBufPointer->ClearDataTaken();
+ }
+
+ spi_tx_dma_setup(outBufPointer, (dataToSend) ? TransactionBuffer::MaxTransferBytes : 4 * TransactionBuffer::headerDwords);
+ spi_tx_dma_enable();
+#endif
+}
+
+// Set up the SPI system
+void Network::SetupSpi()
+{
+#if USE_PDC
+ spi_pdc = spi_get_pdc_base(SPI);
+ // The PDCs are masters 2 and 3 and the SRAM is slave 0. Give the PDCs the highest priority.
+ matrix_set_master_burst_type(0, MATRIX_ULBT_8_BEAT_BURST);
+ matrix_set_slave_default_master_type(0, MATRIX_DEFMSTR_LAST_DEFAULT_MASTER);
+ matrix_set_slave_priority(0, (3 << MATRIX_PRAS0_M2PR_Pos) | (3 << MATRIX_PRAS0_M3PR_Pos));
+ matrix_set_slave_slot_cycle(0, 8);
+#endif
+
+#if USE_DMAC
+ pmc_enable_periph_clk(ID_DMAC);
+ dmac_init(DMAC);
+ dmac_set_priority_mode(DMAC, DMAC_PRIORITY_ROUND_ROBIN);
+ dmac_enable(DMAC);
+ // The DMAC is master 4 and the SRAM is slave 0. Give the DMAC the highest priority.
+ matrix_set_slave_default_master_type(0, MATRIX_DEFMSTR_LAST_DEFAULT_MASTER);
+ matrix_set_slave_priority(0, (3 << MATRIX_PRAS0_M4PR_Pos));
+ // Set the slave slot cycle limit.
+ // If we leave it at the default value of 511 clock cycles, we get transmit underruns due to the HSMCI using the bus for too long.
+ // A value of 8 seems to work. I haven't tried other values yet.
+ matrix_set_slave_slot_cycle(0, 8);
+#endif
+
+ pmc_enable_periph_clk(ID_SPI);
+ spi_dma_disable();
+ spi_reset(SPI); // this clears the transmit and receive registers and puts the SPI into slave mode
+
+ // Set up the SPI pins
+ ConfigurePin(g_APinDescription[APIN_SPI_SCK]);
+ ConfigurePin(g_APinDescription[APIN_SPI_MOSI]);
+ ConfigurePin(g_APinDescription[APIN_SPI_MISO]);
+ ConfigurePin(g_APinDescription[APIN_SPI_SS0]);
+
+#if USE_DMAC
+ // Configure DMA RX channel
+ dmac_channel_set_configuration(DMAC, CONF_SPI_DMAC_RX_CH,
+ DMAC_CFG_SRC_PER(DMA_HW_ID_SPI_RX) | DMAC_CFG_SRC_H2SEL | DMAC_CFG_SOD | DMAC_CFG_FIFOCFG_ASAP_CFG);
+
+ // Configure DMA TX channel
+ dmac_channel_set_configuration(DMAC, CONF_SPI_DMAC_TX_CH,
+ DMAC_CFG_DST_PER(DMA_HW_ID_SPI_TX) | DMAC_CFG_DST_H2SEL | DMAC_CFG_SOD | DMAC_CFG_FIFOCFG_ASAP_CFG);
+#endif
+}
+
+// Start a transfer if necessary. Not called from an ISR.
+void Network::TryStartTransfer()
+{
+ cpu_irq_disable();
+ if (state == idle)
+ {
+ if (outBuffer.IsReady())
+ {
+ PrepareForTransfer(true, true);
+ }
+ else if (digitalRead(EspTransferRequestPin) == HIGH)
+ {
+ PrepareForTransfer(false, true);
+ }
+ }
+ else if (state == sending && outBuffer.IsReady())
+ {
+ PrepareForTransfer(true, false);
+ }
+ cpu_irq_enable();
+}
+
+// This is called from the ISR when the ESP requests to send data
+void Network::EspRequestsTransfer()
+{
+ irqflags_t flags = cpu_irq_save();
+ if (state == idle)
+ {
+ PrepareForTransfer(false, true);
+ }
+ cpu_irq_restore(flags);
+}
+
+// Set up the DMA controller and assert transfer ready. Must be called with interrupts disabled.
+void Network::PrepareForTransfer(bool dataToSend, bool allowReceive)
+//pre(state == idle || state == sending)
+{
+ if (allowReceive)
+ {
+ state = (dataToSend) ? sendReceivePending : receivePending;
+ }
+
+ // DMA may have transferred an extra word to the SPI transmit data register. We need to clear this.
+ // The only way I can find to do this is to issue a software reset to the SPI system.
+ // Fortunately, this leaves the SPI system in slave mode.
+ spi_reset(SPI);
+ spi_set_bits_per_transfer(SPI, 0, SPI_CSR_BITS_8_BIT);
+
+ // Set up the DMA controller
+ spi_slave_dma_setup(dataToSend, allowReceive);
+ spi_enable(SPI);
+
+ // Enable the end-of transfer interrupt
+ (void)SPI->SPI_SR; // clear any pending interrupt
+ SPI->SPI_IER = SPI_IER_NSSR; // enable the NSS rising interrupt
+
+ // Tell the ESP that we are ready to accept data
+ digitalWrite(SamTfrReadyPin, HIGH);
+}
+
+// SPI interrupt handler, called when NSS goes high
+void SPI_Handler()
+{
+ reprap.GetNetwork()->SpiInterrupt();
+}
+
+void Network::SpiInterrupt()
+{
+ uint32_t status = SPI->SPI_SR; // read status and clear interrupt
+ SPI->SPI_IDR = SPI_IER_NSSR; // disable the interrupt
+ if ((status & SPI_SR_NSSR) != 0)
+ {
+ if (state == sendReceivePending || state == receivePending)
+ {
+#if USE_PDC
+ pdc_disable_transfer(spi_pdc, PERIPH_PTCR_TXTDIS | PERIPH_PTCR_RXTDIS);
+#endif
+
+#if USE_DMAC
+ spi_tx_dma_disable();
+ dmac_channel_suspend(DMAC, CONF_SPI_DMAC_RX_CH); // suspend the receive channel, don't disable it because the FIFO needs to empty first
+#endif
+ spi_disable(SPI);
+ digitalWrite(SamTfrReadyPin, LOW);
+ if (state == sendReceivePending)
+ {
+ outBuffer.Clear(); // don't send the data again
+ }
+ if ((status & SPI_SR_OVRES) != 0)
+ {
+ ++spiRxOverruns;
+ }
+ if (state == sendReceivePending && (status & SPI_SR_UNDES) != 0)
+ {
+ ++spiTxUnderruns;
+ }
+ state = transferDone;
+ }
+ else if (state == sending)
+ {
+ spi_tx_dma_disable();
+ spi_disable(SPI);
+ digitalWrite(SamTfrReadyPin, LOW);
+ outBuffer.Clear(); // don't send the data again
+ if ((status & SPI_SR_UNDES) != 0)
+ {
+ ++spiTxUnderruns;
+ }
+ }
+ }
+}
+
+// Reset the ESP8266 and leave held in reset
+void Network::ResetWiFi()
+{
+ pinMode(EspResetPin, OUTPUT);
+ digitalWrite(EspResetPin, LOW);
+}
+
+// Reset the ESP8266 to take commands from the UART. The caller must wait for the reset to complete after calling this.
+// ESP8266 boot modes:
+// GPIO0 GPIO2 GPIO15
+// 0 1 0 Firmware download from UART
+// 1 1 0 Normal boot from flash memory
+// 0 0 1 SD card boot (not used in on Duet)
+void Network::ResetWiFiForUpload()
+{
+ // Make sure the ESP8266 is in the reset state
+ pinMode(EspResetPin, OUTPUT);
+ digitalWrite(EspResetPin, LOW);
+
+ // Take the ESP8266 out of power down
+ pinMode(EspEnablePin, OUTPUT);
+ digitalWrite(EspEnablePin, HIGH);
+
+ // Set up our transfer request pin (GPIO4) as an output and set it low
+ pinMode(SamTfrReadyPin, OUTPUT);
+ digitalWrite(SamTfrReadyPin, LOW);
+
+ // Set up our data ready pin (ESP GPIO0) as an output and set it low ready to boot the ESP from UART
+ pinMode(EspTransferRequestPin, OUTPUT);
+ digitalWrite(EspTransferRequestPin, LOW);
+
+ // GPIO2 also needs to be high to boot up. It's connected to MISO on the SAM, so set the pullup resistor on that pin
+ pinMode(APIN_SPI_MISO, INPUT_PULLUP);
+
+ // Set our CS input (ESP GPIO15) low ready for booting the ESP. This also clears the transfer ready latch.
+ pinMode(SamCsPin, OUTPUT);
+ digitalWrite(SamCsPin, LOW);
+
+ // Make sure it has time to reset - no idea how long it needs, but 50ms should be plenty
+ delay(50);
+
+ // Release the reset on the ESP8266
+ digitalWrite(EspResetPin, HIGH);
+}
+
+// Reset the ESP8266 to take commands from an external input. The caller must wait for the reset to complete after calling this.
+void Network::ResetWiFiForExternalUpload()
+{
+ ResetWiFiForUpload();
+
+ // Set our TxD pin low to make things easier for the FTDI chip to drive the ESP RxD input
+ pinMode(APIN_UART1_TXD, OUTPUT);
+ digitalWrite(APIN_UART1_TXD, LOW);
+}
+
+// End
diff --git a/src/DuetNG/Network.h b/src/DuetNG/Network.h
new file mode 100644
index 00000000..2555b830
--- /dev/null
+++ b/src/DuetNG/Network.h
@@ -0,0 +1,121 @@
+/****************************************************************************************************
+
+RepRapFirmware - Network: RepRapPro Ormerod with Duet controller
+
+Separated out from Platform.h by dc42 and extended by zpl
+
+****************************************************************************************************/
+
+#ifndef NETWORK_H
+#define NETWORK_H
+
+#include <cctype>
+#include <cstring>
+#include <cstdlib>
+#include <climits>
+
+#include "MessageType.h"
+
+// Return code definitions
+const uint32_t rcNumber = 0x0000FFFF;
+const uint32_t rcJson = 0x00010000;
+const uint32_t rcKeepOpen = 0x00020000;
+
+static const uint8_t IP_ADDRESS[4] = { 192, 168, 1, 10 }; // Need some sort of default...
+static const uint8_t NET_MASK[4] = { 255, 255, 255, 0 };
+static const uint8_t GATE_WAY[4] = { 192, 168, 1, 1 };
+static const uint16_t DEFAULT_HTTP_PORT = 80;
+
+class TransactionBuffer;
+class WifiFirmwareUploader;
+
+// The main network class that drives the network.
+class Network
+{
+ enum TransferState
+ {
+ disabled, // WiFi not active
+ enabled, // WiFi enabled but not started yet
+ starting, // starting up (waiting for WiFi to initialise)
+ idle, // nothing happening
+ receivePending, // we have asserted TransferReady and await completion of a receive-only transaction
+ sendReceivePending, // we have asserted TransferReady and await completion of a transmit/receive
+ transferDone, // transfer completed but receive DMA fifo may not have been flushed yet
+ processing, // a transaction has been completed but we haven't released the input buffer yet
+ sending // a transaction has been completed and we are sending the response
+ };
+
+public:
+ const uint8_t *IPAddress() const;
+ void SetIPAddress(const uint8_t ipAddress[], const uint8_t netmask[], const uint8_t gateway[]);
+
+ Network(Platform* p);
+ void Init();
+ void Activate();
+ void Exit();
+ void Spin();
+ void SpiInterrupt();
+ void Diagnostics(MessageType mtype);
+ void Start();
+ void Stop();
+
+ bool InLwip() const { return false; }
+
+ void Enable();
+ void Disable();
+ bool IsEnabled() const;
+
+ void SetHttpPort(uint16_t port);
+ uint16_t GetHttpPort() const;
+
+ void SetHostname(const char *name);
+ void EspRequestsTransfer();
+
+#if 0
+ void AcquireBus();
+ void ReleaseBus();
+#endif
+
+ const char *GetRequest(uint32_t& ip, size_t& length, uint32_t& fragment) const;
+ void SendReply(uint32_t ip, unsigned int code, OutputBuffer *body);
+ void SendReply(uint32_t ip, unsigned int code, const char *text);
+ void SendReply(uint32_t ip, unsigned int code, FileStore *file);
+ void DiscardMessage();
+
+ WifiFirmwareUploader *GetWifiUploader() { return uploader; }
+
+ static void ResetWiFi();
+ static void ResetWiFiForUpload();
+ static void ResetWiFiForExternalUpload();
+
+private:
+ void SetupSpi();
+ void PrepareForTransfer(bool dataToSend, bool allowReceive);
+ void ProcessIncomingData(TransactionBuffer &buf);
+ void ClearIpAddress();
+ void TryStartTransfer();
+ void DebugPrintResponse();
+
+ Platform *platform;
+ WifiFirmwareUploader *uploader;
+
+ uint32_t responseIp;
+ uint32_t responseCode;
+ uint32_t responseFragment;
+ OutputBuffer *responseBody;
+ const char* responseText;
+ FileStore *responseFile;
+ uint32_t responseFileBytes;
+
+ uint32_t spiTxUnderruns;
+ uint32_t spiRxOverruns;
+
+ float longWait;
+ TransferState state;
+ bool activated;
+ bool connectedToAp;
+ uint8_t ipAddress[4];
+ char hostname[16]; // Limit DHCP hostname to 15 characters + terminating 0
+};
+
+#endif
diff --git a/src/DuetNG/Pins_DuetNG.h b/src/DuetNG/Pins_DuetNG.h
new file mode 100644
index 00000000..1a80ea9d
--- /dev/null
+++ b/src/DuetNG/Pins_DuetNG.h
@@ -0,0 +1,186 @@
+#ifndef PINS_DUETNG_H__
+#define PINS_DUETNG_H__
+
+#define NAME "RepRapFirmware for Duet WiFi"
+
+const size_t NumFirmwareUpdateModules = 4; // 3 modules, plus one for manual upload to WiFi module
+#define IAP_UPDATE_FILE "iap4e.bin"
+#define IAP_FIRMWARE_FILE "DuetWiFiFirmware.bin"
+#define WIFI_FIRMWARE_FILE "DuetWiFiServer.bin"
+#define WIFI_WEB_FILE "DuetWebControl.bin"
+
+// Default board type
+#ifdef PROTOTYPE_1
+#define DEFAULT_BOARD_TYPE BoardType::DuetNG_06
+#else
+#define DEFAULT_BOARD_TYPE BoardType::DuetNG_10
+#endif
+
+#define SUPPORT_ETHERNET 0 // set nonzero to support embedded web interface over Ethernet
+#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
+
+const size_t DRIVES = 9; // The number of drives in the machine, including X, Y, and Z plus extruder drives
+#define DRIVES_(a,b,c,d,e,f,g,h,i) { a,b,c,d,e,f,g,h,i }
+
+// If enabled, the following control the use of the optional ExternalDrivers module
+#define EXTERNAL_DRIVERS (9)
+#define FIRST_EXTERNAL_DRIVE (0)
+
+const int8_t HEATERS = 7; // The number of heaters in the machine; 0 is the heated bed even if there isn't one
+#define HEATERS_(a,b,c,d,e,f,g) { a,b,c,d,e,f,g }
+
+const size_t AXES = 3; // The number of movement axes in the machine, usually just X, Y and Z. <= DRIVES
+const size_t NUM_SERIAL_CHANNELS = 2; // The number of serial IO channels (USB and one auxiliary UART)
+#define SERIAL_MAIN_DEVICE SerialUSB
+#define SERIAL_AUX_DEVICE Serial
+
+// 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
+
+const Pin ENABLE_PINS[DRIVES] = { 78, 41, 42, 49, 57, 87, 88, 89, 90 };
+const bool ENABLE_VALUES[DRIVES] = { false, false, false, false, false, false, false, false, false }; // What to send to enable a drive
+const Pin STEP_PINS[DRIVES] = { 70, 71, 72, 69, 68, 66, 65, 64, 67 };
+const Pin DIRECTION_PINS[DRIVES] = { 75, 76, 77, 01, 73, 92, 86, 80, 81 };
+const bool DIRECTIONS[DRIVES] = { BACKWARDS, FORWARDS, FORWARDS, FORWARDS, FORWARDS, FORWARDS, FORWARDS, FORWARDS, FORWARDS }; // What each axis needs to make it go forwards - defaults
+
+// Endstops
+// RepRapFirmware only has 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.
+const Pin END_STOP_PINS[DRIVES] = { 46, 02, 93, 74, 48, 96, 97, 98, 99 };
+
+// Indices for motor current digipots (if any): first 4 are for digipot 1 (on duet), second 4 for digipot 2 (on expansion board)
+#ifdef PROTOTYPE_1
+const uint8_t POT_WIPES[8] = { 1, 3, 2, 0, 1, 3, 2, 0 };
+const float SENSE_RESISTOR = 0.1; // Stepper motor current sense resistor
+const float MAX_STEPPER_DIGIPOT_VOLTAGE = (3.3 * 2.5 / (2.7 + 2.5)); // Stepper motor current reference voltage
+const float STEPPER_DAC_VOLTAGE_RANGE = 2.02; // Stepper motor current reference voltage for E1 if using a DAC
+const float STEPPER_DAC_VOLTAGE_OFFSET = -0.11; // Stepper motor current offset voltage for E1 if using a DAC
+#endif
+
+// HEATERS
+
+const bool HEAT_ON = false; // false for inverted heater (e.g. Duet v0.6), true for not (e.g. Duet v0.4)
+
+const Pin TEMP_SENSE_PINS[HEATERS] = { 45, 47, 44, 61, 62, 63, 59 }; // Thermistor pin numbers
+
+#ifdef PROTOTYPE_1
+const Pin HEAT_ON_PINS[HEATERS] = { 19, 20, 16, 15, 37, 40, 43 }; // Heater pin numbers
+#else
+const Pin HEAT_ON_PINS[HEATERS] = { 19, 20, 16, 35, 37, 40, 43 }; // Heater pin numbers
+#endif
+
+// Default thermistor parameters
+// Bed thermistor: now assuming 100K
+// Hot end thermistor: http://www.digikey.co.uk/product-search/en?x=20&y=11&KeyWords=480-3137-ND
+const float BED_R25 = 100000.0;
+const float BED_BETA = 3988.0;
+const float EXT_R25 = 100000.0;
+const float EXT_BETA = 4388.0;
+
+// Thermistor series resistor value in Ohms
+const float THERMISTOR_SERIES_RS = 4700.0;
+
+// Number of SPI temperature sensors to support
+
+#if SUPPORT_ROLAND
+
+// chrishamm's pin assignments
+const size_t MaxSpiTempSensors = 2;
+
+// Digital pins the 31855s have their select lines tied to
+const Pin SpiTempSensorCsPins[MaxSpiTempSensors] = { 56, 27 };
+
+#else
+
+const size_t MaxSpiTempSensors = 4;
+
+// Digital pins the 31855s have their select lines tied to
+# ifdef PROTOTYPE_1
+const Pin SpiTempSensorCsPins[MaxSpiTempSensors] = { 24, 25, 50, 51 }; // SPI1_CS0, SPI1_CS1, CS2, CS3
+# else
+const Pin SpiTempSensorCsPins[MaxSpiTempSensors] = { 28, 50, 51, 52 }; // SPI0_CS1, SPI0_CS2, CS3, CS4
+# endif
+
+#endif
+
+// Arduino Due pin number that controls the ATX power on/off
+const Pin ATX_POWER_PIN = 79; // Arduino Due pin number that controls the ATX power on/off
+
+// Analogue pin numbers
+const Pin Z_PROBE_PIN = 33; // Z probe analog input
+
+// Digital pin number to turn the IR LED on (high) or off (low)
+const Pin Z_PROBE_MOD_PIN = 34; // Digital pin number to turn the IR LED on (high) or off (low) on Duet v0.6 and v1.0 (PB21)
+
+// COOLING FANS
+
+const size_t NUM_FANS = 3;
+const Pin COOLING_FAN_PINS[NUM_FANS] = { 55, 58, 00 };
+const Pin COOLING_FAN_RPM_PIN = 32;
+
+#if SUPPORT_INKJET
+// Inkjet control pins
+const Pin INKJET_SERIAL_OUT = xx; // Serial bitpattern into the shift register
+const Pin INKJET_SHIFT_CLOCK = xx; // Shift the register
+const Pin INKJET_STORAGE_CLOCK = xx; // Put the pattern in the output register
+const Pin INKJET_OUTPUT_ENABLE = xx; // Make the output visible
+const Pin INKJET_CLEAR = xx; // Clear the register to 0
+
+#endif
+
+#if SUPPORT_ROLAND
+// Roland mill
+const Pin ROLAND_CTS_PIN = xx; // Expansion pin 11, PA12_TXD1
+const Pin ROLAND_RTS_PIN = xx; // Expansion pin 12, PA13_RXD1
+
+#endif
+
+// Definition of which pins we allow to be controlled using M42
+//
+// The allowed pins are these ones on the DueX4 expansion connector:
+//TODO document these
+
+const size_t NUM_PINS_ALLOWED = 96;
+
+#if 1
+#define PINS_ALLOWED { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; //TODO temporary!
+#else
+#define PINS_ALLOWED { \
+ /* pins 00-07 */ 0b00000000, \
+ /* pins 08-15 */ 0b00000000, \
+ /* pins 16-23 */ 0b00000110, \
+ /* pins 24-31 */ 0b10000011, \
+ /* pins 32-39 */ 0b00001001, \
+ /* pins 40-47 */ 0b00000000, \
+ /* pins 48-55 */ 0b00011000, \
+ /* pins 56-63 */ 0b00000000, \
+ /* pins 64-71 */ 0b00000000, \
+ /* pins 72-79 */ 0b00000000, \
+ /* pins 80-87 */ 0b00000000, \
+ /* pins 88-95 */ 0b00001000 \
+}
+#endif
+// SAM4E Flash locations (may be expanded in the future)
+const uint32_t IAP_FLASH_START = 0x00470000;
+const uint32_t IAP_FLASH_END = 0x0047FBFF;
+
+// Duet pin numbers to control the WiFi interface
+const Pin EspResetPin = 100; // Low on this in holds the WiFi module in reset (ESP_RESET)
+const Pin EspEnablePin = 101; // High to enable the WiFi module, low to power it down (ESP_CH_PD)
+const Pin EspTransferRequestPin = 95; // Input from the WiFi module indicating that it wants to transfer data (ESP GPIO0)
+const Pin SamTfrReadyPin = 94; // Output from the SAM to the WiFi module indicating we can accept a data transfer (ESP GPIO4 via 7474)
+const Pin SamCsPin = 11; // SPI NPCS pin, input from WiFi module
+
+// Timer allocation (no network timer on DuetNG)
+// TC0 channel 0 is used for FAM2
+// TC0 channel 1 is used for the TMC clock
+// TC0 channel 2 is available for us to use
+#define STEP_TC (TC0)
+#define STEP_TC_CHAN (2)
+#define STEP_TC_IRQN TC2_IRQn
+#define STEP_TC_HANDLER TC2_Handler
+
+#endif
diff --git a/src/DuetNG/TransactionBuffer.cpp b/src/DuetNG/TransactionBuffer.cpp
new file mode 100644
index 00000000..7179d4e0
--- /dev/null
+++ b/src/DuetNG/TransactionBuffer.cpp
@@ -0,0 +1,58 @@
+/*
+ * TransactionBuffer.cpp
+ *
+ * Created on: 17 May 2016
+ * Author: David
+ */
+
+#include "Core.h"
+#include "TransactionBuffer.h"
+#include <cstring>
+
+void TransactionBuffer::Clear()
+{
+ trType = 0;
+ ip = 0;
+ seq = 0;
+ fragment = 0;
+ dataLength = 0;
+}
+
+bool TransactionBuffer::SetMessage(uint32_t tt, uint32_t p_ip, uint32_t frag)
+{
+ if (IsReady())
+ {
+ return false;
+ }
+ trType = tt;
+ ip = p_ip;
+ seq = 0;
+ fragment = frag;
+ dataLength = 0;
+ return true;
+}
+
+// Append data to the message in the output buffer, returning the length appended
+size_t TransactionBuffer::AppendData(const void* dataToAppend, uint32_t length)
+{
+ uint32_t spaceLeft = maxSpiDataLength - dataLength;
+ uint32_t bytesToCopy = min<size_t>(length, spaceLeft);
+ memcpy((char*)data + dataLength, dataToAppend, bytesToCopy);
+ dataLength += bytesToCopy;
+ return bytesToCopy;
+}
+
+// Get the address and size to write data into
+char *TransactionBuffer::GetBuffer(size_t& length)
+{
+ length = maxSpiDataLength - dataLength;
+ return reinterpret_cast<char*>(data) + dataLength;
+}
+
+// Say we have appended some data
+void TransactionBuffer::DataAppended(size_t length)
+{
+ dataLength += length;
+}
+
+// End
diff --git a/src/DuetNG/TransactionBuffer.h b/src/DuetNG/TransactionBuffer.h
new file mode 100644
index 00000000..e42d5531
--- /dev/null
+++ b/src/DuetNG/TransactionBuffer.h
@@ -0,0 +1,165 @@
+/*
+ * TransactionBuffer.h
+ *
+ * Created on: 17 May 2016
+ * Author: David
+ */
+
+#ifndef SRC_DUETNG_TRANSACTIONBUFFER_H_
+#define SRC_DUETNG_TRANSACTIONBUFFER_H_
+
+#include <cstdint>
+
+//-------------------------------------------------------------------------------------------
+// Transaction buffer class
+// ***** This must be kept in step with the corresponding class in the WiFi module code *****
+const uint32_t maxSpiDataLength = 2048;
+
+//---------------------------------------------------
+// SPI transaction type field bits
+// Byte 3 (MSB) is the packet type.
+// Byte 2 holds flags
+// Byte 1 is currently unused
+// Byte 0 is the opcode if the packet is a request or info message, or the error code if it is a response.
+
+// Packet types
+const uint32_t trTypeRequest = 0x3A000000; // this is a request
+const uint32_t trTypeResponse = 0xB6000000; // this is a response to a request
+const uint32_t trTypeInfo = 0x93000000; // this is an informational message that does not require a response
+
+// Flags
+const uint32_t ttDataTaken = 0x00010000; // indicates to the SAM the the ESP8266 has read its data, or vice verse
+
+// Opcodes for requests from web sever to Duet
+const uint32_t ttRr = 0x01; // any request starting with "rr_"
+
+// Opcodes for info messages from web server to Duet
+const uint32_t ttNetworkInfo = 0x70; // pass network info to Duet, e.g. when first connected
+
+// Opcodes for requests and info from Duet to web server
+const uint32_t ttNetworkConfig = 0x80; // set network configuration (SSID, password etc.)
+const uint32_t ttNetworkEnable = 0x81; // enable WiFi
+const uint32_t ttGetNetworkInfo = 0x83; // get IP address etc.
+
+// Opcodes for info messages from Duet to server
+const uint32_t ttMachineConfigChanged = 0x82; // notify server that the machine configuration has changed significantly
+
+// Last fragment bit
+const uint32_t lastFragment = 0x80000000;
+
+class TransactionBuffer
+{
+ uint32_t trType; // type of transaction, and flags
+ uint32_t seq; // sequence number of the request
+ uint32_t ip; // requesting IP address
+ uint32_t fragment; // fragment number of this packet, top bit set if last fragment
+ uint32_t dataLength; // number of bytes of data following the header
+ uint32_t data[maxSpiDataLength/4]; // the actual data, if needed
+ uint32_t dummy; // extra so that we can append a null and to allow overrun to be detected
+
+public:
+ static const uint32_t headerDwords = 5;
+
+ static const uint32_t MaxTransferBytes = maxSpiDataLength + (4 * headerDwords);
+
+ // Initialise
+ void Init();
+
+ // Mark this buffer empty
+ void Clear();
+
+ // When we send this packet, tell the ESP that we have taken its data
+ void SetDataTaken() { trType |= ttDataTaken; }
+
+ // Clear the DataTaken flag
+ void ClearDataTaken() { trType &= ~ttDataTaken; }
+
+ // Say whether data was taken
+ bool DataWasTaken() const { return (trType & ttDataTaken) != 0; }
+
+ // Return true if this buffer contains data
+ bool IsReady() const
+ {
+ return trType != 0;
+ }
+
+ bool IsValid() const
+ {
+ return dataLength <= maxSpiDataLength;
+ }
+
+ bool IsEmpty() const
+ {
+ return trType == 0;
+ }
+
+ uint32_t GetOpcode() const
+ {
+ return trType;
+ }
+
+ uint32_t GetSeq() const
+ {
+ return seq;
+ }
+
+ uint32_t GetIp() const
+ {
+ return ip;
+ }
+
+ uint32_t GetFragment() const
+ {
+ return fragment;
+ }
+
+ uint32_t GetLength() const
+ {
+ return dataLength;
+ }
+
+ const void* GetData(size_t& length) const
+ {
+ length = dataLength;
+ return data;
+ }
+
+ void AppendNull()
+ {
+ if (IsReady())
+ {
+ *((char *)data + dataLength) = 0;
+ }
+ }
+
+ // Get SPI packet length in dwords
+ uint32_t PacketLength() const
+ {
+ return (IsReady()) ? (dataLength + 3)/4 + headerDwords : headerDwords;
+ }
+
+ // Set up a message in this buffer
+ bool SetMessage(uint32_t tt, uint32_t p_ip, uint32_t frag);
+
+ // Append data to the message in the output buffer, returning the length appended
+ size_t AppendData(const void* dataToAppend, uint32_t length);
+
+ // Append a single 32-bit integer to the output buffer
+ size_t AppendU32(uint32_t val)
+ {
+ return AppendData(&val, sizeof(val));
+ }
+
+ void SetLastFragment()
+ {
+ fragment |= lastFragment;
+ }
+
+ // Get the address and remaining size to write data into
+ char *GetBuffer(size_t& length);
+
+ // Say we have appended some data
+ void DataAppended(size_t length);
+};
+
+#endif /* SRC_DUETNG_TRANSACTIONBUFFER_H_ */
diff --git a/src/DuetNG/Webserver.cpp b/src/DuetNG/Webserver.cpp
new file mode 100644
index 00000000..27312f03
--- /dev/null
+++ b/src/DuetNG/Webserver.cpp
@@ -0,0 +1,1059 @@
+/****************************************************************************************************
+
+ RepRapFirmware - Webserver
+
+ This class serves a single-page web applications to the attached network. This page forms the user's
+ interface with the RepRap machine. This software interprests returned values from the page and uses it
+ to generate G Codes, which it sends to the RepRap. It also collects values from the RepRap like
+ temperature and uses those to construct the web page.
+
+ The page itself - reprap.htm - uses Jquery.js to perform AJAX. See:
+
+ http://jquery.com/
+
+ -----------------------------------------------------------------------------------------------------
+
+ Version 0.2
+
+ 10 May 2013
+
+ Adrian Bowyer
+ RepRap Professional Ltd
+ http://reprappro.com
+
+ Licence: GPL
+
+ -----------------------------------------------------------------------------------------------------
+
+ The supported requests are GET requests for files (for which the root is the www directory on the
+ SD card), and the following. These all start with "/rr_". Ordinary files used for the web interface
+ must not have names starting "/rr_" or they will not be found.
+
+ rr_connect?password=xxx
+ Sent by the web interface software to establish an initial connection, indicating that
+ any state variables relating to the web interface (e.g. file upload in progress) should
+ be reset. This only happens if the password could be verified.
+
+ rr_fileinfo Returns file information about the file being printed.
+
+ rr_fileinfo?name=xxx
+ Returns file information about a file on the SD card or a JSON-encapsulated response
+ with err = 1 if the passed filename was invalid.
+
+ rr_status New-style status response, in which temperatures, axis positions and extruder positions
+ are returned in separate variables. Another difference is that extruder positions are
+ returned as absolute positions instead of relative to the previous gcode. A client
+ may also request different status responses by specifying the "type" keyword, followed
+ by a custom status response type. Also see "M105 S1".
+
+ rr_files?dir=xxx&flagDirs={1/0}
+ Returns a listing of the filenames in the /gcode directory of the SD card. 'dir' is a
+ directory path relative to the root of the SD card. If the 'dir' variable is not present,
+ it defaults to the /gcode directory. If flagDirs is set to 1, all directories will be
+ prefixed by an asterisk.
+
+ rr_reply Returns the last-known G-code reply as plain text (not encapsulated as JSON).
+
+ rr_upload?name=xxx
+ Upload a specified file using a POST request. The payload of this request has to be
+ the file content. Only one file may be uploaded at once. When the upload has finished,
+ a JSON response with the variable "err" will be returned, which will be 0 if the job
+ has finished without problems, it will be set to 1 otherwise.
+
+ rr_upload_begin?name=xxx
+ Indicates that we wish to upload the specified file. xxx is the filename relative
+ to the root of the SD card. The directory component of the filename must already
+ exist. Returns variables ubuff (= max upload data we can accept in the next message)
+ and err (= 0 if the file was created successfully, nonzero if there was an error).
+
+ rr_upload_data?data=xxx
+ Provides a data block for the file upload. Returns the samwe variables as rr_upload_begin,
+ except that err is only zero if the file was successfully created and there has not been
+ a file write error yet. This response is returned before attempting to write this data block.
+
+ rr_upload_end
+ Indicates that we have finished sending upload data. The server closes the file and reports
+ the overall status in err. It may also return ubuff again.
+
+ rr_upload_cancel
+ Indicates that the user wishes to cancel the current upload. Returns err and ubuff.
+
+ rr_delete?name=xxx
+ Delete file xxx. Returns err (zero if successful).
+
+ rr_mkdir?dir=xxx
+ Create a new directory xxx. Return err (zero if successful).
+
+ rr_move?old=xxx&new=yyy
+ Rename an old file xxx to yyy. May also be used to move a file to another directory.
+
+ ****************************************************************************************************/
+
+#include "RepRapFirmware.h"
+
+const char* overflowResponse = "overflow";
+const char* badEscapeResponse = "bad escape";
+
+// Constructor and initialisation
+Webserver::Webserver(Platform* p, Network *n) : state(doingFilename), platform(p), network(n), numSessions(0), clientsServed(0)
+{
+ gcodeReadIndex = gcodeWriteIndex = 0;
+ gcodeReply = new OutputStack();
+ processingDeferredRequest = false;
+ seq = 0;
+ webserverActive = false;
+}
+
+void Webserver::Init()
+{
+ // initialise the webserver class
+ longWait = platform->Time();
+ webserverActive = true;
+ numSessions = clientsServed = 0;
+ uploadIp = 0;
+
+ // initialise all protocol handlers
+ ResetState();
+}
+
+// Deal with input/output from/to the client (if any)
+void Webserver::Spin()
+{
+ if (webserverActive)
+ {
+ uint32_t ip;
+ size_t length;
+ uint32_t fragment;
+ const char* request = network->GetRequest(ip, length, fragment);
+ if (request != nullptr)
+ {
+ if (reprap.Debug(moduleWebserver))
+ {
+ platform->MessageF(HOST_MESSAGE, "Request: %s fragment %u\n", request, fragment);
+ }
+
+ uint32_t fragNum = fragment & ~lastFragmentFlag;
+ if (fragNum == 0)
+ {
+ HttpSession *session = StartSession(ip);
+ if (session == nullptr)
+ {
+ network->SendReply(ip, 400, "Too many sessions");
+ }
+ else
+ {
+ // First fragment, so parse the request
+ ResetState();
+ const char *error = nullptr;
+ bool finished = false;
+ while (!finished && length != 0)
+ {
+ finished = CharFromClient(*request++, error);
+ --length;
+ }
+
+ if (!finished)
+ {
+ error = "Incomplete command";
+ }
+
+ if (error != nullptr)
+ {
+ network->SendReply(ip, 400, error);
+ }
+ else
+ {
+ ProcessFirstFragment(*session, clientMessage, (fragment & lastFragmentFlag) != 0);
+ }
+ }
+ }
+ else
+ {
+ HttpSession *session = FindSession(ip);
+ if (session != nullptr)
+ {
+ ProcessUploadFragment(*session, request, length, fragment);
+ }
+ else
+ {
+ // Discard the message
+ network->DiscardMessage();
+ platform->MessageF(DEBUG_MESSAGE, "session not found, fragment=%u\n", fragment);
+ }
+ }
+ }
+ }
+ CheckSessions();
+ platform->ClassReport(longWait);
+}
+
+// This is called to process a file upload request.
+void Webserver::StartUpload(HttpSession& session, const char* fileName, uint32_t fileLength)
+{
+ CancelUpload(session);
+ if (uploadIp != 0)
+ {
+ session.uploadState = uploadBusy; // upload buffer already in use
+ }
+ else
+ {
+ FileStore *file = platform->GetFileStore(FS_PREFIX, fileName, true);
+ if (file == nullptr)
+ {
+ session.uploadState = cantCreate;
+ }
+ else
+ {
+ session.fileBeingUploaded.Set(file);
+ session.postLength = fileLength;
+ session.bytesWritten = 0;
+ session.nextFragment = 1;
+ session.uploadState = uploading;
+ session.bufNumber = 0;
+ session.bufPointer = 0;
+ uploadIp = session.ip;
+ }
+ }
+}
+
+// This is called when we have received the last upload packet
+void Webserver::FinishUpload(HttpSession& session)
+{
+ bool b = session.fileBeingUploaded.Close();
+
+ if (session.uploadState == uploading)
+ {
+ if (!b)
+ {
+ session.uploadState = cantClose;
+ }
+ else if (session.bytesWritten != session.postLength)
+ {
+ session.uploadState = wrongLength;
+ }
+ }
+
+ if (session.uploadState != uploading)
+ {
+ platform->MessageF(HOST_MESSAGE, "Error: Upload finished in state %d\n", (int)session.uploadState);
+ }
+
+ network->SendReply(session.ip, 200 | rcJson, (session.uploadState == uploading) ? "{\"err\":0}" : "{\"err\":1}");
+ session.uploadState = notUploading;
+}
+
+void Webserver::CancelUpload(HttpSession& session)
+{
+ if (session.fileBeingUploaded.IsLive())
+ {
+ session.fileBeingUploaded.Close();
+ //TODO delete the file as well
+ }
+ if (uploadIp == session.ip)
+ {
+ uploadIp = 0;
+ }
+}
+
+void Webserver::ProcessUploadFragment(HttpSession& session, const char* request, size_t length, uint32_t fragment)
+{
+ if (session.uploadState == uploading)
+ {
+ //platform->MessageF(HOST_MESSAGE, "writing fragment=%u\n", fragment);
+ uint32_t frag = fragment & ~lastFragmentFlag;
+ if (frag != session.nextFragment)
+ {
+ platform->MessageF(HOST_MESSAGE, "expecting fragment %u received %u\n", session.nextFragment, frag);
+ session.uploadState = wrongFragment;
+ }
+ }
+
+ if (session.uploadState == uploading)
+ {
+ size_t bytesToCopy = uploadBufLength - session.bufPointer;
+ if (bytesToCopy > length)
+ {
+ bytesToCopy = length;
+ }
+ memcpy(reinterpret_cast<uint8_t*>(uploadBuffers[session.bufNumber]) + session.bufPointer, request, bytesToCopy);
+ session.bufPointer += bytesToCopy;
+ if (session.bufPointer == uploadBufLength)
+ {
+ // Switch to the other buffer. We know the rest of the data will fit because the buffer size is bigger than the SPI packet size.
+ session.bufNumber ^= 1;
+ if (bytesToCopy < length)
+ {
+ memcpy(uploadBuffers[session.bufNumber], request + bytesToCopy, length - bytesToCopy);
+ }
+ session.bufPointer = length - bytesToCopy;
+
+ // Free up the SPI buffer before we start the file write
+ if ((fragment & lastFragmentFlag) == 0)
+ {
+ network->DiscardMessage();
+ }
+
+ if (session.fileBeingUploaded.Write(reinterpret_cast<const char*>(uploadBuffers[session.bufNumber ^ 1]), uploadBufLength))
+ {
+ session.bytesWritten += uploadBufLength;
+ }
+ else
+ {
+ session.uploadState = cantWrite;
+ }
+ }
+ else if ((fragment & lastFragmentFlag) == 0)
+ {
+ network->DiscardMessage(); // free up the SPI buffer
+ }
+ }
+ else if ((fragment & lastFragmentFlag) == 0)
+ {
+ network->DiscardMessage(); // free up the SPI buffer
+ }
+ ++session.nextFragment;
+
+ if ((fragment & lastFragmentFlag) != 0)
+ {
+ if (session.bufPointer != 0 && session.uploadState == uploading)
+ {
+ if (session.fileBeingUploaded.Write(reinterpret_cast<const char*>(uploadBuffers[session.bufNumber]), session.bufPointer))
+ {
+ session.bytesWritten += session.bufPointer;
+ }
+ else
+ {
+ session.uploadState = cantWrite;
+ }
+
+ }
+ FinishUpload(session);
+ }
+}
+
+// Start a new session for this requester. Return nullptr if on more sessions available.
+Webserver::HttpSession *Webserver::StartSession(uint32_t ip)
+{
+ HttpSession *s = FindSession(ip);
+ if (s != nullptr)
+ {
+ // Abandon the existing session for this requester and start a new one
+ s->nextFragment = 0;
+ s->fileBeingUploaded.Close(); // TODO delete any partially-uploaded file
+ return s;
+ }
+
+ // Find an empty session
+ if (numSessions < maxHttpSessions)
+ {
+ s = &sessions[numSessions];
+ s->ip = ip;
+ s->isAuthenticated = false;
+ s->nextFragment = 0;
+ s->fileBeingUploaded.Close(); // make sure no file is open
+ s->lastQueryTime = platform->Time();
+ ++numSessions;
+ return s;
+ }
+ return nullptr;
+}
+
+// Find an existing session for this requester, returning nullptr if there isn't one
+Webserver::HttpSession *Webserver::FindSession(uint32_t ip)
+{
+ for (size_t i = 0; i < numSessions; ++i)
+ {
+ HttpSession *s = &sessions[i];
+ if (s->ip == ip)
+ {
+ s->lastQueryTime = platform->Time();
+ return s;
+ }
+ }
+
+ return nullptr;
+}
+
+// Delete a session
+void Webserver::DeleteSession(uint32_t ip)
+{
+ for (size_t i = 0; i < numSessions; ++i)
+ {
+ HttpSession *s = &sessions[i];
+ if (s->ip == ip)
+ {
+ s->fileBeingUploaded.Close(); // TODO delete it as well
+ for (size_t j = i + 1; j < numSessions; ++j)
+ {
+ memcpy(&sessions[j - 1], &sessions[j], sizeof(HttpSession));
+ }
+ --numSessions;
+ break;
+ }
+ }
+ if (uploadIp == ip)
+ {
+ uploadIp = 0; // free the upload buffer
+ }
+}
+
+void Webserver::Exit()
+{
+ platform->Message(GENERIC_MESSAGE, "Webserver class exited.\n");
+ webserverActive = false;
+}
+
+void Webserver::ResetState()
+{
+ clientPointer = 0;
+ state = doingFilename;
+ numQualKeys = 0;
+ processingDeferredRequest = false;
+}
+
+void Webserver::Diagnostics(MessageType mtype)
+{
+ platform->Message(mtype, "Webserver Diagnostics:\n");
+ platform->MessageF(mtype, "HTTP sessions: %d of %d\n", numSessions, maxHttpSessions);
+}
+
+bool Webserver::GCodeAvailable(const WebSource source) const
+{
+ switch (source)
+ {
+ case WebSource::HTTP:
+ return gcodeReadIndex != gcodeWriteIndex;
+
+ case WebSource::Telnet:
+ // Telnet not supported
+ return false;
+ }
+
+ return false;
+}
+
+char Webserver::ReadGCode(const WebSource source)
+{
+ switch (source)
+ {
+ case WebSource::HTTP:
+ if (gcodeReadIndex != gcodeWriteIndex)
+ {
+ char c = gcodeBuffer[gcodeReadIndex];
+ gcodeReadIndex = (gcodeReadIndex + 1u) % gcodeBufferLength;
+ return c;
+ }
+ break;
+
+ case WebSource::Telnet:
+ // Telnet not supported
+ return 0;
+ }
+
+ return 0;
+}
+
+void Webserver::HandleGCodeReply(const WebSource source, OutputBuffer *reply)
+{
+ switch (source)
+ {
+ case WebSource::HTTP:
+#if 0
+ OutputBuffer::ReleaseAll(reply);
+#else
+ if (reply != nullptr)
+ {
+ if (numSessions > 0)
+ {
+ // FIXME: This might cause G-code responses to be sent twice to fast HTTP clients, but
+ // I (chrishamm) cannot think of a nicer way to deal with slow clients at the moment...
+ gcodeReply->Push(reply);
+ clientsServed = 0;
+ seq++;
+ }
+ else
+ {
+ // Don't use buffers that may never get released...
+ OutputBuffer::ReleaseAll(reply);
+ }
+ }
+#endif
+ break;
+
+ case WebSource::Telnet:
+ // Telnet not supported
+ default:
+ OutputBuffer::ReleaseAll(reply);
+ break;
+ }
+}
+
+void Webserver::HandleGCodeReply(const WebSource source, const char *reply)
+{
+ switch (source)
+ {
+ case WebSource::HTTP:
+#if 0
+#else
+ if (numSessions > 0)
+ {
+ OutputBuffer *buffer = gcodeReply->GetLastItem();
+ if (buffer == nullptr || buffer->IsReferenced())
+ {
+ if (!OutputBuffer::Allocate(buffer))
+ {
+ // No more space available, stop here
+ return;
+ }
+ gcodeReply->Push(buffer);
+ }
+
+ buffer->cat(reply);
+ seq++;
+ }
+#endif
+ break;
+
+ case WebSource::Telnet:
+ default:
+ break;
+ }
+}
+
+//----------------------------------------------------------------------------------------------------
+
+// Process the first fragment of input from the client.
+// Return true if the session should be kept open.
+bool Webserver::ProcessFirstFragment(HttpSession& session, const char* command, bool isOnlyFragment)
+{
+ // Get the first two key/value pairs
+ const char* key1 = (numQualKeys >= 1) ? qualifiers[0].key : "";
+ const char* value1 = (numQualKeys >= 1) ? qualifiers[0].value : "";
+ const char* key2 = (numQualKeys >= 1) ? qualifiers[1].key : "";
+ const char* value2 = (numQualKeys >= 1) ? qualifiers[1].value : "";
+
+ // Process connect messages first
+ if (StringEquals(command, "connect") && StringEquals(key1, "password"))
+ {
+ const char *response;
+ if (session.isAuthenticated)
+ {
+ // This IP is already authenticated, no need to check the password again
+ response = "{\"err\":0}";
+ }
+ else if (reprap.CheckPassword(value1))
+ {
+ session.isAuthenticated = true;
+ response = "{\"err\":0}";
+ }
+ else
+ {
+ // Wrong password
+ response = "{\"err\":1}";
+ }
+ network->SendReply(session.ip, 200 | rcJson, response);
+ return false;
+ }
+
+ if (StringEquals(command, "disconnect"))
+ {
+ network->SendReply(session.ip, 200 | rcJson, "{\"err\":0}");
+ DeleteSession(session.ip);
+ return false;
+ }
+
+ // Try to authorise the user automatically to retain compatibility with the old web interface
+ if (!session.isAuthenticated && reprap.NoPasswordSet())
+ {
+ session.isAuthenticated = true;
+ }
+
+ // If the client is still not authenticated, stop here
+ if (!session.isAuthenticated)
+ {
+ network->SendReply(session.ip, 500, "Not authorized");
+ return false;
+ }
+
+ if (StringEquals(command, "reply"))
+ {
+ SendGCodeReply(session);
+ return false;
+ }
+
+ // rr_configfile sends the config as plain text well
+ if (StringEquals(command, "configfile"))
+ {
+ SendConfigFile(session);
+ return false;
+ }
+
+ if (StringEquals(command, "upload"))
+ {
+ if (StringEquals(key1, "name") && StringEquals(key2, "length"))
+ {
+ // Deal with file upload request
+ uint32_t fileLength = atol(value2);
+ StartUpload(session, value1, fileLength);
+ if (session.uploadState == uploading)
+ {
+ if (isOnlyFragment)
+ {
+ FinishUpload(session);
+ return false;
+ }
+ else
+ {
+ network->DiscardMessage(); // no reply needed yet
+ return true; // expecting more data
+ }
+ }
+ }
+
+ network->SendReply(session.ip, 200 | rcJson, "{\"err\":1}"); // TODO return the cause of the error
+ return false;
+ }
+
+ if (StringEquals(command, "move"))
+ {
+ const char* response = "{\"err\":1}"; // assume failure
+ if (StringEquals(key1, "old") && StringEquals(key2, "new"))
+ {
+ bool success = platform->GetMassStorage()->Rename(value1, value2);
+ if (success)
+ {
+ response = "{\"err\":0}";
+ }
+ }
+ network->SendReply(session.ip, 200 | rcJson, response);
+ return false;
+ }
+
+ if (StringEquals(command, "mkdir"))
+ {
+ const char* response = "{\"err\":1}"; // assume failure
+ if (StringEquals(key1, "dir"))
+ {
+ bool ok = (platform->GetMassStorage()->MakeDirectory(value1));
+ if (ok)
+ {
+ response = "{\"err\":0}";
+ }
+ }
+ network->SendReply(session.ip, 200 | rcJson, response);
+ return false;
+ }
+
+ if (StringEquals(command, "delete"))
+ {
+ const char* response = "{\"err\":1}"; // assume failure
+ if (StringEquals(key1, "name"))
+ {
+ bool ok = platform->GetMassStorage()->Delete("0:/", value1);
+ if (ok)
+ {
+ response = "{\"err\":0}";
+ }
+ }
+ network->SendReply(session.ip, 200 | rcJson, response);
+ return false;
+ }
+
+ // The remaining commands use an OutputBuffer for the response
+ OutputBuffer *response = nullptr;
+ if (StringEquals(command, "status"))
+ {
+ int type = 0;
+ if (StringEquals(key1, "type"))
+ {
+ // New-style JSON status responses
+ type = atoi(value1);
+ if (type < 1 || type > 3)
+ {
+ type = 1;
+ }
+
+ response = reprap.GetStatusResponse(type, ResponseSource::HTTP);
+ }
+ else
+ {
+ response = reprap.GetLegacyStatusResponse(1, 0);
+ }
+ }
+ else if (StringEquals(command, "gcode"))
+ {
+ if (StringEquals(key1, "gcode"))
+ {
+ LoadGcodeBuffer(value1);
+ if (OutputBuffer::Allocate(response))
+ {
+ response->printf("{\"buff\":%u}", GetGCodeBufferSpace());
+ }
+ }
+ else
+ {
+ network->SendReply(session.ip, 200 | rcJson, "{\"err\":1}");
+ return false;
+ }
+ }
+ else if (StringEquals(command, "files"))
+ {
+ const char* dir = (StringEquals(key1, "dir")) ? value1 : platform->GetGCodeDir();
+ bool flagDirs = false;
+ if (numQualKeys >= 2)
+ {
+ if (StringEquals(qualifiers[1].key, "flagDirs"))
+ {
+ flagDirs = StringEquals(qualifiers[1].value, "1");
+ }
+ }
+ response = reprap.GetFilesResponse(dir, flagDirs);
+ }
+ else if (StringEquals(command, "fileinfo"))
+ {
+ if (reprap.GetPrintMonitor()->GetFileInfoResponse(StringEquals(key1, "name") ? value1 : nullptr, response))
+ {
+ processingDeferredRequest = false;
+ }
+ else
+ {
+ processingDeferredRequest = true;
+ }
+ }
+ else if (StringEquals(command, "config"))
+ {
+ response = reprap.GetConfigResponse();
+ }
+ else
+ {
+ platform->MessageF(HOST_MESSAGE, "KnockOut request: %s not recognised\n", command);
+ network->SendReply(session.ip, 400, "Unknown rr_ command");
+ return false;
+ }
+
+ if (response != nullptr)
+ {
+ network->SendReply(session.ip, 200 | rcJson, response);
+ }
+ else if (!processingDeferredRequest)
+ {
+ network->SendReply(session.ip, 500, "No buffer available");
+ }
+ return processingDeferredRequest;
+}
+
+void Webserver::SendGCodeReply(HttpSession& session)
+{
+ if (gcodeReply->IsEmpty())
+ {
+ network->SendReply(session.ip, 200, "");
+ }
+ else
+ {
+ clientsServed++;
+ if (clientsServed < numSessions)
+ {
+ gcodeReply->IncreaseReferences(1);
+ network->SendReply(session.ip, 200, gcodeReply->GetFirstItem());
+ }
+ else
+ {
+ network->SendReply(session.ip, 200, gcodeReply->Pop());
+ }
+
+ if (reprap.Debug(moduleWebserver))
+ {
+ platform->MessageF(HOST_MESSAGE, "Serving client %d of %d\n", clientsServed, numSessions);
+ }
+ }
+}
+
+void Webserver::SendConfigFile(HttpSession& session)
+{
+ FileStore *configFile = platform->GetFileStore(platform->GetSysDir(), platform->GetConfigFile(), false);
+ if (configFile == nullptr)
+ {
+ network->SendReply(session.ip, 400, "File config.g not found");
+ }
+ else
+ {
+ // Send an initial message containing the response code and the file size (needed for the Content-length field)
+ network->SendReply(session.ip, 200, configFile); // tell the network layer to send the file
+ }
+}
+
+// Process a character from the client
+// Rewritten as a state machine by dc42 to increase capability and speed, and reduce RAM requirement.
+// On entry:
+// There is space for at least 1 character in clientMessage.
+// On return:
+// If we return false:
+// We want more characters. There is space for at least 1 character in clientMessage.
+// If we return true:
+// We have finished processing the message. No more characters may be read from this message.
+// Whenever this calls ProcessMessage:
+// The first line has been split up into words. Variables numCommandWords and commandWords give the number of words we found
+// and the pointers to each word. The second word is treated specially. It is assumed to be a filename followed by an optional
+// qualifier comprising key/value pairs. Both may include %xx escapes, and the qualifier may include + to mean space. We store
+// a pointer to the filename without qualifier in commandWords[1]. We store the qualifier key/value pointers in array 'qualifiers'
+// and the number of them in numQualKeys.
+// The remaining lines have been parsed as header name/value pairs. Pointers to them are stored in array 'headers' and the number
+// of them in numHeaders.
+// If one of our arrays is about to overflow, or the message is not in a format we expect, then we call RejectMessage with an
+// appropriate error code and string.
+bool Webserver::CharFromClient(char c, const char* &error)
+{
+ switch(state)
+ {
+ case doingFilename:
+ switch(c)
+ {
+ case '\0':
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\r':
+ clientMessage[clientPointer++] = 0;
+ numQualKeys = 0;
+ error = nullptr;
+ return true;
+ case '?':
+ clientMessage[clientPointer++] = 0;
+ numQualKeys = 0;
+ qualifiers[0].key = clientMessage + clientPointer;
+ state = doingQualifierKey;
+ break;
+ case '%':
+ state = doingFilenameEsc1;
+ break;
+ default:
+ clientMessage[clientPointer++] = c;
+ break;
+ }
+ break;
+
+ case doingQualifierKey:
+ switch(c)
+ {
+ case '=':
+ clientMessage[clientPointer++] = 0;
+ qualifiers[numQualKeys].value = clientMessage + clientPointer;
+ ++numQualKeys;
+ state = doingQualifierValue;
+ break;
+ case '\n': // key with no value
+ case ' ':
+ case '\t':
+ case '\r':
+ case '%': // none of our keys needs escaping, so treat an escape within a key as an error
+ case '&': // key with no value
+ error = "bad qualifier key";
+ return true;
+ default:
+ clientMessage[clientPointer++] = c;
+ break;
+ }
+ break;
+
+ case doingQualifierValue:
+ switch(c)
+ {
+ case '\0':
+ case '\n':
+ case ' ':
+ case '\t':
+ case '\r':
+ clientMessage[clientPointer++] = 0;
+ qualifiers[numQualKeys].key = clientMessage + clientPointer; // so that we can read the whole value even if it contains a null
+ error = nullptr;
+ return true;
+ case '%':
+ state = doingQualifierValueEsc1;
+ break;
+ case '&':
+ // Another variable is coming
+ clientMessage[clientPointer++] = 0;
+ qualifiers[numQualKeys].key = clientMessage + clientPointer; // so that we can read the whole value even if it contains a null
+ if (numQualKeys < maxQualKeys)
+ {
+ state = doingQualifierKey;
+ }
+ else
+ {
+ error = "too many keys in qualifier";
+ return true;
+ }
+ break;
+ case '+':
+ clientMessage[clientPointer++] = ' ';
+ break;
+ default:
+ clientMessage[clientPointer++] = c;
+ break;
+ }
+ break;
+
+ case doingFilenameEsc1:
+ case doingQualifierValueEsc1:
+ if (c >= '0' && c <= '9')
+ {
+ decodeChar = (c - '0') << 4;
+ state = (HttpState)(state + 1);
+ }
+ else if (c >= 'A' && c <= 'F')
+ {
+ decodeChar = (c - ('A' - 10)) << 4;
+ state = (HttpState)(state + 1);
+ }
+ else
+ {
+ error = badEscapeResponse;
+ return true;
+ }
+ break;
+
+ case doingFilenameEsc2:
+ case doingQualifierValueEsc2:
+ if (c >= '0' && c <= '9')
+ {
+ clientMessage[clientPointer++] = decodeChar | (c - '0');
+ state = (HttpState)(state - 2);
+ }
+ else if (c >= 'A' && c <= 'F')
+ {
+ clientMessage[clientPointer++] = decodeChar | (c - ('A' - 10));
+ state = (HttpState)(state - 2);
+ }
+ else
+ {
+ error = badEscapeResponse;
+ return true;
+ }
+ break;
+ }
+
+ if (clientPointer == ARRAY_SIZE(clientMessage))
+ {
+ error = overflowResponse;
+ return true;
+ }
+ return false;
+}
+
+void Webserver::CheckSessions()
+{
+ // Check if any HTTP session can be purged
+ const float time = platform->Time();
+ for (size_t i = 0; i < numSessions; ++i)
+ {
+ if ((time - sessions[i].lastQueryTime) > httpSessionTimeout)
+ {
+ DeleteSession(sessions[i].ip);
+ clientsServed++; // assume the disconnected client hasn't fetched the G-Code reply yet
+ }
+ }
+
+ // If we cannot send the G-Code reply to anyone, we may free up some run-time space by dumping it
+ if (numSessions == 0 || clientsServed >= numSessions)
+ {
+ while (!gcodeReply->IsEmpty())
+ {
+ OutputBuffer::ReleaseAll(gcodeReply->Pop());
+ }
+ clientsServed = 0;
+ }
+}
+
+// Process a received string of gcodes
+void Webserver::LoadGcodeBuffer(const char* gc)
+{
+ char gcodeTempBuf[GCODE_LENGTH];
+ uint16_t gtp = 0;
+ bool inComment = false;
+ for (;;)
+ {
+ char c = *gc++;
+ if (c == 0)
+ {
+ gcodeTempBuf[gtp] = 0;
+ ProcessGcode(gcodeTempBuf);
+ return;
+ }
+
+ if (c == '\n')
+ {
+ gcodeTempBuf[gtp] = 0;
+ ProcessGcode(gcodeTempBuf);
+ gtp = 0;
+ inComment = false;
+ }
+ else
+ {
+ if (c == ';')
+ {
+ inComment = true;
+ }
+
+ if (gtp == ARRAY_UPB(gcodeTempBuf))
+ {
+ // gcode is too long, we haven't room for another character and a null
+ if (c != ' ' && !inComment)
+ {
+ platform->Message(HOST_MESSAGE, "Error: GCode local buffer overflow in HTTP webserver.\n");
+ return;
+ }
+ // else we're either in a comment or the current character is a space.
+ // If we're in a comment, we'll silently truncate it.
+ // If the current character is a space, we'll wait until we see a non-comment character before reporting an error,
+ // in case the next character is end-of-line or the start of a comment.
+ }
+ else
+ {
+ gcodeTempBuf[gtp++] = c;
+ }
+ }
+ }
+}
+
+// Process a null-terminated gcode
+// We intercept one M Codes so we can deal with emergencies. That
+// way things don't get out of sync, and - as a file name can contain
+// a valid G code (!) - confusion is avoided.
+void Webserver::ProcessGcode(const char* gc)
+{
+ if (StringStartsWith(gc, "M112") && !isdigit(gc[4])) // emergency stop
+ {
+ reprap.EmergencyStop();
+ gcodeReadIndex = gcodeWriteIndex; // clear the buffer
+ reprap.GetGCodes()->Reset();
+ }
+ else
+ {
+ StoreGcodeData(gc, strlen(gc) + 1);
+ }
+}
+
+// Process a received string of gcodes
+void Webserver::StoreGcodeData(const char* data, uint16_t len)
+{
+ if (len > GetGCodeBufferSpace())
+ {
+ platform->Message(HOST_MESSAGE, "Error: GCode buffer overflow in HTTP Webserver!\n");
+ }
+ else
+ {
+ uint16_t remaining = gcodeBufferLength - gcodeWriteIndex;
+ if (len <= remaining)
+ {
+ memcpy(gcodeBuffer + gcodeWriteIndex, data, len);
+ }
+ else
+ {
+ memcpy(gcodeBuffer + gcodeWriteIndex, data, remaining);
+ memcpy(gcodeBuffer, data + remaining, len - remaining);
+ }
+ gcodeWriteIndex = (gcodeWriteIndex + len) % gcodeBufferLength;
+ }
+}
+
+// End
diff --git a/src/DuetNG/Webserver.h b/src/DuetNG/Webserver.h
new file mode 100644
index 00000000..ffef6d6f
--- /dev/null
+++ b/src/DuetNG/Webserver.h
@@ -0,0 +1,171 @@
+/****************************************************************************************************
+
+RepRapFirmware - Webserver
+
+This class serves a single-page web applications to the attached network. This page forms the user's
+interface with the RepRap machine. This software interprests returned values from the page and uses it
+to Generate G Codes, which it sends to the RepRap. It also collects values from the RepRap like
+temperature and uses those to construct the web page.
+
+The page itself - reprap.htm - uses Knockout.js and Jquery.js. See:
+
+http://knockoutjs.com/
+
+http://jquery.com/
+
+-----------------------------------------------------------------------------------------------------
+
+Version 0.2
+
+10 May 2013
+
+Adrian Bowyer
+RepRap Professional Ltd
+http://reprappro.com
+
+Licence: GPL
+
+****************************************************************************************************/
+
+#ifndef WEBSERVER_H
+#define WEBSERVER_H
+
+
+// List of protocols that can execute G-Codes
+enum class WebSource
+{
+ HTTP,
+ Telnet
+};
+
+const uint16_t gcodeBufferLength = 512; // size of our gcode ring buffer, preferably a power of 2
+const uint16_t webMessageLength = 2000; // maximum length of the web message we accept after decoding
+const size_t maxQualKeys = 5; // max number of key/value pairs in the qualifier
+const size_t maxHttpSessions = 8; // maximum number of simultaneous HTTP sessions
+const float httpSessionTimeout = 20.0; // HTTP session timeout in seconds
+
+class Webserver
+{
+public:
+
+ friend class Platform;
+
+ Webserver(Platform* p, Network *n);
+ void Init();
+ void Spin();
+ void Exit();
+ void Diagnostics(MessageType mtype);
+
+ bool GCodeAvailable(const WebSource source) const;
+ char ReadGCode(const WebSource source);
+ void HandleGCodeReply(const WebSource source, OutputBuffer *reply);
+ void HandleGCodeReply(const WebSource source, const char *reply);
+ uint32_t GetReplySeq() const { return seq; }
+ // Returns the available G-Code buffer space of the HTTP interpreter (may be dropped in a future version)
+ uint16_t GetGCodeBufferSpace(const WebSource source) const { return 0; }
+
+private:
+ static const uint32_t lastFragmentFlag = 0x80000000;
+ static const size_t uploadBufLength = 8192;
+
+ enum UploadState
+ {
+ uploading = 0,
+ wrongFragment,
+ cantCreate,
+ cantWrite,
+ wrongLength,
+ cantClose,
+ notUploading,
+ uploadBusy
+ };
+
+ // HTTP sessions
+ struct HttpSession
+ {
+ uint32_t ip;
+ uint32_t nextFragment;
+ float lastQueryTime;
+ FileData fileBeingUploaded;
+ uint32_t postLength;
+ uint32_t bytesWritten;
+ UploadState uploadState;
+ size_t bufNumber;
+ size_t bufPointer;
+ bool isAuthenticated;
+ };
+
+ void ResetState();
+ void CheckSessions();
+
+ bool CharFromClient(char c, const char* &error);
+ bool ProcessFirstFragment(HttpSession& session, const char* command, bool isOnlyFragment);
+ void ProcessUploadFragment(HttpSession& session, const char* request, size_t length, uint32_t fragment);
+ void StartUpload(HttpSession& session, const char* fileName, uint32_t fileLength);
+ void FinishUpload(HttpSession& session);
+ void CancelUpload(HttpSession& session);
+ bool GetJsonResponse(uint32_t remoteIp, const char* request, OutputBuffer *&response, const char* key, const char* value, size_t valueLength, bool& keepOpen);
+ void SendConfigFile(HttpSession& session);
+
+ // Deal with incoming G-Codes
+ char gcodeBuffer[gcodeBufferLength];
+ uint16_t gcodeReadIndex, gcodeWriteIndex; // head and tail indices into gcodeBuffer
+ uint32_t seq; // sequence number for G-Code replies
+
+ void LoadGcodeBuffer(const char* gc);
+ void ProcessGcode(const char* gc);
+ void StoreGcodeData(const char* data, uint16_t len);
+ void SendGCodeReply(HttpSession& session);
+ uint16_t GetGCodeBufferSpace() const;
+
+ HttpSession *StartSession(uint32_t ip); // start a new session for this requester
+ HttpSession *FindSession(uint32_t ip); // find an existing session for this requester
+ void DeleteSession(uint32_t ip); // delete a session
+
+ // Response from GCodes class
+ OutputStack *gcodeReply;
+
+ enum HttpState
+ {
+ doingFilename, // receiving the filename (second word in the command line)
+ doingFilenameEsc1, // received '%' in the filename (e.g. we are being asked for a filename with spaces in it)
+ doingFilenameEsc2, // received '%' and one hex digit in the filename
+ doingQualifierKey, // receiving a key name in the HTTP request
+ doingQualifierValue, // receiving a key value in the HTTP request
+ doingQualifierValueEsc1, // received '%' in the qualifier
+ doingQualifierValueEsc2, // received '%' and one hex digit in the qualifier
+ };
+ HttpState state;
+
+ struct KeyValueIndices
+ {
+ const char* key;
+ const char* value;
+ };
+
+ uint32_t uploadBuffers[2][uploadBufLength/4]; // two 8K buffers for uploading files
+ uint32_t uploadIp; // session that owns the upload buffer
+ Platform *platform;
+ Network *network;
+ float longWait;
+ bool webserverActive;
+
+ char clientMessage[webMessageLength + 3]; // holds the command, qualifier, and headers
+ size_t clientPointer; // current index into clientMessage
+ char decodeChar;
+
+ bool processingDeferredRequest; // it's no good idea to parse 128kB of text in one go...
+
+ KeyValueIndices qualifiers[maxQualKeys + 1]; // offsets into clientQualifier of the key/value pairs, the +1 is needed so that values can contain nulls
+ size_t numQualKeys; // number of qualifier keys we have found, <= maxQualKeys
+
+ HttpSession sessions[maxHttpSessions];
+ size_t numSessions, clientsServed;
+};
+
+inline uint16_t Webserver::GetGCodeBufferSpace() const
+{
+ return (gcodeReadIndex - gcodeWriteIndex - 1u) % gcodeBufferLength;
+}
+
+#endif
diff --git a/src/DuetNG/WifiFirmwareUploader.cpp b/src/DuetNG/WifiFirmwareUploader.cpp
new file mode 100644
index 00000000..8bc740d8
--- /dev/null
+++ b/src/DuetNG/WifiFirmwareUploader.cpp
@@ -0,0 +1,743 @@
+/*
+ * EspFirmwareUpload.cpp
+ *
+ * Created on: 15 Apr 2016
+ * Author: David
+ */
+
+#include "WifiFirmwareUploader.h"
+#include "Core.h"
+#include "RepRapFirmware.h"
+
+// ESP8266 command codes
+const uint8_t ESP_FLASH_BEGIN = 0x02;
+const uint8_t ESP_FLASH_DATA = 0x03;
+const uint8_t ESP_FLASH_END = 0x04;
+const uint8_t ESP_MEM_BEGIN = 0x05;
+const uint8_t ESP_MEM_END = 0x06;
+const uint8_t ESP_MEM_DATA = 0x07;
+const uint8_t ESP_SYNC = 0x08;
+const uint8_t ESP_WRITE_REG = 0x09;
+const uint8_t ESP_READ_REG = 0x0a;
+
+// MAC address storage locations
+const uint32_t ESP_OTP_MAC0 = 0x3ff00050;
+const uint32_t ESP_OTP_MAC1 = 0x3ff00054;
+const uint32_t ESP_OTP_MAC2 = 0x3ff00058;
+const uint32_t ESP_OTP_MAC3 = 0x3ff0005c;
+
+const size_t EspFlashBlockSize = 0x0400; // 1K byte blocks
+
+const uint8_t ESP_IMAGE_MAGIC = 0xe9;
+const uint8_t ESP_CHECKSUM_MAGIC = 0xef;
+
+const uint32_t ESP_ERASE_CHIP_ADDR = 0x40004984; // &SPIEraseChip
+const uint32_t ESP_SEND_PACKET_ADDR = 0x40003c80; // &send_packet
+const uint32_t ESP_SPI_READ_ADDR = 0x40004b1c; // &SPIRead
+const uint32_t ESP_UNKNOWN_ADDR = 0x40001121; // not used
+const uint32_t ESP_USER_DATA_RAM_ADDR = 0x3ffe8000; // &user data ram
+const uint32_t ESP_IRAM_ADDR = 0x40100000; // instruction RAM
+const uint32_t ESP_FLASH_ADDR = 0x40200000; // address of start of Flash
+const uint32_t ESP_FLASH_READ_STUB_BEGIN = IRAM_ADDR + 0x18;
+
+// Messages corresponding to result codes, should make sense when followed by " error"
+const char *resultMessages[] =
+{
+ "no",
+ "timeout",
+ "comm write",
+ "connect",
+ "bad reply",
+ "file read",
+ "empty file",
+ "response header",
+ "slip frame",
+ "slip state",
+ "slip data"
+};
+
+// A note on baud rates.
+// The ESP8266 supports 921600, 460800, 230400, 115200, 74880 and some lower baud rates.
+// 921600b is not reliable because even though it sometimes succeeds in connecting, we get a bad response during uploading after a few blocks.
+// Probably our UART ISR cannot receive bytes fast enough, perhaps because of the latency of the system tick ISR.
+// 460800b doesn't always manage to connect, but if it does then uploading appears to be reliable.
+// 230400b always manages to connect.
+static const uint32_t uploadBaudRates[] = { 460800, 230400, 115200, 74880 };
+
+WifiFirmwareUploader::WifiFirmwareUploader(UARTClass& port)
+ : uploadPort(port), uploadFile(nullptr), state(UploadState::idle)
+{
+}
+
+bool WifiFirmwareUploader::IsReady() const
+{
+ return state == UploadState::idle;
+}
+
+void WifiFirmwareUploader::MessageF(const char *fmt, ...)
+{
+ va_list vargs;
+ va_start(vargs, fmt);
+ reprap.GetPlatform()->MessageF(FIRMWARE_UPDATE_MESSAGE, fmt, vargs);
+ va_end(vargs);
+}
+
+void WifiFirmwareUploader::flushInput()
+{
+ while (uploadPort.available() != 0)
+ {
+ (void)uploadPort.read();
+ }
+}
+
+// Extract 1-4 bytes of a value in little-endian order from a buffer beginning at a specified offset
+uint32_t WifiFirmwareUploader::getData(unsigned byteCnt, const uint8_t *buf, int ofst)
+{
+ uint32_t val = 0;
+
+ if (buf && byteCnt)
+ {
+ unsigned int shiftCnt = 0;
+ if (byteCnt > 4)
+ byteCnt = 4;
+ do
+ {
+ val |= (uint32_t)buf[ofst++] << shiftCnt;
+ shiftCnt += 8;
+ } while (--byteCnt);
+ }
+ return(val);
+}
+
+// Put 1-4 bytes of a value in little-endian order into a buffer beginning at a specified offset.
+void WifiFirmwareUploader::putData(uint32_t val, unsigned byteCnt, uint8_t *buf, int ofst)
+{
+ if (buf && byteCnt)
+ {
+ if (byteCnt > 4)
+ {
+ byteCnt = 4;
+ }
+ do
+ {
+ buf[ofst++] = (uint8_t)(val & 0xff);
+ val >>= 8;
+ } while (--byteCnt);
+ }
+}
+
+// Read a byte optionally performing SLIP decoding. The return values are:
+//
+// 2 - an escaped byte was read successfully
+// 1 - a non-escaped byte was read successfully
+// 0 - no data was available
+// -1 - the value 0xc0 was encountered (shouldn't happen)
+// -2 - a SLIP escape byte was found but the following byte wasn't available
+// -3 - a SLIP escape byte was followed by an invalid byte
+int WifiFirmwareUploader::ReadByte(uint8_t& data, bool slipDecode)
+{
+ if (uploadPort.available() == 0)
+ {
+ return(0);
+ }
+
+ // at least one byte is available
+ data = uploadPort.read();
+ if (!slipDecode)
+ {
+ return(1);
+ }
+
+ if (data == 0xc0)
+ {
+ // this shouldn't happen
+ return(-1);
+ }
+
+ // if not the SLIP escape, we're done
+ if (data != 0xdb)
+ {
+ return(1);
+ }
+
+ // SLIP escape, check availability of subsequent byte
+ if (uploadPort.available() == 0)
+ {
+ return(-2);
+ }
+
+ // process the escaped byte
+ data = uploadPort.read();
+ if (data == 0xdc)
+ {
+ data = 0xc0;
+ return(2);
+ }
+
+ if (data == 0xdd)
+ {
+ data = 0xdb;
+ return(2);
+ }
+ // invalid
+ return(-3);
+}
+
+// When we write a sync packet, there must be no gaps between most of the characters.
+// So use this function, which does a block write to the UART buffer in the latest CoreNG.
+void WifiFirmwareUploader::writePacketRaw(const uint8_t *buf, size_t len)
+{
+ uploadPort.write(buf, len);
+}
+
+// Write a byte to the serial port optionally SLIP encoding. Return the number of bytes actually written.
+inline void WifiFirmwareUploader::WriteByteRaw(uint8_t b)
+{
+ uploadPort.write(b);
+}
+
+// Write a byte to the serial port optionally SLIP encoding. Return the number of bytes actually written.
+inline void WifiFirmwareUploader::WriteByteSlip(uint8_t b)
+{
+ if (b == 0xC0)
+ {
+ WriteByteRaw(0xDB);
+ WriteByteRaw(0xDC);
+ }
+ else if (b == 0xDB)
+ {
+ WriteByteRaw(0xDB);
+ WriteByteRaw(0xDD);
+ }
+ else
+ {
+ uploadPort.write(b);
+ }
+}
+
+// Wait for a data packet to be returned. If the body of the packet is
+// non-zero length, return an allocated buffer indirectly containing the
+// data and return the data length. Note that if the pointer for returning
+// the data buffer is NULL, the response is expected to be two bytes of zero.
+//
+// If an error occurs, return a negative value. Otherwise, return the number
+// of bytes in the response (or zero if the response was not the standard "two bytes of zero").
+WifiFirmwareUploader::EspUploadResult WifiFirmwareUploader::readPacket(uint8_t op, uint32_t *valp, size_t& bodyLen, uint32_t msTimeout)
+{
+ enum class PacketState
+ {
+ begin = 0,
+ header,
+ body,
+ end,
+ done
+ };
+
+ const size_t headerLength = 8;
+
+ uint32_t startTime = millis();
+ uint8_t hdr[headerLength];
+ uint16_t hdrIdx = 0;
+ bodyLen = 0;
+ uint16_t bodyIdx = 0;
+ uint8_t respBuf[2];
+
+ // wait for the response
+ uint16_t needBytes = 1;
+ PacketState state = PacketState::begin;
+ while (state != PacketState::done)
+ {
+ uint8_t c;
+ EspUploadResult stat;
+
+ if (millis() - startTime > msTimeout)
+ {
+ return(EspUploadResult::timeout);
+ }
+
+ if (uploadPort.available() < needBytes)
+ {
+ // insufficient data available
+ // preferably, return to Spin() here
+ continue;
+ }
+
+ // sufficient bytes have been received for the current state, process them
+ switch(state)
+ {
+ case PacketState::begin: // expecting frame start
+ case PacketState::end: // expecting frame end
+ c = uploadPort.read();
+ if (c != 0xc0)
+ {
+ return EspUploadResult::slipFrame;
+ }
+ if (state == PacketState::begin)
+ {
+ state = PacketState::header;
+ needBytes = 2;
+ }
+ else
+ {
+ state = PacketState::done;
+ }
+ break;
+
+ case PacketState::header: // reading an 8-byte header
+ case PacketState::body: // reading the response body
+ {
+ int rslt;
+ // retrieve a byte with SLIP decoding
+ rslt = ReadByte(c, true);
+ if (rslt != 1 && rslt != 2)
+ {
+ // some error occurred
+ stat = (rslt == 0 || rslt == -2) ? EspUploadResult::slipData : EspUploadResult::slipFrame;
+ return stat;
+ }
+ else if (state == PacketState::header)
+ {
+ //store the header byte
+ hdr[hdrIdx++] = c;
+ if (hdrIdx >= headerLength)
+ {
+ // get the body length, prepare a buffer for it
+ bodyLen = (uint16_t)getData(2, hdr, 2);
+
+ // extract the value, if requested
+ if (valp != nullptr)
+ {
+ *valp = getData(4, hdr, 4);
+ }
+
+ if (bodyLen != 0)
+ {
+ state = PacketState::body;
+ }
+ else
+ {
+ needBytes = 1;
+ state = PacketState::end;
+ }
+ }
+ }
+ else
+ {
+ // Store the response body byte, check for completion
+ if (bodyIdx < ARRAY_SIZE(respBuf))
+ {
+ respBuf[bodyIdx] = c;
+ }
+ ++bodyIdx;
+ if (bodyIdx >= bodyLen)
+ {
+ needBytes = 1;
+ state = PacketState::end;
+ }
+ }
+ }
+ break;
+
+ default: // this shouldn't happen
+ return EspUploadResult::slipState;
+ }
+ }
+
+ // Extract elements from the header
+ const uint8_t resp = (uint8_t)getData(1, hdr, 0);
+ const uint8_t opRet = (uint8_t)getData(1, hdr, 1);
+ // Sync packets often provoke a response with a zero opcode instead of ESP_SYNC
+ if (resp != 0x01 || opRet != op)
+ {
+//debugPrintf("resp %02x %02x\n", resp, opRet);
+ return EspUploadResult::respHeader;
+ }
+
+ return EspUploadResult::success;
+}
+
+// Send a block of data performing SLIP encoding of the content.
+inline void WifiFirmwareUploader::writePacket(const uint8_t *data, size_t len)
+{
+ while (len != 0)
+ {
+ WriteByteSlip(*data++);
+ --len;
+ }
+}
+
+// Send a packet to the serial port while performing SLIP framing. The packet data comprises a header and an optional data block.
+// A SLIP packet begins and ends with 0xc0. The data encapsulated has the bytes
+// 0xc0 and 0xdb replaced by the two-byte sequences {0xdb, 0xdc} and {0xdb, 0xdd} respectively.
+void WifiFirmwareUploader::writePacket(const uint8_t *hdr, size_t hdrLen, const uint8_t *data, size_t dataLen)
+{
+ WriteByteRaw(0xc0); // send the packet start character
+ writePacket(hdr, hdrLen); // send the header
+ writePacket(data, dataLen); // send the data block
+ WriteByteRaw(0xc0); // send the packet end character
+}
+
+// Send a packet to the serial port while performing SLIP framing. The packet data comprises a header and an optional data block.
+// This is like writePacket except that it does a fast block write for both the header and the main data with no SLIP encoding. Used to send sync commands.
+void WifiFirmwareUploader::writePacketRaw(const uint8_t *hdr, size_t hdrLen, const uint8_t *data, size_t dataLen)
+{
+ WriteByteRaw(0xc0); // send the packet start character
+ writePacketRaw(hdr, hdrLen); // send the header
+ writePacketRaw(data, dataLen); // send the data block in raw mode
+ WriteByteRaw(0xc0); // send the packet end character
+}
+
+// Send a command to the attached device together with the supplied data, if any.
+// The data is supplied via a list of one or more segments.
+void WifiFirmwareUploader::sendCommand(uint8_t op, uint32_t checkVal, const uint8_t *data, size_t dataLen)
+{
+ // populate the header
+ uint8_t hdr[8];
+ putData(0, 1, hdr, 0);
+ putData(op, 1, hdr, 1);
+ putData(dataLen, 2, hdr, 2);
+ putData(checkVal, 4, hdr, 4);
+
+ // send the packet
+ flushInput();
+ if (op == ESP_SYNC)
+ {
+ writePacketRaw(hdr, sizeof(hdr), data, dataLen);
+ }
+ else
+ {
+ writePacket(hdr, sizeof(hdr), data, dataLen);
+ }
+}
+
+// Send a command to the attached device together with the supplied data, if any, and get the response
+WifiFirmwareUploader::EspUploadResult WifiFirmwareUploader::doCommand(uint8_t op, const uint8_t *data, size_t dataLen, uint32_t checkVal, uint32_t *valp, uint32_t msTimeout)
+{
+ sendCommand(op, checkVal, data, dataLen);
+ size_t bodyLen;
+ EspUploadResult stat = readPacket(op, valp, bodyLen, msTimeout);
+ if (stat == EspUploadResult::success && bodyLen != 2)
+ {
+ stat = EspUploadResult::badReply;
+ }
+
+ return stat;
+}
+
+// Send a synchronising packet to the serial port in an attempt to induce
+// the ESP8266 to auto-baud lock on the baud rate.
+WifiFirmwareUploader::EspUploadResult WifiFirmwareUploader::Sync(uint16_t timeout)
+{
+ uint8_t buf[36];
+
+ // compose the data for the sync attempt
+ memset(buf, 0x55, sizeof(buf));
+ buf[0] = 0x07;
+ buf[1] = 0x07;
+ buf[2] = 0x12;
+ buf[3] = 0x20;
+
+ EspUploadResult stat = doCommand(ESP_SYNC, buf, sizeof(buf), 0, nullptr, timeout);
+
+ // If we got a response other than sync, discard it and wait for a sync response. This happens at higher baud rates.
+ for (int i = 0; i < 10 && stat == EspUploadResult::respHeader; ++i)
+ {
+ size_t bodyLen;
+ stat = readPacket(ESP_SYNC, nullptr, bodyLen, timeout);
+ }
+
+ if (stat == EspUploadResult::success)
+ {
+ // Read and discard additional replies
+ for (;;)
+ {
+ size_t bodyLen;
+ EspUploadResult rc = readPacket(ESP_SYNC, nullptr, bodyLen, defaultTimeout);
+ if (rc != EspUploadResult::success || bodyLen != 2)
+ {
+ break;
+ }
+ }
+ }
+//DEBUG
+// else debugPrintf("stat=%d\n", (int)stat);
+ return stat;
+}
+
+// Send a command to the device to begin the Flash process.
+WifiFirmwareUploader::EspUploadResult WifiFirmwareUploader::flashBegin(uint32_t addr, uint32_t size)
+{
+ // determine the number of blocks represented by the size
+ uint32_t blkCnt;
+ blkCnt = (size + EspFlashBlockSize - 1) / EspFlashBlockSize;
+
+ // ensure that the address is on a block boundary
+ addr &= ~(EspFlashBlockSize - 1);
+
+ // begin the Flash process
+ uint8_t buf[16];
+ putData(size, 4, buf, 0);
+ putData(blkCnt, 4, buf, 4);
+ putData(EspFlashBlockSize, 4, buf, 8);
+ putData(addr, 4, buf, 12);
+
+ uint32_t timeout = (size != 0) ? eraseTimeout : defaultTimeout;
+ return doCommand(ESP_FLASH_BEGIN, buf, sizeof(buf), 0, nullptr, timeout);
+}
+
+// Send a command to the device to terminate the Flash process
+WifiFirmwareUploader::EspUploadResult WifiFirmwareUploader::flashFinish(bool reboot)
+{
+ uint8_t buf[4];
+ putData(reboot ? 0 : 1, 4, buf, 0);
+ return doCommand(ESP_FLASH_END, buf, sizeof(buf), 0, nullptr, defaultTimeout);
+}
+
+// Compute the checksum of a block of data
+uint16_t WifiFirmwareUploader::checksum(const uint8_t *data, uint16_t dataLen, uint16_t cksum)
+{
+ if (data != NULL)
+ {
+ while (dataLen--)
+ {
+ cksum ^= (uint16_t)*data++;
+ }
+ }
+ return(cksum);
+}
+
+WifiFirmwareUploader::EspUploadResult WifiFirmwareUploader::flashWriteBlock(uint16_t flashParmVal, uint16_t flashParmMask)
+{
+ const uint32_t blkSize = EspFlashBlockSize;
+
+ // Allocate a data buffer for the combined header and block data
+ const uint16_t hdrOfst = 0;
+ const uint16_t dataOfst = 16;
+ const uint16_t blkBufSize = dataOfst + blkSize;
+ uint8_t blkBuf[blkBufSize];
+
+ // Prepare the header for the block
+ putData(blkSize, 4, blkBuf, hdrOfst + 0);
+ putData(uploadBlockNumber, 4, blkBuf, hdrOfst + 4);
+ putData(0, 4, blkBuf, hdrOfst + 8);
+ putData(0, 4, blkBuf, hdrOfst + 12);
+
+ // Get the data for the block
+ size_t cnt = uploadFile->Read((char *)blkBuf + dataOfst, blkSize);
+ if (cnt != EspFlashBlockSize)
+ {
+ if (uploadFile->Position() == fileSize)
+ {
+ // partial last block, fill the remainder
+ memset(blkBuf + dataOfst + cnt, 0xff, blkSize - cnt);
+ }
+ else
+ {
+ return EspUploadResult::fileRead;
+ }
+ }
+
+ // Patch the flash parameters into the first block if it is loaded at address 0
+ if (uploadBlockNumber == 0 && uploadAddress == 0 && blkBuf[dataOfst] == ESP_IMAGE_MAGIC && flashParmMask != 0)
+ {
+ // update the Flash parameters
+ uint32_t flashParm = getData(2, blkBuf + dataOfst + 2, 0) & ~(uint32_t)flashParmMask;
+ putData(flashParm | flashParmVal, 2, blkBuf + dataOfst + 2, 0);
+ }
+
+ // Calculate the block checksum
+ uint16_t cksum = checksum(blkBuf + dataOfst, blkSize, ESP_CHECKSUM_MAGIC);
+ EspUploadResult stat;
+ for (int i = 0; i < 3; i++)
+ {
+ if ((stat = doCommand(ESP_FLASH_DATA, blkBuf, blkBufSize, cksum, nullptr, blockWriteTimeout)) == EspUploadResult::success)
+ {
+ break;
+ }
+ }
+
+ return stat;
+}
+
+void WifiFirmwareUploader::Spin()
+{
+ switch (state)
+ {
+ case UploadState::resetting:
+ if (connectAttemptNumber == ARRAY_SIZE(uploadBaudRates) * retriesPerBaudRate)
+ {
+ // Time to give up
+ Network::ResetWiFi();
+ uploadResult = EspUploadResult::connect;
+ state = UploadState::done;
+ }
+ else
+ {
+ // Reset the serial port at the new baud rate. Also reset the ESP8266.
+ const uint32_t baud = uploadBaudRates[connectAttemptNumber/retriesPerBaudRate];
+ if (connectAttemptNumber % retriesPerBaudRate == 0)
+ {
+ // First attempt at this baud rate
+ MessageF("Trying to connect at %u baud: ", baud);
+ }
+ uploadPort.begin(baud);
+ uploadPort.setInterruptPriority(1); // we are going to move data at seriously high speeds
+ Network::ResetWiFiForUpload();
+ lastAttemptTime = lastResetTime = millis();
+ state = UploadState::connecting;
+ }
+ break;
+
+ case UploadState::connecting:
+ if (millis() - lastAttemptTime >= connectAttemptInterval && millis() - lastResetTime >= resetDelay)
+ {
+ // Attempt to establish a connection to the ESP8266.
+ EspUploadResult res = Sync(syncTimeout);
+ lastAttemptTime = millis();
+ if (res == EspUploadResult::success)
+ {
+ // Successful connection
+// MessageF(" success on attempt %d\n", (connectAttemptNumber % retriesPerBaudRate) + 1);
+ MessageF(" success\n");
+ state = UploadState::erasing;
+ }
+ else
+ {
+ // This attempt failed
+ ++connectAttemptNumber;
+ if (connectAttemptNumber % retriesPerReset == 0)
+ {
+ if (connectAttemptNumber % retriesPerBaudRate == 0)
+ {
+ MessageF(" failed\n");
+ }
+ state = UploadState::resetting; // try a reset and a lower baud rate
+ }
+ }
+ }
+ break;
+
+ case UploadState::erasing:
+ if (millis() - lastAttemptTime >= blockWriteInterval)
+ {
+ const uint32_t sectorsPerBlock = 16;
+ const uint32_t sectorSize = 4096;
+ const uint32_t numSectors = (fileSize + sectorSize - 1)/sectorSize;
+ const uint32_t startSector = uploadAddress/sectorSize;
+ uint32_t headSectors = sectorsPerBlock - (startSector % sectorsPerBlock);
+
+ if (numSectors < headSectors)
+ {
+ headSectors = numSectors;
+ }
+ const uint32_t eraseSize = (numSectors < 2 * headSectors)
+ ? (numSectors + 1) / 2 * sectorSize
+ : (numSectors - headSectors) * sectorSize;
+
+ MessageF("Erasing %u bytes...\n", fileSize);
+ uploadResult = flashBegin(uploadAddress, eraseSize);
+ if (uploadResult == EspUploadResult::success)
+ {
+ MessageF("Uploading file...\n");
+ uploadBlockNumber = 0;
+ uploadNextPercentToReport = percentToReportIncrement;
+ lastAttemptTime = millis();
+ state = UploadState::uploading;
+ }
+ else
+ {
+ MessageF("Erase failed\n");
+ state = UploadState::done;
+ }
+ }
+ break;
+
+ case UploadState::uploading:
+ // The ESP needs several milliseconds to recover from one packet before it will accept another
+ if (millis() - lastAttemptTime >= blockWriteInterval)
+ {
+ const uint32_t blkCnt = (fileSize + EspFlashBlockSize - 1) / EspFlashBlockSize;
+ if (uploadBlockNumber < blkCnt)
+ {
+ uploadResult = flashWriteBlock(0, 0);
+ lastAttemptTime = millis();
+ if (uploadResult != EspUploadResult::success)
+ {
+ MessageF("Flash block upload failed\n");
+ state = UploadState::done;
+ }
+ const unsigned int percentComplete = (100 * uploadBlockNumber)/blkCnt;
+ ++uploadBlockNumber;
+ if (percentComplete >= uploadNextPercentToReport)
+ {
+ MessageF("%u%% complete\n", percentComplete);
+ uploadNextPercentToReport += percentToReportIncrement;
+ }
+ }
+ else
+ {
+ state = UploadState::done;
+ }
+ }
+ break;
+
+ case UploadState::done:
+ uploadFile->Close();
+ uploadPort.end(); // disable the port, it has a high interrupt priority
+ if (uploadResult == EspUploadResult::success)
+ {
+ reprap.GetPlatform()->Message(FIRMWARE_UPDATE_MESSAGE, "Upload successful\n");
+ if (restartOnCompletion)
+ {
+ reprap.GetNetwork()->Start();
+ }
+ else
+ {
+ reprap.GetNetwork()->ResetWiFi();
+ }
+ }
+ else
+ {
+ reprap.GetPlatform()->MessageF(FIRMWARE_UPDATE_MESSAGE, "Error: Installation failed due to %s error\n", resultMessages[(size_t)uploadResult]);
+ // Not safe to restart the network
+ reprap.GetNetwork()->ResetWiFi();
+ }
+ state = UploadState::idle;
+ break;
+
+ default:
+ break;
+ }
+}
+
+// Try to upload the given file at the given address
+void WifiFirmwareUploader::SendUpdateFile(const char *file, const char *dir, uint32_t address)
+{
+ Platform *platform = reprap.GetPlatform();
+ uploadFile = platform->GetFileStore(dir, file, false);
+ if (uploadFile == nullptr)
+ {
+ platform->MessageF(FIRMWARE_UPDATE_MESSAGE, "Failed to open file %s\n", file);
+ return;
+ }
+
+ fileSize = uploadFile->Length();
+ if (fileSize == 0)
+ {
+ uploadFile->Close();
+ platform->MessageF(FIRMWARE_UPDATE_MESSAGE, "Upload file is empty %s\n", file);
+ return;
+ }
+
+ // Stop the network
+ Network *network = reprap.GetNetwork();
+ restartOnCompletion = network->IsEnabled();
+ network->Stop();
+
+ // Set up the state so that subsequent calls to Spin() will attempt the upload
+ uploadAddress = address;
+ connectAttemptNumber = 0;
+ state = UploadState::resetting;
+}
+
+// End
diff --git a/src/DuetNG/WifiFirmwareUploader.h b/src/DuetNG/WifiFirmwareUploader.h
new file mode 100644
index 00000000..33f387f3
--- /dev/null
+++ b/src/DuetNG/WifiFirmwareUploader.h
@@ -0,0 +1,96 @@
+/*
+ * EspFirmwareUpload.h
+ *
+ * Created on: 15 Apr 2016
+ * Author: David
+ */
+
+#ifndef SRC_DUETNG_WIFIFIRMWAREUPLOADER_H_
+#define SRC_DUETNG_WIFIFIRMWAREUPLOADER_H_
+
+#include "Core.h"
+#include "FileStore.h"
+
+class WifiFirmwareUploader
+{
+public:
+ WifiFirmwareUploader(UARTClass& port);
+ bool IsReady() const;
+ void SendUpdateFile(const char *file, const char *dir, uint32_t address);
+ void Spin();
+
+ static const uint32_t FirmwareAddress = 0x00000000;
+ static const uint32_t WebFilesAddress = 0x00100000;
+
+private:
+ static const uint32_t defaultTimeout = 500; // default timeout in milliseconds
+ static const uint32_t syncTimeout = 1000;
+ static const unsigned int retriesPerBaudRate = 9;
+ static const unsigned int retriesPerReset = 3;
+ static const uint32_t connectAttemptInterval = 50;
+ static const uint32_t resetDelay = 500;
+ static const uint32_t blockWriteInterval = 15; // 15ms is long enough, 10ms is mostly too short
+ static const uint32_t blockWriteTimeout = 200;
+ static const uint32_t eraseTimeout = 15000; // increased from 12 to 15 seconds because Roland's board was timing out
+ static const unsigned int percentToReportIncrement = 5; // how often we report % complete
+
+ // Return codes - this list must be kept in step with the corresponding messages
+ enum class EspUploadResult
+ {
+ success = 0,
+ timeout,
+ connect,
+ badReply,
+ fileRead,
+ emptyFile,
+ respHeader,
+ slipFrame,
+ slipState,
+ slipData,
+ };
+
+ enum class UploadState
+ {
+ idle,
+ resetting,
+ connecting,
+ erasing,
+ uploading,
+ done
+ };
+
+ void MessageF(const char *fmt, ...);
+ uint32_t getData(unsigned byteCnt, const uint8_t *buf, int ofst);
+ void putData(uint32_t val, unsigned byteCnt, uint8_t *buf, int ofst);
+ int ReadByte(uint8_t& data, bool slipDecode);
+ void WriteByteRaw(uint8_t b);
+ void WriteByteSlip(uint8_t b);
+ void flushInput();
+ EspUploadResult readPacket(uint8_t op, uint32_t *valp, size_t& bodyLen, uint32_t msTimeout);
+ void writePacket(const uint8_t *data, size_t len);
+ void writePacketRaw(const uint8_t *buf, size_t len);
+ void writePacket(const uint8_t *hdr, size_t hdrLen, const uint8_t *data, size_t dataLen);
+ void writePacketRaw(const uint8_t *hdr, size_t hdrLen, const uint8_t *data, size_t dataLen);
+ void sendCommand(uint8_t op, uint32_t checkVal, const uint8_t *data, size_t dataLen);
+ EspUploadResult doCommand(uint8_t op, const uint8_t *data, size_t dataLen, uint32_t checkVal, uint32_t *valp, uint32_t msTimeout);
+ EspUploadResult Sync(uint16_t timeout);
+ EspUploadResult flashBegin(uint32_t addr, uint32_t size);
+ EspUploadResult flashFinish(bool reboot);
+ static uint16_t checksum(const uint8_t *data, uint16_t dataLen, uint16_t cksum);
+ EspUploadResult flashWriteBlock(uint16_t flashParmVal, uint16_t flashParmMask);
+
+ UARTClass& uploadPort;
+ FileStore *uploadFile;
+ FilePosition fileSize;
+ uint32_t uploadAddress;
+ uint32_t uploadBlockNumber;
+ unsigned int uploadNextPercentToReport;
+ unsigned int connectAttemptNumber;
+ uint32_t lastAttemptTime;
+ uint32_t lastResetTime;
+ UploadState state;
+ EspUploadResult uploadResult;
+ bool restartOnCompletion;
+};
+
+#endif /* SRC_DUETNG_WIFIFIRMWAREUPLOADER_H_ */
diff --git a/src/ExternalDrivers.cpp b/src/ExternalDrivers.cpp
index 04634fa0..0fb01a0d 100644
--- a/src/ExternalDrivers.cpp
+++ b/src/ExternalDrivers.cpp
@@ -5,7 +5,6 @@
* Author: David
*/
-//#include "Platform.h" // for typedefs uint8_t etc.
#include "RepRapFirmware.h"
#ifdef EXTERNAL_DRIVERS
@@ -41,29 +40,45 @@ const size_t NumExternalDrivers = DRIVES - FIRST_EXTERNAL_DRIVE;
// 5V_USB 5 +3.3V 3 (+3.3V)
#ifdef DUET_NG
-# if 1
+
+const Pin DriverSelectPins[NumExternalDrivers] = {78, 41, 42, 49, 57, 87, 88, 89, 90};
+
+# ifdef PROTOTYPE_1
+
// Pin assignments for the first prototype, using USART0 SPI
const Pin DriversMosiPin = 27; // PB1
const Pin DriversMisoPin = 26; // PB0
const Pin DriversSclkPin = 30; // PB13
# define USART_EXT_DRV USART0
# define ID_USART_EXT_DRV ID_USART0
+
# else
+
+# include "sam/drivers/tc/tc.h"
+
// Pin assignments for the second prototype, using USART1 SPI
+const Pin DriversClockPin = 15; // PB15/TIOA1
const Pin DriversMosiPin = 22; // PA13
const Pin DriversMisoPin = 21; // PA22
const Pin DriversSclkPin = 23; // PA23
# define USART_EXT_DRV USART1
# define ID_USART_EXT_DRV ID_USART1
+# define TMC_CLOCK_TC TC0
+# define TMC_CLOCK_CHAN 1
+# define TMC_CLOCK_ID ID_TC1 // this is channel 1 on TC0
# endif
-const Pin DriverSelectPins[NumExternalDrivers] = {87, 88, 89, 90};
+
#else
+
+// Duet 0.6 or 0.8.5
+
+const Pin DriverSelectPins[NumExternalDrivers] = {37, X8, 50, 47 /*, X13*/ };
const Pin DriversMosiPin = 16; // PA13
const Pin DriversMisoPin = 17; // PA12
const Pin DriversSclkPin = 54; // PA16
-const Pin DriverSelectPins[NumExternalDrivers] = {37, X8, 50, 47 /*, X13*/ };
# define USART_EXT_DRV USART1
# define ID_USART_EXT_DRV ID_USART1
+
#endif
const uint32_t DriversSpiClockFrequency = 1000000; // 1MHz SPI clock for now
@@ -171,6 +186,7 @@ const uint32_t defaultSgscConfReg =
// Driver configuration register
const uint32_t defaultDrvConfReg =
TMC_REG_DRVCONF
+ | TMC_DRVCONF_VSENSE // use high sensitivity range
| 0;
// Driver control register
@@ -275,25 +291,40 @@ void TmcDriverState::SetMicrostepping(uint32_t shift, bool interpolate)
void TmcDriverState::SetCurrent(float current)
{
- // I am assuming that the current sense resistor is 0.1 ohms as on the evaluation board.
- // This gives us a range of 95mA to 3.05A in 95mA steps when VSENSE is high (but max allowed RMS current is 2A),
- // or 52mA to 1.65A in 52mA steps when VSENSE is low.
+#if defined(DUET_NG) && !defined(PROTOTYPE_1)
+
+ // The current sense resistor is 0.051 ohms.
+ // This gives us a range of 101mA to 3.236A in 101mA steps in the high sensitivity range (VSENSE = 1)
+ drvConfReg |= TMC_DRVCONF_VSENSE; // this should always be set, but send it again just in case
+ SpiSendWord(pin, drvConfReg);
+
+ const uint32_t iCurrent = (current > 2000.0) ? 2000 : (current < 100) ? 100 : (uint32_t)current;
+ const uint32_t csBits = (uint32_t)((32 * iCurrent - 1600)/3236); // formula checked by simulation on a spreadsheet
+ sgcsConfReg &= ~TMC_SGCSCONF_CS_MASK;
+ sgcsConfReg |= TMC_SGCSCONF_CS(csBits);
+ SpiSendWord(pin, sgcsConfReg);
+
+#else
+
+ // The current sense resistor is 0.1 ohms on the evaluation board.
+ // This gives us a range of 95mA to 3.05A in 95mA steps when VSENSE is low (but max allowed RMS current is 2A),
+ // or 52mA to 1.65A in 52mA steps when VSENSE is high.
if (current > 1650.0)
{
- // Need VSENSE = 1, but set up the current first to avoid temporarily exceeding the 2A rating
+ // Need VSENSE = 0, but set up the current first to avoid temporarily exceeding the 2A rating
const uint32_t iCurrent = (current > 2000.0) ? 2000 : (uint32_t)current;
const uint32_t csBits = (uint32_t)((32 * iCurrent - 1500)/3050); // formula checked by simulation on a spreadsheet
sgcsConfReg &= ~TMC_SGCSCONF_CS_MASK;
sgcsConfReg |= TMC_SGCSCONF_CS(csBits);
SpiSendWord(pin, sgcsConfReg);
- drvConfReg |= TMC_DRVCONF_VSENSE;
+ drvConfReg &= ~TMC_DRVCONF_VSENSE;
SpiSendWord(pin, drvConfReg);
}
else
{
- // Use VSENSE = 0
- drvConfReg &= ~TMC_DRVCONF_VSENSE;
+ // Use VSENSE = 1
+ drvConfReg |= TMC_DRVCONF_VSENSE;
SpiSendWord(pin, drvConfReg);
const uint32_t iCurrent = (current < 50) ? 50 : (uint32_t)current;
@@ -302,6 +333,8 @@ void TmcDriverState::SetCurrent(float current)
sgcsConfReg |= TMC_SGCSCONF_CS(csBits);
SpiSendWord(pin, sgcsConfReg);
}
+
+ #endif
}
void TmcDriverState::Enable(bool en)
@@ -347,9 +380,24 @@ namespace ExternalDrivers
pio_configure(pin1.pPort, PIO_PERIPH_A, pin1.ulPin, PIO_DEFAULT);
#endif
- // Enable the clock to UART1
+ // Enable the clock to the USART
pmc_enable_periph_clk(ID_USART_EXT_DRV);
+#if defined(DUET_NG) && !defined(PROTOTYPE_1)
+ // Set up the 15MHz clock to the TMC drivers on TIOA1
+ pmc_enable_periph_clk(TMC_CLOCK_ID);
+ ConfigurePin(GetPinDescription(DriversClockPin)); // set up TIOA1 to be an output
+ tc_init(TMC_CLOCK_TC, TMC_CLOCK_CHAN,
+ TC_CMR_TCCLKS_TIMER_CLOCK1 | // clock is MCLK/2 (fastest available)
+ TC_CMR_BURST_NONE | // clock is not gated
+ TC_CMR_WAVE | // Waveform mode
+ TC_CMR_WAVSEL_UP_RC | // Counter runs up and reset when equals to RC
+ TC_CMR_EEVT_XC0 | // Set external events from XC0 (this sets up TIOB as output)
+ TC_CMR_ACPC_TOGGLE); // toggle TIOA output
+ tc_write_rc(TMC_CLOCK_TC, TMC_CLOCK_CHAN, 2); // divisor = 2, gives us a 15MHz clock with a master clock of 120MHz.
+ tc_start(TMC_CLOCK_TC, TMC_CLOCK_CHAN);
+#endif
+
// Set up the CS pins and set them all high
// When this becomes the standard code, we must set up the STEP and DIR pins here too.
for (size_t drive = 0; drive < NumExternalDrivers; ++drive)
diff --git a/src/GCodeBuffer.cpp b/src/GCodeBuffer.cpp
index 6ebedb31..c8480e27 100644
--- a/src/GCodeBuffer.cpp
+++ b/src/GCodeBuffer.cpp
@@ -184,7 +184,6 @@ bool GCodeBuffer::Seen(char c)
}
// Get a float after a G Code letter found by a call to Seen()
-
float GCodeBuffer::GetFValue()
{
if (readPointer < 0)
@@ -199,7 +198,6 @@ float GCodeBuffer::GetFValue()
}
// Get a :-separated list of floats after a key letter
-
const void GCodeBuffer::GetFloatArray(float a[], size_t& returnedLength)
{
if(readPointer < 0)
@@ -214,7 +212,7 @@ const void GCodeBuffer::GetFloatArray(float a[], size_t& returnedLength)
bool inList = true;
while(inList)
{
- if(length >= returnedLength) // Array limit has been set in here
+ if (length >= returnedLength) // array limit has been set in here
{
platform->MessageF(GENERIC_MESSAGE, "Error: GCodes: Attempt to read a GCode float array that is too long: %s\n", gcodeBuffer);
readPointer = -1;
@@ -223,12 +221,11 @@ const void GCodeBuffer::GetFloatArray(float a[], size_t& returnedLength)
}
a[length] = (float)strtod(&gcodeBuffer[readPointer + 1], 0);
length++;
- readPointer++;
- while(gcodeBuffer[readPointer] && (gcodeBuffer[readPointer] != ' ') && (gcodeBuffer[readPointer] != LIST_SEPARATOR))
+ do
{
readPointer++;
- }
- if(gcodeBuffer[readPointer] != LIST_SEPARATOR)
+ } while(gcodeBuffer[readPointer] && (gcodeBuffer[readPointer] != ' ') && (gcodeBuffer[readPointer] != LIST_SEPARATOR));
+ if (gcodeBuffer[readPointer] != LIST_SEPARATOR)
{
inList = false;
}
@@ -252,10 +249,9 @@ const void GCodeBuffer::GetFloatArray(float a[], size_t& returnedLength)
}
// Get a :-separated list of longs after a key letter
-
const void GCodeBuffer::GetLongArray(long l[], size_t& returnedLength)
{
- if(readPointer < 0)
+ if (readPointer < 0)
{
platform->Message(GENERIC_MESSAGE, "Error: GCodes: Attempt to read a GCode long array before a search.\n");
readPointer = -1;
@@ -266,7 +262,7 @@ const void GCodeBuffer::GetLongArray(long l[], size_t& returnedLength)
bool inList = true;
while(inList)
{
- if(length >= returnedLength) // Array limit has been set in here
+ if (length >= returnedLength) // Array limit has been set in here
{
platform->MessageF(GENERIC_MESSAGE, "Error: GCodes: Attempt to read a GCode long array that is too long: %s\n", gcodeBuffer);
readPointer = -1;
@@ -275,12 +271,11 @@ const void GCodeBuffer::GetLongArray(long l[], size_t& returnedLength)
}
l[length] = strtol(&gcodeBuffer[readPointer + 1], 0, 0);
length++;
- readPointer++;
- while(gcodeBuffer[readPointer] && (gcodeBuffer[readPointer] != ' ') && (gcodeBuffer[readPointer] != LIST_SEPARATOR))
+ do
{
readPointer++;
- }
- if(gcodeBuffer[readPointer] != LIST_SEPARATOR)
+ } while(gcodeBuffer[readPointer] != 0 && (gcodeBuffer[readPointer] != ' ') && (gcodeBuffer[readPointer] != LIST_SEPARATOR));
+ if (gcodeBuffer[readPointer] != LIST_SEPARATOR)
{
inList = false;
}
diff --git a/src/GCodes.cpp b/src/GCodes.cpp
index fb2ae899..908f8ead 100644
--- a/src/GCodes.cpp
+++ b/src/GCodes.cpp
@@ -56,7 +56,6 @@ void GCodes::Exit()
void GCodes::Init()
{
Reset();
- firmwareUpdateModuleMap = 0;
distanceScale = 1.0;
rawExtruderTotal = 0.0;
for (size_t extruder = 0; extruder < DRIVES - AXES; extruder++)
@@ -122,7 +121,7 @@ void GCodes::Reset()
feedRate = pausedMoveBuffer[DRIVES] = DEFAULT_FEEDRATE/minutesToSeconds;
ClearMove();
- for (size_t i =0; i < MaxTriggers; ++i)
+ for (size_t i = 0; i < MaxTriggers; ++i)
{
triggers[i].Init();
}
@@ -139,6 +138,7 @@ void GCodes::Reset()
isPaused = false;
filePos = moveBuffer.filePos = noFilePosition;
lastEndstopStates = platform->GetAllEndstopStates();
+ firmwareUpdateModuleMap = 0;
}
float GCodes::FractionOfFilePrinted() const
@@ -397,7 +397,7 @@ void GCodes::Spin()
if (FirmwareUpdater::IsReady())
{
bool updating = false;
- for (unsigned int module = 0; module < NumFirmwareUpdateModules; ++module)
+ for (unsigned int module = 1; module < NumFirmwareUpdateModules; ++module)
{
if ((firmwareUpdateModuleMap & (1 << module)) != 0)
{
@@ -626,7 +626,10 @@ bool GCodes::CheckTriggers()
unsigned int lowestTriggerPending = MaxTriggers;
for (unsigned int triggerNumber = 0; triggerNumber < MaxTriggers; ++triggerNumber)
{
- if ((triggers[triggerNumber].rising & risen) != 0 || (triggers[triggerNumber].falling & fallen) != 0)
+ const Trigger& ct = triggers[triggerNumber];
+ if ( ((ct.rising & risen) != 0 || (ct.falling & fallen) != 0)
+ && (ct.condition == 0 || (ct.condition == 1 && reprap.GetPrintMonitor()->IsPrinting()))
+ )
{
triggersPending |= (1 << triggerNumber);
}
@@ -740,7 +743,6 @@ void GCodes::Diagnostics(MessageType mtype)
platform->Message(mtype, "GCodes Diagnostics:\n");
platform->MessageF(mtype, "Move available? %s\n", moveAvailable ? "yes" : "no");
platform->MessageF(mtype, "Stack pointer: %u of %u\n", stackPointer, StackSize);
-
fileMacroGCode->Diagnostics(mtype);
httpGCode->Diagnostics(mtype);
telnetGCode->Diagnostics(mtype);
@@ -1642,9 +1644,10 @@ void GCodes::GetCurrentCoordinates(StringRef& s) const
s.catf("E%u:%.1f ", i - AXES, liveCoordinates[i]);
}
- // Print the stepper motor positions as Marlin does, as an aid to debugging
+ // 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 < DRIVES; ++i)
+ for (size_t i = 0; i < AXES; ++i)
{
s.catf(" %d", reprap.GetMove()->GetEndPoint(i));
}
@@ -2769,7 +2772,7 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply)
bool error = false;
int code = gb->GetIValue();
- if (simulationMode != 0 && (code < 20 || code > 37) && code != 82 && code != 83 && code != 111 && code != 105 && code != 122 && code != 408 && code != 999)
+ if (simulationMode != 0 && (code < 20 || code > 37) && code != 0 && code != 1 && code != 82 && code != 83 && code != 111 && code != 105 && code != 122 && code != 408 && code != 999)
{
return true; // we don't yet simulate most M codes
}
@@ -3409,6 +3412,10 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply)
hh |= (1 << (unsigned int)hnum);
}
}
+ if (hh != 0)
+ {
+ platform->SetFanValue(fanNum, 1.0); // default the fan speed to full for safety
+ }
platform->SetHeatersMonitored(fanNum, hh);
}
@@ -3510,7 +3517,7 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply)
DoEmergencyStop();
break;
- case 114: // Deprecated
+ case 114:
GetCurrentCoordinates(reply);
break;
@@ -3585,11 +3592,8 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply)
case 117: // Display message
{
- const char *msg = gb->GetUnprecedentedString();
- if (msg != nullptr)
- {
- reprap.SetMessage(msg);
- }
+ const char *msg = gb->GetUnprecedentedString(true);
+ reprap.SetMessage((msg == nullptr) ? "" : msg);
}
break;
@@ -4112,7 +4116,11 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply)
{
seen = true;
int microsteps = gb->GetIValue();
- if (!ChangeMicrostepping(axis, microsteps, interp))
+ if (ChangeMicrostepping(axis, microsteps, interp))
+ {
+ axisIsHomed[axis] = false;
+ }
+ else
{
platform->MessageF(GENERIC_MESSAGE, "Drive %c does not support %dx microstepping%s\n",
axisLetters[axis], microsteps, (interp) ? " with interpolation" : "");
@@ -5018,63 +5026,81 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply)
triggersPending |= (1 << triggerNumber);
}
}
- else if (gb->Seen('S'))
+ else
{
- int sval = gb->GetIValue();
- TriggerMask triggerMask = 0;
- for (size_t axis = 0; axis < AXES; ++axis)
+ bool seen = false;
+ if (gb->Seen('C'))
{
- if (gb->Seen(axisLetters[axis]))
- {
- triggerMask |= (1u << axis);
- }
+ 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(extrudeLetter))
+ if (gb->Seen('S'))
{
- long eStops[DRIVES - AXES];
- size_t numEntries = DRIVES - AXES;
- gb->GetLongArray(eStops, numEntries);
- for (size_t i = 0; i < numEntries; ++i)
+ seen = true;
+ int sval = gb->GetIValue();
+ TriggerMask triggerMask = 0;
+ for (size_t axis = 0; axis < AXES; ++axis)
{
- if (eStops[i] >= 0 && (unsigned long)eStops[i] < DRIVES - AXES)
+ if (gb->Seen(axisLetters[axis]))
{
- triggerMask |= (1u << (eStops[i] + AXES));
+ triggerMask |= (1u << axis);
}
}
- }
- switch(sval)
- {
- case -1:
- if (triggerMask == 0)
+ if (gb->Seen(extrudeLetter))
{
- triggers[triggerNumber].rising = triggers[triggerNumber].falling = 0;
+ long eStops[DRIVES - AXES];
+ size_t numEntries = DRIVES - AXES;
+ gb->GetLongArray(eStops, numEntries);
+ for (size_t i = 0; i < numEntries; ++i)
+ {
+ if (eStops[i] >= 0 && (unsigned long)eStops[i] < DRIVES - AXES)
+ {
+ triggerMask |= (1u << (eStops[i] + AXES));
+ }
+ }
}
- else
+ switch(sval)
{
- triggers[triggerNumber].rising &= (~triggerMask);
- triggers[triggerNumber].falling &= (~triggerMask);
- }
- break;
+ 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 0:
+ triggers[triggerNumber].falling |= triggerMask;
+ break;
- case 1:
- triggers[triggerNumber].rising |= triggerMask;
- break;
+ case 1:
+ triggers[triggerNumber].rising |= triggerMask;
+ break;
- default:
- platform->Message(GENERIC_MESSAGE, "Bad S parameter in M581 command\n");
+ 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
- {
- 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");
}
}
else
@@ -5301,27 +5327,21 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply)
break;
case 997: // Perform firmware update
+ if (!AllMovesAreFinishedAndMoveBufferIsLoaded())
+ {
+ 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
- long modulesToUpdate[3];
- size_t numUpdateModules;
if (gb->Seen('S'))
{
- numUpdateModules = ARRAY_SIZE(modulesToUpdate);
+ long modulesToUpdate[3];
+ size_t numUpdateModules = ARRAY_SIZE(modulesToUpdate);
gb->GetLongArray(modulesToUpdate, numUpdateModules);
- }
- else
- {
- numUpdateModules = 0;
- }
-
- if (numUpdateModules == 0)
- {
- firmwareUpdateModuleMap = (1 << 0); // no modules specified, so update module 0 to match old behaviour
- }
- else
- {
for (size_t i = 0; i < numUpdateModules; ++i)
{
long t = modulesToUpdate[i];
@@ -5331,27 +5351,37 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply)
firmwareUpdateModuleMap = 0;
break;
}
- firmwareUpdateModuleMap |= (1 << t);
+ firmwareUpdateModuleMap |= (1 << (unsigned int)t);
}
}
+ else
+ {
+ firmwareUpdateModuleMap = (1 << 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
- reprap.GetHeat()->SwitchOffAll(); // turn all heaters off because the main loop may get suspended
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 are notified
+ if (!DoDwellTime(1.0)) // wait a second so all HTTP clients and PanelDue are notified
{
return false;
}
@@ -5370,7 +5400,6 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply)
if (val != 0)
{
reply.printf("Checksum error on line %d", val);
- //resend = true; // FIXME?
}
}
break;
diff --git a/src/GCodes.h b/src/GCodes.h
index c7f3c5fd..9a89fc56 100644
--- a/src/GCodes.h
+++ b/src/GCodes.h
@@ -78,10 +78,17 @@ struct Trigger
{
TriggerMask rising;
TriggerMask falling;
+ uint8_t condition;
void Init()
{
rising = falling = 0;
+ condition = 0;
+ }
+
+ bool IsUnused() const
+ {
+ return rising == 0 && falling == 0;
}
};
@@ -257,7 +264,6 @@ private:
uint32_t auxSeq; // Sequence number for AUX devices
float simulationTime; // Accumulated simulation time
uint8_t simulationMode; // 0 = not simulating, 1 = simulating, >1 are simulation modes for debugging
- bool isFlashing; // Is a new firmware binary going to be flashed?
FilePosition filePos; // The position we got up to in the file being printed
// Firmware retraction settings
@@ -273,6 +279,7 @@ private:
// Firmware update
uint8_t firmwareUpdateModuleMap; // Bitmap of firmware modules to be updated
+ bool isFlashing; // Is a new firmware binary going to be flashed?
};
//*****************************************************************************************************
diff --git a/src/Heat.cpp b/src/Heat.cpp
index f7de96c1..b4060b59 100644
--- a/src/Heat.cpp
+++ b/src/Heat.cpp
@@ -71,12 +71,12 @@ void Heat::Spin()
void Heat::Diagnostics(MessageType mtype)
{
- platform->Message(mtype, "Heat Diagnostics:\n");
+ platform->MessageF(mtype, "Heat Diagnostics:\nBed heater = %d, chamber heater = %d\n", bedHeater, chamberHeater);
for (size_t heater=0; heater < HEATERS; heater++)
{
if (pids[heater]->Active())
{
- platform->MessageF(mtype, "Heater %d: I-accumulator = %.1f\n", heater, pids[heater]->GetAccumulator());
+ platform->MessageF(mtype, "Heater %d is on, I-accum = %.1f\n", heater, pids[heater]->GetAccumulator());
}
}
}
@@ -231,8 +231,7 @@ void PID::Init()
averagePWM = 0.0;
// Time the sensor was last sampled. During startup, we use the current
- // time as the initial value so as to not trigger an immediate warning from
- // the Tick ISR.
+ // time as the initial value so as to not trigger an immediate warning from the Tick ISR.
lastSampleTime = millis();
}
@@ -240,7 +239,7 @@ void PID::SwitchOn()
{
if (reprap.Debug(Module::moduleHeat))
{
- platform->MessageF(GENERIC_MESSAGE, "Heater %d switched on.\n", heater);
+ platform->MessageF(GENERIC_MESSAGE, "Heater %d %s\n", heater, (temperatureFault) ? "not switched on due to temperature fault" : "switched on");
}
switchedOff = temperatureFault;
}
@@ -474,6 +473,10 @@ void PID::SwitchOff()
active = false;
switchedOff = true;
heatingUp = false;
+ if (reprap.Debug(Module::moduleHeat))
+ {
+ platform->MessageF(GENERIC_MESSAGE, "Heater %d switched off", heater);
+ }
}
float PID::GetAveragePWM() const
diff --git a/src/Platform.cpp b/src/Platform.cpp
index 437b4ff8..d5083261 100644
--- a/src/Platform.cpp
+++ b/src/Platform.cpp
@@ -1,6 +1,6 @@
/****************************************************************************************************
- RepRapFirmware - Platform: RepRapPro Ormerod with Arduino Due controller
+ RepRapFirmware - Platform: RepRapPro Ormerod with Duet controller
Platform contains all the code and definitions to deal with machine-dependent things such as control
pins, bed area, number of extruders, tolerable accelerations and speeds and so on.
@@ -25,7 +25,7 @@
#include "sam/drivers/tc/tc.h"
#include "sam/drivers/hsmci/hsmci.h"
-#ifdef EXTERNAL_DRIVERS
+#if defined(DUET_NG) || defined (EXTERNAL_DRIVERS)
# include "ExternalDrivers.h"
#endif
@@ -185,8 +185,10 @@ void Platform::Init()
fileStructureInitialised = true;
+#if !defined(DUET_NG) || defined(PROTOTYPE_1)
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
@@ -209,11 +211,15 @@ void Platform::Init()
ARRAY_INIT(accelerations, ACCELERATIONS);
ARRAY_INIT(driveStepsPerUnit, DRIVE_STEPS_PER_UNIT);
ARRAY_INIT(instantDvs, INSTANT_DVS);
+
+#if !defined(DUET_NG) || defined(PROTOTYPE_1)
ARRAY_INIT(potWipes, POT_WIPES);
senseResistor = SENSE_RESISTOR;
maxStepperDigipotVoltage = MAX_STEPPER_DIGIPOT_VOLTAGE;
stepperDacVoltageRange = STEPPER_DAC_VOLTAGE_RANGE;
stepperDacVoltageOffset = STEPPER_DAC_VOLTAGE_OFFSET;
+#endif
+
maxAverageAcceleration = 10000.0; // high enough to have no effect until it is changed
// Z PROBE
@@ -264,14 +270,17 @@ void Platform::Init()
{
pinMode(directionPins[drive], OUTPUT);
}
-#ifdef EXTERNAL_DRIVERS
+
+#if !defined(DUET_NG) || defined(PROTOTYPE_1)
+# ifdef EXTERNAL_DRIVERS
if (drive < FIRST_EXTERNAL_DRIVE && enablePins[drive] >= 0)
-#else
+# else
if (enablePins[drive] >= 0)
-#endif
+# endif
{
pinMode(enablePins[drive], OUTPUT);
}
+#endif
if (endStopPins[drive] >= 0)
{
pinMode(endStopPins[drive], INPUT_PULLUP);
@@ -928,6 +937,19 @@ void Platform::Beep(int freq, int ms)
MessageF(AUX_MESSAGE, "{\"beep_freq\":%d,\"beep_length\":%d}\n", freq, ms);
}
+// Send a short message to the aux channel. There is no flow control on this port, so it can't block for long.
+void Platform::SendMessage(const char* msg)
+{
+ OutputBuffer *buf;
+ if (OutputBuffer::Allocate(buf))
+ {
+ buf->copy("{\"message\":");
+ buf->EncodeString(msg, strlen(msg), false, true);
+ buf->cat("}\n");
+ Message(AUX_MESSAGE, buf);
+ }
+}
+
// Note: the use of floating point time will cause the resolution to degrade over time.
// For example, 1ms time resolution will only be available for about half an hour from startup.
// Personally, I (dc42) would rather just maintain and provide the time in milliseconds in a uint32_t.
@@ -1316,13 +1338,15 @@ void Platform::Diagnostics(MessageType mtype)
}
MessageF(mtype, "Free file entries: %u\n", numFreeFiles);
- // Show the longest write time
- MessageF(mtype, "Longest block write time: %.1fms\n", FileStore::GetAndClearLongestWriteTime());
-
// Show the HSMCI speed
- MessageF(mtype, "SD card speed: %.1fMHz\n", (float)hsmci_get_speed()/1000000.0);
+ MessageF(mtype, "SD card interface speed: %.1fMBytes/sec\n", (float)hsmci_get_speed()/1000000.0);
+
+ // Show the longest SD card write time
+ MessageF(mtype, "SD card longest block write time: %.1fms\n", FileStore::GetAndClearLongestWriteTime());
// Debug
+//MessageF(mtype, "TC_FMR = %08x, PWM_FPE = %08x, PWM_FSR = %08x\n", TC2->TC_FMR, PWM->PWM_FPE, PWM->PWM_FSR);
+//MessageF(mtype, "PWM2 period %08x, duty %08x\n", PWM->PWM_CH_NUM[2].PWM_CPRD, PWM->PWM_CH_NUM[2].PWM_CDTY);
//MessageF(mtype, "Shortest/longest times read %.1f/%.1f write %.1f/%.1f ms, %u/%u\n",
// (float)shortestReadWaitTime/1000, (float)longestReadWaitTime/1000, (float)shortestWriteWaitTime/1000, (float)longestWriteWaitTime/1000,
// maxRead, maxWrite);
@@ -1660,21 +1684,25 @@ void Platform::EnableDrive(size_t drive)
{
UpdateMotorCurrent(driver); // the current may have been reduced by the idle timeout
-#ifdef EXTERNAL_DRIVERS
+#if defined(DUET_NG) && !defined(PROTOTYPE_1)
+ ExternalDrivers::EnableDrive(driver, true);
+#else
+# ifdef EXTERNAL_DRIVERS
if (driver >= FIRST_EXTERNAL_DRIVE)
{
ExternalDrivers::EnableDrive(driver - FIRST_EXTERNAL_DRIVE, true);
}
else
{
-#endif
+# endif
const int pin = enablePins[driver];
if (pin >= 0)
{
digitalWrite(pin, enableValues[driver]);
}
-#ifdef EXTERNAL_DRIVERS
+# ifdef EXTERNAL_DRIVERS
}
+# endif
#endif
}
}
@@ -1685,23 +1713,27 @@ void Platform::DisableDrive(size_t drive)
{
if (drive < DRIVES)
{
+#if defined(DUET_NG) && !defined(PROTOTYPE_1)
+ ExternalDrivers::EnableDrive(driverNumbers[drive], false);
+#else
const size_t driver = driverNumbers[drive];
-#ifdef EXTERNAL_DRIVERS
+# ifdef EXTERNAL_DRIVERS
if (driver >= FIRST_EXTERNAL_DRIVE)
{
ExternalDrivers::EnableDrive(driver - FIRST_EXTERNAL_DRIVE, false);
}
else
{
-#endif
+# endif
const int pin = enablePins[driver];
if (pin >= 0)
{
digitalWrite(pin, !enableValues[driver]);
}
driveState[drive] = DriveStatus::disabled;
-#ifdef EXTERNAL_DRIVERS
+# ifdef EXTERNAL_DRIVERS
}
+# endif
#endif
}
}
@@ -1741,14 +1773,19 @@ void Platform::UpdateMotorCurrent(size_t drive)
current *= idleCurrentFactor;
}
const size_t driver = driverNumbers[drive];
-#ifdef EXTERNAL_DRIVERS
+
+#if defined(DUET_NG) && !defined(PROTOTYPE_1)
+ ExternalDrivers::SetCurrent(driver, current);
+#else
+
+# ifdef EXTERNAL_DRIVERS
if (driver >= FIRST_EXTERNAL_DRIVE)
{
ExternalDrivers::SetCurrent(driver - FIRST_EXTERNAL_DRIVE, current);
}
else
{
-#endif
+# endif
unsigned short pot = (unsigned short)((0.256*current*8.0*senseResistor + maxStepperDigipotVoltage/2)/maxStepperDigipotVoltage);
if (driver < 4)
{
@@ -1757,37 +1794,39 @@ void Platform::UpdateMotorCurrent(size_t drive)
}
else
{
-#ifndef DUET_NG
+# ifndef DUET_NG
if (board == BoardType::Duet_085)
{
-#endif
+# endif
// Extruder 0 is on DAC channel 0
if (driver == 4)
{
float dacVoltage = max<float>(current * 0.008*senseResistor + stepperDacVoltageOffset, 0.0); // the voltage we want from the DAC relative to its minimum
uint32_t dac = (uint32_t)((256 * dacVoltage + 0.5 * stepperDacVoltageRange)/stepperDacVoltageRange);
-#ifdef DUET_NG
+# ifdef DUET_NG
AnalogWrite(DAC1, dac);
-#else
+# else
AnalogWrite(DAC0, dac);
-#endif
+# endif
}
else
{
mcpExpansion.setNonVolatileWiper(potWipes[driver-1], pot);
mcpExpansion.setVolatileWiper(potWipes[driver-1], pot);
}
-#ifndef DUET_NG
+# ifndef DUET_NG
}
else if (driver < 8) // on a Duet 0.6 we have a maximum of 8 drives
{
mcpExpansion.setNonVolatileWiper(potWipes[driver], pot);
mcpExpansion.setVolatileWiper(potWipes[driver], pot);
}
-#endif
+# endif
}
-#ifdef EXTERNAL_DRIVERS
+# ifdef EXTERNAL_DRIVERS
}
+# endif
+
#endif
}
}
@@ -1815,7 +1854,10 @@ bool Platform::SetMicrostepping(size_t drive, int microsteps, int mode)
{
if (drive < DRIVES)
{
-#ifdef EXTERNAL_DRIVERS
+#if defined(DUET_NG) && !defined(PROTOTYPE_1)
+ return ExternalDrivers::SetMicrostepping(driverNumbers[drive], microsteps, mode);
+#else
+# ifdef EXTERNAL_DRIVERS
const size_t driver = driverNumbers[drive];
if (driver >= FIRST_EXTERNAL_DRIVE)
{
@@ -1823,12 +1865,14 @@ bool Platform::SetMicrostepping(size_t drive, int microsteps, int mode)
}
else
{
-#endif
+# endif
// On-board drivers only support x16 microstepping.
// We ignore the interpolation on/off parameter so that e.g. M350 I1 E16:128 won't give an error if E1 supports interpolation but E0 doesn't.
return microsteps == 16;
-#ifdef EXTERNAL_DRIVERS
+# ifdef EXTERNAL_DRIVERS
}
+# endif
+
#endif
}
return false;
@@ -1836,7 +1880,13 @@ bool Platform::SetMicrostepping(size_t drive, int microsteps, int mode)
unsigned int Platform::GetMicrostepping(size_t drive, bool& interpolation) const
{
-#ifdef EXTERNAL_DRIVERS
+#if defined(DUET_NG) && !defined(PROTOTYPE_1)
+ if (drive < DRIVES)
+ {
+ return ExternalDrivers::GetMicrostepping(driverNumbers[drive], interpolation);
+ }
+# else
+# ifdef EXTERNAL_DRIVERS
if (drive < DRIVES)
{
const size_t driver = driverNumbers[drive];
@@ -1845,8 +1895,8 @@ unsigned int Platform::GetMicrostepping(size_t drive, bool& interpolation) const
return ExternalDrivers::GetMicrostepping(driver - FIRST_EXTERNAL_DRIVE, interpolation);
}
}
+# endif
#endif
-
// On-board drivers only support x16 microstepping without interpolation
interpolation = false;
return 16;
@@ -1937,10 +1987,11 @@ void Platform::InitFans()
for (size_t i = 0; i < NUM_FANS; ++i)
{
fans[i].Init(COOLING_FAN_PINS[i],
- !HEAT_ON
-#ifndef DUET_NG
- // The cooling fan 0 output pin gets inverted if HEAT_ON == 0 on a Duet 0.4, 0.6 or 0.7
- && (board == BoardType::Duet_06 || board == BoardType::Duet_07)
+#ifdef DUET_NG
+ false
+#else
+ // The cooling fan output pin gets inverted if HEAT_ON == 0 on a Duet 0.6 or 0.7
+ !HEAT_ON && (board == BoardType::Duet_06 || board == BoardType::Duet_07)
#endif
);
}
@@ -1949,6 +2000,7 @@ void Platform::InitFans()
{
// Set fan 1 to be thermostatic by default, monitoring all heaters except the default bed heater
fans[1].SetHeatersMonitored(0xFFFF & ~(1 << BED_HEATER));
+ fans[1].SetValue(1.0); // set it full on
}
coolingFanRpmPin = COOLING_FAN_RPM_PIN;
@@ -2060,7 +2112,9 @@ void Platform::Fan::Check()
{
if (heatersMonitored != 0)
{
- val = (reprap.GetPlatform()->AnyHeaterHot(heatersMonitored, triggerTemperature)) ? 1.0 : 0.0;
+ val = (reprap.GetPlatform()->AnyHeaterHot(heatersMonitored, triggerTemperature))
+ ? max<float>(0.5, val) // make sure that thermostatic fans always run at 50% speed or more
+ : 0.0;
Refresh();
}
}
@@ -2198,18 +2252,8 @@ void Platform::Message(MessageType type, const char *message)
break;
case FIRMWARE_UPDATE_MESSAGE:
- Message(HOST_MESSAGE, message);
- // Send an alert message to the aux port
- {
- OutputBuffer *buf;
- if (OutputBuffer::Allocate(buf, false))
- {
- buf->cat("{\"alert\":");
- buf->EncodeString(message, strlen(message), true, true);
- buf->cat("}\n");
- Message(AUX_MESSAGE, buf);
- }
- }
+ Message(HOST_MESSAGE, message); // send message to USB
+ SendMessage(message); // send message to aux
break;
case GENERIC_MESSAGE:
@@ -2287,7 +2331,7 @@ void Platform::Message(const MessageType type, OutputBuffer *buffer)
break;
case FIRMWARE_UPDATE_MESSAGE:
- // We don't generate any of these with an OutputBuffer argument, but if we get one, just send it to USB
+ // We don't generate any of these with an OutputBuffer argument, but if do we get one, just send it to USB
Message(HOST_MESSAGE, buffer);
break;
@@ -2416,7 +2460,11 @@ void Platform::SetBoardType(BoardType bt)
if (bt == BoardType::Auto)
{
#ifdef DUET_NG
- board = BoardType::DuetNG_08;
+# ifdef PROTOTYPE_1
+ board = BoardType::DuetNG_06;
+# else
+ board = BoardType::DuetNG_10;
+# endif
#else
// Determine whether this is a Duet 0.6 or a Duet 0.8.5 board.
// If it is a 0.85 board then DAC0 (AKA digital pin 67) is connected to ground via a diode and a 2.15K resistor.
@@ -2445,7 +2493,11 @@ const char* Platform::GetElectronicsString() const
switch (board)
{
#ifdef DUET_NG
- case BoardType::DuetNG_08: return "DuetNG 0.6";
+# ifdef PROTOTYPE_1
+ case BoardType::DuetNG_06: return "DuetNG 0.6";
+# else
+ case BoardType::DuetNG_10: return "DuetNG 1.0";
+# endif
#else
case BoardType::Duet_06: return "Duet 0.6";
case BoardType::Duet_07: return "Duet 0.7";
@@ -2459,18 +2511,26 @@ const char* Platform::GetElectronicsString() const
// Set the specified pin to the specified output level. Return true if success, false if not allowed.
bool Platform::SetPin(int pin, int level)
{
- if (pin >= 0 && (unsigned int)pin < NUM_PINS_ALLOWED && (level == 0 || level == 1))
+ if (pin >= 0 && (unsigned int)pin < NUM_PINS_ALLOWED && (level >= 0 || level <= 255))
{
const size_t index = (unsigned int)pin/8;
const uint8_t mask = 1 << ((unsigned int)pin & 7);
if ((pinAccessAllowed[index] & mask) != 0)
{
+#ifdef DUET_NG //TODO temporary
+ if (level == 1)
+ {
+ level = 255;
+ }
+ AnalogWrite(pin, level, 1000);
+#else
if ((pinInitialised[index] & mask) == 0)
{
pinMode(pin, OUTPUT);
pinInitialised[index] |= mask;
}
digitalWrite(pin, level);
+#endif
return true;
}
}
diff --git a/src/Platform.h b/src/Platform.h
index 67a40182..e8107bcb 100644
--- a/src/Platform.h
+++ b/src/Platform.h
@@ -49,7 +49,11 @@ Licence: GPL
#include "Core.h"
#include "OutputMemory.h"
#include "ff.h"
-#include "MCP4461.h"
+
+#if !defined(DUET_NG) || defined(PROTOTYPE_1)
+# include "MCP4461.h"
+#endif
+
#include "MassStorage.h"
#include "FileStore.h"
#include "MessageType.h"
@@ -178,7 +182,11 @@ enum class BoardType : uint8_t
{
Auto = 0,
#ifdef DUET_NG
- DuetNG_08 = 1
+# ifdef PROTOTYPE_1
+ DuetNG_06 = 1
+# else
+ DuetNG_10 = 1
+# endif
#else
Duet_06 = 1,
Duet_07 = 2,
@@ -591,6 +599,7 @@ public:
// AUX device
void Beep(int freq, int ms);
+ void SendMessage(const char* msg);
// Hotend configuration
float GetFilamentWidth() const;
@@ -719,12 +728,14 @@ private:
// Digipots
+#if !defined(DUET_NG) || defined(PROTOTYPE_1)
MCP4461 mcpDuet;
MCP4461 mcpExpansion;
Pin potWipes[8]; // we have only 8 digipots, on the Duet 0.8.5 we use the DAC for the 9th
float senseResistor;
float maxStepperDigipotVoltage;
float stepperDacVoltageRange, stepperDacVoltageOffset;
+#endif
// Z probe
diff --git a/src/Reprap.cpp b/src/Reprap.cpp
index afbf8202..9bfd555a 100644
--- a/src/Reprap.cpp
+++ b/src/Reprap.cpp
@@ -562,12 +562,11 @@ OutputBuffer *RepRap::GetStatusResponse(uint8_t type, ResponseSource source)
int toolNumber = (currentTool == nullptr) ? -1 : currentTool->Number();
response->catf("]},\"currentTool\":%d", toolNumber);
- /* Output - only reported once */
+ // Output - only reported once
{
bool sendBeep = (beepDuration != 0 && beepFrequency != 0);
bool sendMessage = (message[0] != 0);
- bool sourceRight = (gCodes->HaveAux() && source == ResponseSource::AUX) || (!gCodes->HaveAux() && source == ResponseSource::HTTP);
- if ((sendBeep || message[0] != 0) && sourceRight)
+ if (sendBeep || sendMessage)
{
response->cat(",\"output\":{");
@@ -579,7 +578,6 @@ OutputBuffer *RepRap::GetStatusResponse(uint8_t type, ResponseSource source)
{
response->cat(",");
}
-
beepFrequency = beepDuration = 0;
}
@@ -600,17 +598,12 @@ OutputBuffer *RepRap::GetStatusResponse(uint8_t type, ResponseSource source)
response->catf(",\"params\":{\"atxPower\":%d", platform->AtxPower() ? 1 : 0);
// Cooling fan value
- response->cat(",\"fanPercent\":[");
+ response->cat(",\"fanPercent\":");
+ ch = '[';
for(size_t i = 0; i < NUM_FANS; i++)
{
- if (i == NUM_FANS - 1)
- {
- response->catf("%.2f", platform->GetFanValue(i) * 100.0);
- }
- else
- {
- response->catf("%.2f,", platform->GetFanValue(i) * 100.0);
- }
+ response->catf("%c%.2f", ch, platform->GetFanValue(i) * 100.0);
+ ch = ',';
}
// Speed and Extrusion factors
@@ -1114,11 +1107,17 @@ OutputBuffer *RepRap::GetLegacyStatusResponse(uint8_t type, int seq)
break;
}
- // Send the fan0 settings (for PanelDue firmware 1.13)
- response->catf(",\"fanPercent\":[%.02f,%.02f]", platform->GetFanValue(0) * 100.0, platform->GetFanValue(1) * 100.0);
+ // Send the fan settings, for PanelDue firmware 1.13 and later
+ response->catf(",\"fanPercent\":");
+ ch = '[';
+ for (size_t i = 0; i < NUM_FANS; ++i)
+ {
+ response->catf("%c%.02f", ch, platform->GetFanValue(i));
+ ch = ',';
+ }
- // Send fan RPM value
- response->catf(",\"fanRPM\":%u", static_cast<unsigned int>(platform->GetFanRPM()));
+ // Send fan RPM value (we only support one)
+ response->catf("],\"fanRPM\":%u", static_cast<unsigned int>(platform->GetFanRPM()));
// Send the home state. To keep the messages short, we send 1 for homed and 0 for not homed, instead of true and false.
response->catf(",\"homed\":[%d,%d,%d]",
@@ -1132,14 +1131,10 @@ OutputBuffer *RepRap::GetLegacyStatusResponse(uint8_t type, int seq)
response->catf(",\"fraction_printed\":%.4f", max<float>(0.0, gCodes->FractionOfFilePrinted()));
}
- response->cat(",\"message\":");
- response->EncodeString(message, ARRAY_SIZE(message), false);
+ // Short messages are now pushed directly to PanelDue, so don't include them here as well
+ // We no longer send the amount of http buffer space here because the web interface doesn't use these formns of status response
- if (type < 2)
- {
- response->catf(",\"buff\":%u", webserver->GetGCodeBufferSpace(WebSource::HTTP)); // send the amount of buffer space available for gcodes
- }
- else if (type == 2)
+ if (type == 2)
{
if (printMonitor->IsPrinting())
{
@@ -1251,25 +1246,29 @@ OutputBuffer *RepRap::GetFilesResponse(const char *dir, bool flagsDirs)
return response;
}
+// Send a beep. We send it to both PanelDue and the web interface.
void RepRap::Beep(int freq, int ms)
{
+ beepFrequency = freq;
+ beepDuration = ms;
+
if (gCodes->HaveAux())
{
// If there is an LCD device present, make it beep
platform->Beep(freq, ms);
}
- else
- {
- // Otherwise queue it until the webserver can process it
- beepFrequency = freq;
- beepDuration = ms;
- }
}
+// Send a short message. We send it to both PanelDue and the web interface.
void RepRap::SetMessage(const char *msg)
{
strncpy(message, msg, MESSAGE_LENGTH);
message[MESSAGE_LENGTH] = 0;
+
+ if (gCodes->HaveAux())
+ {
+ platform->SendMessage(msg);
+ }
}
// Get the status character for the new-style status response
diff --git a/src/TemperatureError.cpp b/src/TemperatureError.cpp
index 182d0755..115d2841 100644
--- a/src/TemperatureError.cpp
+++ b/src/TemperatureError.cpp
@@ -30,15 +30,20 @@ const char* TemperatureErrorString(TemperatureError err)
// and so the heater should just be shut off immediately.
bool IsPermanentError(TemperatureError err)
{
+#if 1
+ return false;
+#else
switch (err)
{
case TemperatureError::success:
case TemperatureError::busBusy:
case TemperatureError::ioError:
+ case TemperatureError::hardwareError: // need this one because it comes up sometimes (Jimustanguitar on seemecnc forum)
return false;
default:
return true;
}
+#endif
}
// End