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:
authorDavid Crocker <dcrocker@eschertech.com>2016-12-25 20:35:33 +0300
committerDavid Crocker <dcrocker@eschertech.com>2016-12-25 20:35:47 +0300
commitad8aa69794b76a081d9c504d5f817b87df39e893 (patch)
tree54c738271cf149a09bfea5f184f27d4755da0b6f
parent81035c9322f35f91831011553eee4bb09edfc737 (diff)
Reduced header file coupling
Restructured the header file inclusion so that the .cpp files are dependent on fewer header files, resulting in less recompilation when most header files are changed
-rw-r--r--.cproject19
-rw-r--r--src/Configuration.h18
-rw-r--r--src/Duet/ConnectionState.cpp37
-rw-r--r--src/Duet/ConnectionState.h36
-rw-r--r--src/Duet/Network.cpp663
-rw-r--r--src/Duet/Network.h244
-rw-r--r--src/Duet/NetworkDefs.h38
-rw-r--r--src/Duet/NetworkTransaction.cpp645
-rw-r--r--src/Duet/NetworkTransaction.h94
-rw-r--r--src/Duet/Pins_Duet.h2
-rw-r--r--src/Duet/Webserver.h382
-rw-r--r--src/DuetNG/DuetEthernet/Network.cpp68
-rw-r--r--src/DuetNG/DuetEthernet/Network.h50
-rw-r--r--src/DuetNG/DuetEthernet/NetworkBuffer.cpp83
-rw-r--r--src/DuetNG/DuetEthernet/NetworkBuffer.h54
-rw-r--r--src/DuetNG/DuetEthernet/NetworkDefs.h35
-rw-r--r--src/DuetNG/DuetEthernet/NetworkTransaction.cpp (renamed from src/DuetNG/DuetEthernet/NetworkTransacrion.cpp)60
-rw-r--r--src/DuetNG/DuetEthernet/NetworkTransaction.h84
-rw-r--r--src/DuetNG/DuetEthernet/Socket.cpp166
-rw-r--r--src/DuetNG/DuetEthernet/Socket.h54
-rw-r--r--src/DuetNG/DuetEthernet/Webserver.cpp2942
-rw-r--r--src/DuetNG/DuetEthernet/Webserver.h384
-rw-r--r--src/DuetNG/DuetEthernet/Wiznet/Ethernet/socketlib.cpp (renamed from src/DuetNG/DuetEthernet/Wiznet/Ethernet/socket.cpp)7
-rw-r--r--src/DuetNG/DuetEthernet/Wiznet/Ethernet/socketlib.h (renamed from src/DuetNG/DuetEthernet/Wiznet/Ethernet/socket.h)2
-rw-r--r--src/DuetNG/DuetEthernet/Wiznet/Internet/DHCP/dhcp.cpp2
-rw-r--r--src/DuetNG/DuetWiFi/Network.cpp8
-rw-r--r--src/DuetNG/DuetWiFi/Network.h6
-rw-r--r--src/DuetNG/DuetWiFi/Webserver.cpp8
-rw-r--r--src/DuetNG/DuetWiFi/Webserver.h3
-rw-r--r--src/DuetNG/DuetWiFi/WifiFirmwareUploader.cpp6
-rw-r--r--src/DuetNG/DuetWiFi/WifiFirmwareUploader.h3
-rw-r--r--src/DuetNG/FirmwareUpdater.cpp4
-rw-r--r--src/DuetNG/Pins_DuetNG.h2
-rw-r--r--src/Fan.cpp4
-rw-r--r--src/Fan.h2
-rw-r--r--src/GCodes/GCodeBuffer.cpp4
-rw-r--r--src/GCodes/GCodeBuffer.h2
-rw-r--r--src/GCodes/GCodeMachineState.h6
-rw-r--r--src/GCodes/GCodes.cpp15
-rw-r--r--src/GCodes/GCodes.h10
-rw-r--r--src/GCodes/GCodes2.cpp11
-rw-r--r--src/Heating/FOPDT.cpp2
-rw-r--r--src/Heating/FOPDT.h2
-rw-r--r--src/Heating/Heat.cpp11
-rw-r--r--src/Heating/Heat.h2
-rw-r--r--src/Heating/Pid.cpp10
-rw-r--r--src/Heating/Pid.h7
-rw-r--r--src/Heating/TemperatureSensor.cpp3
-rw-r--r--src/Heating/TemperatureSensor.h2
-rw-r--r--src/Heating/Thermistor.cpp2
-rw-r--r--src/Heating/Thermistor.h2
-rw-r--r--src/Libraries/Fatfs/fattime_rtc.cpp2
-rw-r--r--src/Movement/DDA.cpp5
-rw-r--r--src/Movement/DDA.h2
-rw-r--r--src/Movement/DeltaParameters.cpp5
-rw-r--r--src/Movement/DeltaParameters.h18
-rw-r--r--src/Movement/DeltaProbe.cpp5
-rw-r--r--src/Movement/DeltaProbe.h2
-rw-r--r--src/Movement/DriveMovement.cpp5
-rw-r--r--src/Movement/DriveMovement.h2
-rw-r--r--src/Movement/Grid.cpp4
-rw-r--r--src/Movement/Grid.h5
-rw-r--r--src/Movement/Move.cpp4
-rw-r--r--src/Movement/Move.h6
-rw-r--r--src/OutputMemory.cpp3
-rw-r--r--src/OutputMemory.h4
-rw-r--r--src/Platform.cpp12
-rw-r--r--src/Platform.h38
-rw-r--r--src/PrintMonitor.cpp8
-rw-r--r--src/PrintMonitor.h2
-rw-r--r--src/RADDS/Network.h16
-rw-r--r--src/RADDS/Pins_radds.h2
-rw-r--r--src/RADDS/Webserver.h6
-rw-r--r--src/RepRapFirmware.cpp4
-rw-r--r--src/RepRapFirmware.h39
-rw-r--r--src/Reprap.cpp13
-rw-r--r--src/Reprap.h3
-rw-r--r--src/Storage/FileStore.cpp1
-rw-r--r--src/Storage/FileStore.h2
-rw-r--r--src/Storage/MassStorage.cpp4
-rw-r--r--src/Storage/MassStorage.h5
-rw-r--r--src/Tool.cpp7
-rw-r--r--src/Tool.h2
-rw-r--r--src/Version.h21
-rw-r--r--src/Webserver/Webserver.cpp (renamed from src/Duet/Webserver.cpp)66
-rw-r--r--src/Webserver/Webserver.h385
86 files changed, 2131 insertions, 4883 deletions
diff --git a/.cproject b/.cproject
index bcac8a4e..a05ce993 100644
--- a/.cproject
+++ b/.cproject
@@ -103,6 +103,7 @@
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/src/Duet}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/src/Duet/Lwip}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/src/Duet/EMAC}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/src/Webserver}&quot;"/>
</option>
<option id="gnu.cpp.compiler.option.preprocessor.def.1548770846" name="Defined symbols (-D)" superClass="gnu.cpp.compiler.option.preprocessor.def" useByScannerDiscovery="false" valueType="definedSymbols">
<listOptionValue builtIn="false" value="__SAM3X8E__"/>
@@ -235,7 +236,7 @@
</toolChain>
</folderInfo>
<sourceEntries>
- <entry excluding="src/RADDS|src/DuetNG/DuetEthernet|src/Duet" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/>
+ <entry excluding="src/Webserver|src/RADDS|src/DuetNG/DuetEthernet|src/Duet" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/>
</sourceEntries>
</configuration>
</storageModule>
@@ -350,7 +351,7 @@
</toolChain>
</folderInfo>
<sourceEntries>
- <entry excluding="src/Duet/Lwip/lwip/src/core/ipv6|src/Duet/Lwip/lwip/test|src/DuetNG|src/Duet" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/>
+ <entry excluding="src/Webserver|src/Duet/Lwip/lwip/src/core/ipv6|src/Duet/Lwip/lwip/test|src/DuetNG|src/Duet" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/>
</sourceEntries>
</configuration>
</storageModule>
@@ -462,6 +463,7 @@
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/src/DuetNG}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/src/DuetNG/DuetEthernet}&quot;"/>
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/src/DuetNG/DuetEthernet/Wiznet/Ethernet}&quot;"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/src/Webserver}&quot;"/>
</option>
<option id="gnu.cpp.compiler.option.preprocessor.def.1120095540" name="Defined symbols (-D)" superClass="gnu.cpp.compiler.option.preprocessor.def" useByScannerDiscovery="false" valueType="definedSymbols">
<listOptionValue builtIn="false" value="__SAM4E8E__"/>
@@ -478,12 +480,19 @@
<toolChain id="cdt.managedbuild.toolchain.gnu.cross.exe.release.1089268166" name="Cross GCC" superClass="cdt.managedbuild.toolchain.gnu.cross.exe.release" unusedChildren="">
<option id="cdt.managedbuild.option.gnu.cross.path.1965306885.313001552" name="Path" superClass="cdt.managedbuild.option.gnu.cross.path.1965306885"/>
<option id="cdt.managedbuild.option.gnu.cross.prefix.444696517.1953934546" name="Prefix" superClass="cdt.managedbuild.option.gnu.cross.prefix.444696517"/>
- <tool id="cdt.managedbuild.tool.gnu.cross.c.compiler.200282872" name="Cross GCC Compiler" superClass="cdt.managedbuild.tool.gnu.cross.c.compiler.1431507147"/>
- <tool id="cdt.managedbuild.tool.gnu.cross.cpp.compiler.1364761638" name="Cross G++ Compiler" superClass="cdt.managedbuild.tool.gnu.cross.cpp.compiler.1806370384"/>
+ <targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="cdt.managedbuild.targetPlatform.gnu.cross" isAbstract="false" osList="all" superClass="cdt.managedbuild.targetPlatform.gnu.cross"/>
+ <tool id="cdt.managedbuild.tool.gnu.cross.c.compiler.200282872" name="Cross GCC Compiler" superClass="cdt.managedbuild.tool.gnu.cross.c.compiler.1431507147">
+ <inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.1696293223" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
+ </tool>
+ <tool id="cdt.managedbuild.tool.gnu.cross.cpp.compiler.1364761638" name="Cross G++ Compiler" superClass="cdt.managedbuild.tool.gnu.cross.cpp.compiler.1806370384">
+ <inputType id="cdt.managedbuild.tool.gnu.cpp.compiler.input.376414925" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.input"/>
+ </tool>
<tool id="cdt.managedbuild.tool.gnu.cross.c.linker.162777510" name="Cross GCC Linker" superClass="cdt.managedbuild.tool.gnu.cross.c.linker.212863977"/>
<tool id="cdt.managedbuild.tool.gnu.cross.cpp.linker.57723374" name="Cross G++ Linker" superClass="cdt.managedbuild.tool.gnu.cross.cpp.linker.394147701"/>
<tool id="cdt.managedbuild.tool.gnu.cross.archiver.863372043" name="Cross GCC Archiver" superClass="cdt.managedbuild.tool.gnu.cross.archiver.136873036"/>
- <tool id="cdt.managedbuild.tool.gnu.cross.assembler.555179118" name="Cross GCC Assembler" superClass="cdt.managedbuild.tool.gnu.cross.assembler.2020291169"/>
+ <tool id="cdt.managedbuild.tool.gnu.cross.assembler.555179118" name="Cross GCC Assembler" superClass="cdt.managedbuild.tool.gnu.cross.assembler.2020291169">
+ <inputType id="cdt.managedbuild.tool.gnu.assembler.input.403264608" superClass="cdt.managedbuild.tool.gnu.assembler.input"/>
+ </tool>
</toolChain>
</folderInfo>
<folderInfo id="cdt.managedbuild.config.gnu.cross.exe.release.516195201.976458850.241502451.1275216290.918081093" name="/" resourcePath="src/DuetNG/DuetEthernet/Ethernet3/examples">
diff --git a/src/Configuration.h b/src/Configuration.h
index ff706cbe..a2475a36 100644
--- a/src/Configuration.h
+++ b/src/Configuration.h
@@ -25,20 +25,6 @@ Licence: GPL
#include <cstddef> // for size_t
-// Firmware name is now defined in the Pins file
-
-#ifndef VERSION
-# define VERSION "1.17"
-#endif
-
-#ifndef DATE
-# define DATE "2016-12-24"
-#endif
-
-#define AUTHORS "reprappro, dc42, zpl, t3p3, dnewman"
-
-#define FLASH_SAVE_ENABLED (1)
-
// Other firmware that we might switch to be compatible with.
enum Compatibility
@@ -95,6 +81,10 @@ const float DefaultHotEndHeaterGain = 340.0;
const float DefaultHotEndHeaterTimeConstant = 140.0;
const float DefaultHotEndHeaterDeadTime = 5.5;
+const int8_t DefaultBedHeater = 0;
+const int8_t DefaultChamberHeater = -1;
+const int8_t DefaultE0Heater = 1; // Index of the default first extruder heater
+
// These parameters are about right for a typical PCB bed heater that maxes out at 110C
const float DefaultBedHeaterGain = 90.0;
const float DefaultBedHeaterTimeConstant = 700.0;
diff --git a/src/Duet/ConnectionState.cpp b/src/Duet/ConnectionState.cpp
new file mode 100644
index 00000000..181682c3
--- /dev/null
+++ b/src/Duet/ConnectionState.cpp
@@ -0,0 +1,37 @@
+/*
+ * ConnectionState.cpp
+ *
+ * Created on: 25 Dec 2016
+ * Author: David
+ */
+
+#include "ConnectionState.h"
+
+extern "C" {
+ #include "lwip/src/include/lwip/tcp.h"
+}
+
+//***************************************************************************************************
+// ConnectionState class
+
+void ConnectionState::Init(tcp_pcb *p)
+{
+ pcb = p;
+ localPort = p->local_port;
+ remoteIPAddress = p->remote_ip.addr;
+ remotePort = p->remote_port;
+ next = nullptr;
+ sendingTransaction = nullptr;
+ persistConnection = true;
+ isTerminated = false;
+}
+
+void ConnectionState::Terminate()
+{
+ if (pcb != nullptr)
+ {
+ tcp_abort(pcb);
+ }
+}
+
+// End
diff --git a/src/Duet/ConnectionState.h b/src/Duet/ConnectionState.h
new file mode 100644
index 00000000..e3b448d0
--- /dev/null
+++ b/src/Duet/ConnectionState.h
@@ -0,0 +1,36 @@
+/*
+ * ConnectionState.h
+ *
+ * Created on: 25 Dec 2016
+ * Author: David
+ */
+
+#ifndef SRC_DUET_CONNECTIONSTATE_H_
+#define SRC_DUET_CONNECTIONSTATE_H_
+
+#include <NetworkDefs.h>
+#include "RepRapFirmware.h"
+
+class tcp_pcb;
+
+// ConnectionState structure that we use to track TCP connections. It is usually combined with NetworkTransactions.
+struct ConnectionState
+{
+ tcp_pcb *volatile pcb; // Connection PCB
+ uint16_t localPort, remotePort; // Copy of the local and remote ports, because the PCB may be unavailable
+ uint32_t remoteIPAddress; // Same for the remote IP address
+ NetworkTransaction * volatile sendingTransaction; // NetworkTransaction that is currently sending via this connection
+ ConnectionState * volatile next; // Next ConnectionState in this list
+ bool persistConnection; // Do we expect this connection to stay alive?
+ volatile bool isTerminated; // Will be true if the connection has gone down unexpectedly (TCP RST)
+
+ void Init(tcp_pcb *p);
+ uint16_t GetLocalPort() const { return localPort; }
+ uint32_t GetRemoteIP() const { return remoteIPAddress; }
+ uint16_t GetRemotePort() const { return remotePort; }
+ bool IsConnected() const { return pcb != nullptr; }
+ bool IsTerminated() const { return isTerminated; }
+ void Terminate();
+};
+
+#endif /* SRC_DUET_CONNECTIONSTATE_H_ */
diff --git a/src/Duet/Network.cpp b/src/Duet/Network.cpp
index ad2fdb8b..90cbeb35 100644
--- a/src/Duet/Network.cpp
+++ b/src/Duet/Network.cpp
@@ -38,7 +38,14 @@
****************************************************************************************************/
-#include "RepRapFirmware.h"
+#include "Network.h"
+
+#include "ConnectionState.h"
+#include "NetworkTransaction.h"
+#include "Platform.h"
+#include "RepRap.h"
+#include "Webserver.h"
+#include "Version.h"
extern "C"
{
@@ -94,13 +101,13 @@ static const char *mdns_txt_records[] = {
static bool closingDataPort = false;
-static ConnectionState *sendingConnection = nullptr;
+ConnectionState *sendingConnection = nullptr;
static uint32_t sendingWindow32[(TCP_WND + 3)/4]; // should be 32-bit aligned for efficiency
-static char * const sendingWindow = reinterpret_cast<char *>(sendingWindow32);
-static uint16_t sendingWindowSize, sentDataOutstanding;
-static uint8_t sendingRetries;
-static err_t writeResult, outputResult;
+char * const sendingWindow = reinterpret_cast<char *>(sendingWindow32);
+uint16_t sendingWindowSize, sentDataOutstanding;
+uint8_t sendingRetries;
+err_t writeResult, outputResult;
static uint16_t httpPort = DefaultHttpPort;
@@ -176,83 +183,6 @@ static void conn_err(void *arg, err_t err)
}
}
-static err_t conn_poll(void *arg, tcp_pcb *pcb)
-{
- ConnectionState *cs = (ConnectionState*)arg;
- if (cs == sendingConnection)
- {
- // Data could not be sent last time, check if the connection has to be timed out
- sendingRetries++;
- if (sendingRetries == TCP_MAX_SEND_RETRIES)
- {
- reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: Could not transmit data after %.1f seconds\n", (float)TCP_WRITE_TIMEOUT / 1000.0);
- tcp_abort(pcb);
- return ERR_ABRT;
- }
-
- // Try to write the remaining data once again (if required)
- if (writeResult != ERR_OK)
- {
- writeResult = tcp_write(pcb, sendingWindow + (sendingWindowSize - sentDataOutstanding), sentDataOutstanding, 0);
- if (ERR_IS_FATAL(writeResult))
- {
- reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: Failed to write data in conn_poll (code %d)\n", writeResult);
- tcp_abort(pcb);
- return ERR_ABRT;
- }
-
- if (writeResult != ERR_OK && reprap.Debug(moduleNetwork))
- {
- reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: tcp_write resulted in error code %d\n", writeResult);
- }
- }
-
- // If that worked, try to output the remaining data (if required)
- if (outputResult != ERR_OK)
- {
- outputResult = tcp_output(pcb);
- if (ERR_IS_FATAL(outputResult))
- {
- reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: Failed to output data in conn_poll (code %d)\n", outputResult);
- tcp_abort(pcb);
- return ERR_ABRT;
- }
-
- if (outputResult != ERR_OK && reprap.Debug(moduleNetwork))
- {
- reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: tcp_output resulted in error code %d\n", outputResult);
- }
- }
- }
- else
- {
- reprap.GetPlatform()->Message(HOST_MESSAGE, "Network: Mismatched pcb in conn_poll!\n");
- }
- return ERR_OK;
-}
-
-static err_t conn_sent(void *arg, tcp_pcb *pcb, u16_t len)
-{
- ConnectionState *cs = (ConnectionState*)arg;
- if (cs == sendingConnection)
- {
- if (sentDataOutstanding > len)
- {
- sentDataOutstanding -= len;
- }
- else
- {
- tcp_poll(pcb, nullptr, TCP_WRITE_TIMEOUT / TCP_SLOW_INTERVAL / TCP_MAX_SEND_RETRIES);
- sendingConnection = nullptr;
- }
- }
- else
- {
- reprap.GetPlatform()->Message(HOST_MESSAGE, "Network: Mismatched pcb in conn_sent!\n");
- }
- return ERR_OK;
-}
-
static err_t conn_recv(void *arg, tcp_pcb *pcb, pbuf *p, err_t err)
{
ConnectionState *cs = (ConnectionState*)arg;
@@ -1002,566 +932,11 @@ bool Network::AcquireTransaction(ConnectionState *cs)
return true;
}
-//***************************************************************************************************
-
-// ConnectionState class
-
-void ConnectionState::Init(tcp_pcb *p)
-{
- pcb = p;
- localPort = p->local_port;
- remoteIPAddress = p->remote_ip.addr;
- remotePort = p->remote_port;
- next = nullptr;
- sendingTransaction = nullptr;
- persistConnection = true;
- isTerminated = false;
-}
-
-void ConnectionState::Terminate()
-{
- if (pcb != nullptr)
- {
- tcp_abort(pcb);
- }
-}
-
-//***************************************************************************************************
-// NetworkTransaction class
-
-NetworkTransaction::NetworkTransaction(NetworkTransaction *n) : next(n), status(released)
-{
- sendStack = new OutputStack();
-}
-
-void NetworkTransaction::Set(pbuf *p, ConnectionState *c, TransactionStatus s)
-{
- cs = c;
- pb = readingPb = p;
- status = s;
- inputPointer = 0;
- sendBuffer = nullptr;
- fileBeingSent = nullptr;
- closeRequested = false;
- nextWrite = nullptr;
- dataAcknowledged = false;
-}
-
-// Read one char from the NetworkTransaction
-bool NetworkTransaction::Read(char& b)
-{
- if (readingPb == nullptr)
- {
- b = 0;
- return false;
- }
-
- b = ((const char*)readingPb->payload)[inputPointer++];
- if (inputPointer == readingPb->len)
- {
- readingPb = readingPb->next;
- inputPointer = 0;
- }
- return true;
-}
-
-// Read data from the NetworkTransaction and return true on success
-bool NetworkTransaction::ReadBuffer(const char *&buffer, size_t &len)
-{
- if (readingPb == nullptr)
- {
- return false;
- }
-
- if (inputPointer >= readingPb->len)
- {
- readingPb = readingPb->next;
- inputPointer = 0;
- if (readingPb == nullptr)
- {
- return false;
- }
- }
-
- buffer = (const char*)readingPb->payload + inputPointer;
- len = readingPb->len - inputPointer;
- readingPb = readingPb->next;
- inputPointer = 0;
- return true;
-}
-
-void NetworkTransaction::Write(char b)
-{
- if (CanWrite())
- {
- if (sendBuffer == nullptr && !OutputBuffer::Allocate(sendBuffer))
- {
- // Should never get here
- return;
- }
- sendBuffer->cat(b);
- }
-}
-
-void NetworkTransaction::Write(const char* s)
-{
- if (CanWrite())
- {
- if (sendBuffer == nullptr && !OutputBuffer::Allocate(sendBuffer))
- {
- // Should never get here
- return;
- }
- sendBuffer->cat(s);
- }
-}
-
-void NetworkTransaction::Write(StringRef ref)
-{
- Write(ref.Pointer(), ref.strlen());
-}
-
-void NetworkTransaction::Write(const char* s, size_t len)
-{
- if (CanWrite())
- {
- if (sendBuffer == nullptr && !OutputBuffer::Allocate(sendBuffer))
- {
- // Should never get here
- return;
- }
- sendBuffer->cat(s, len);
- }
-}
-
-void NetworkTransaction::Write(OutputBuffer *buffer)
-{
- if (CanWrite())
- {
- // Note we use an individual stack here, because we don't want to link different
- // OutputBuffers for different destinations together...
- sendStack->Push(buffer);
- }
- else
- {
- // Don't keep buffers we can't send...
- OutputBuffer::ReleaseAll(buffer);
- }
-}
-
-void NetworkTransaction::Write(OutputStack *stack)
-{
- if (stack != nullptr)
- {
- if (CanWrite())
- {
- sendStack->Append(stack);
- }
- else
- {
- stack->ReleaseAll();
- }
- }
-}
-
-void NetworkTransaction::Printf(const char* fmt, ...)
-{
- if (CanWrite() && (sendBuffer != nullptr || OutputBuffer::Allocate(sendBuffer)))
- {
- va_list p;
- va_start(p, fmt);
- sendBuffer->vprintf(fmt, p);
- va_end(p);
- }
-}
-
-void NetworkTransaction::SetFileToWrite(FileStore *file)
-{
- if (CanWrite())
- {
- fileBeingSent = file;
- }
- else if (file != nullptr)
- {
- file->Close();
- }
-}
-
-// Send exactly one TCP window of data and return true when this transaction can be released
-bool NetworkTransaction::Send()
-{
- // Free up this transaction if the connection is supposed to be closed
- if (closeRequested)
- {
- reprap.GetNetwork()->ConnectionClosed(cs, true); // This will release the transaction too
- return false;
- }
-
- // Fill up the TCP window with some data chunks from our OutputBuffer instances
- size_t bytesBeingSent = 0, bytesLeftToSend = TCP_WND;
- while (sendBuffer != nullptr && bytesLeftToSend > 0)
- {
- size_t copyLength = min<size_t>(bytesLeftToSend, sendBuffer->BytesLeft());
- memcpy(sendingWindow + bytesBeingSent, sendBuffer->Read(copyLength), copyLength);
- bytesBeingSent += copyLength;
- bytesLeftToSend -= copyLength;
-
- if (sendBuffer->BytesLeft() == 0)
- {
- sendBuffer = OutputBuffer::Release(sendBuffer);
- if (sendBuffer == nullptr)
- {
- sendBuffer = sendStack->Pop();
- }
- }
- }
-
- // We also intend to send a file, so check if we can fill up the TCP window
- if (sendBuffer == nullptr && bytesLeftToSend != 0 && fileBeingSent != nullptr)
- {
- // For HSMCI efficiency, read from the file in multiples of 4 bytes except at the end.
- // This ensures that the second and subsequent chunks can be DMA'd directly into sendingWindow.
- size_t bytesToRead = bytesLeftToSend & (~3);
- if (bytesToRead != 0)
- {
- int bytesRead = fileBeingSent->Read(sendingWindow + bytesBeingSent, bytesToRead);
- if (bytesRead > 0)
- {
- bytesBeingSent += bytesRead;
- }
-
- if (bytesRead != (int)bytesToRead)
- {
- fileBeingSent->Close();
- fileBeingSent = nullptr;
- }
- }
- }
-
- if (bytesBeingSent == 0)
- {
- // If we have no data to send, this connection can be closed next time
- if (!cs->persistConnection && nextWrite == nullptr)
- {
- Close();
- return false;
- }
-
- // We want to send data from another transaction as well, so only free up this one
- cs->sendingTransaction = nextWrite;
- return true;
- }
-
- // The TCP window has been filled up as much as possible, so send it now. There is no need to check
- // the available space in the SNDBUF queue, because we really write only one TCP window at once.
- writeResult = tcp_write(cs->pcb, sendingWindow, bytesBeingSent, 0);
- if (ERR_IS_FATAL(writeResult))
- {
- reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: Failed to write data in Send (code %d)\n", writeResult);
- tcp_abort(cs->pcb);
- return false;
- }
-
- outputResult = tcp_output(cs->pcb);
- if (ERR_IS_FATAL(outputResult))
- {
- reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: Failed to output data in Send (code %d)\n", outputResult);
- tcp_abort(cs->pcb);
- return false;
- }
-
- if (outputResult != ERR_OK && reprap.Debug(moduleNetwork))
- {
- reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: tcp_output resulted in error code %d\n", outputResult);
- }
-
- // Set LwIP callbacks for ACK and retransmission handling
- tcp_poll(cs->pcb, conn_poll, TCP_WRITE_TIMEOUT / TCP_SLOW_INTERVAL / TCP_MAX_SEND_RETRIES);
- tcp_sent(cs->pcb, conn_sent);
-
- // Set all values for the send process
- sendingConnection = cs;
- sendingRetries = 0;
- sendingWindowSize = sentDataOutstanding = bytesBeingSent;
- return false;
-}
-
-// This is called by the Webserver to send output data to a client. If keepConnectionAlive is set to false,
-// the current connection will be terminated once everything has been sent.
-void NetworkTransaction::Commit(bool keepConnectionAlive)
-{
- // If the connection has been terminated (e.g. RST received while writing upload data), discard this transaction
- if (!IsConnected() || status == released)
- {
- Discard();
- return;
- }
-
- // Free buffer holding the incoming data and prepare some values for the sending process
- FreePbuf();
- cs->persistConnection = keepConnectionAlive;
- if (sendBuffer == nullptr)
- {
- sendBuffer = sendStack->Pop();
- }
- status = sending;
-
- // Unlink the item(s) from the list of ready transactions
- if (keepConnectionAlive)
- {
- // Our connection is still of interest, remove only this transaction from the list
- NetworkTransaction *previous = nullptr;
- for(NetworkTransaction *item = reprap.GetNetwork()->readyTransactions; item != nullptr; item = item->next)
- {
- if (item == this)
- {
- if (previous == nullptr)
- {
- reprap.GetNetwork()->readyTransactions = next;
- }
- else
- {
- previous->next = next;
- }
- break;
- }
- previous = item;
- }
- }
- else
- {
- // We will close this connection soon, stop receiving data from this PCB
- tcp_recv(cs->pcb, nullptr);
-
- // Also remove all ready transactions pointing to our ConnectionState
- NetworkTransaction *previous = nullptr, *item = reprap.GetNetwork()->readyTransactions;
- while (item != nullptr)
- {
- if (item->cs == cs)
- {
- if (item == this)
- {
- // Only unlink this item
- if (previous == nullptr)
- {
- reprap.GetNetwork()->readyTransactions = next;
- }
- else
- {
- previous->next = next;
- }
- item = next;
- }
- else
- {
- // Remove all others
- item->Discard();
- item = (previous == nullptr) ? reprap.GetNetwork()->readyTransactions : previous->next;
- }
- }
- else
- {
- previous = item;
- item = item->next;
- }
- }
- }
-
- // Enqueue this transaction, so it's sent in the right order
- NetworkTransaction *mySendingTransaction = cs->sendingTransaction;
- if (mySendingTransaction == nullptr)
- {
- cs->sendingTransaction = this;
- reprap.GetNetwork()->AppendTransaction(&reprap.GetNetwork()->writingTransactions, this);
- }
- else
- {
- while (mySendingTransaction->nextWrite != nullptr)
- {
- mySendingTransaction = mySendingTransaction->nextWrite;
- }
- mySendingTransaction->nextWrite = this;
- }
-}
-
-// Call this to perform some networking tasks while processing deferred requests,
-// and to move this transaction and all transactions that are associated with its
-// connection to the end of readyTransactions. There are three ways to do this:
-//
-// 1) DeferOnly: Do not modify any of the processed data and don't send an ACK.
-// This will ensure that zero-window packets are sent back to the client
-// 2) ResetData: Reset the read pointers and acknowledge that the data has been processed
-// 3) DiscardData: Free the processed data, acknowledge it and append this transaction as
-// an empty item again without payload (i.e. without pbufs)
-//
-void NetworkTransaction::Defer(DeferralMode mode)
-{
- if (mode == DeferralMode::ResetData)
- {
- // Reset the reading pointers and send an ACK
- inputPointer = 0;
- readingPb = pb;
- if (IsConnected() && pb != nullptr && !dataAcknowledged)
- {
- tcp_recved(cs->pcb, pb->tot_len);
- dataAcknowledged = true;
- }
- }
- else if (mode == DeferralMode::DiscardData)
- {
- // Discard the incoming data, because we don't need to process it any more
- FreePbuf();
- }
-
- status = deferred;
-
- // Unlink this transaction from the list of ready transactions and append it again
- Network *network = reprap.GetNetwork();
- NetworkTransaction *item, *previous = nullptr;
- for(item = network->readyTransactions; item != nullptr; item = item->next)
- {
- if (item == this)
- {
- if (previous == nullptr)
- {
- network->readyTransactions = next;
- }
- else
- {
- previous->next = next;
- }
- break;
- }
- previous = item;
- }
- network->AppendTransaction(&network->readyTransactions, this);
-
- // Append all other transactions that are associated to this connection, so that the
- // Webserver gets a chance to deal with all connected clients even while multiple
- // deferred requests are present in the list.
- item = network->readyTransactions;
- previous = nullptr;
- while (item != this)
- {
- if (item->cs == cs)
- {
- NetworkTransaction *nextItem = item->next;
- if (previous == nullptr)
- {
- network->readyTransactions = item->next;
- network->AppendTransaction(&network->readyTransactions, item);
- }
- else
- {
- previous->next = item->next;
- network->AppendTransaction(&network->readyTransactions, item);
- }
- item = nextItem;
- }
- else
- {
- previous = item;
- item = item->next;
- }
- }
-}
-
-
-// This method should be called if we don't want to send data to the client and if we
-// don't want to interfere with the connection state. May also be called from ISR!
-void NetworkTransaction::Discard()
-{
- // Can we do anything?
- if (status == released)
- {
- // No - don't free up released items multiple times
- return;
- }
-
- // Free up some resources
- FreePbuf();
-
- if (fileBeingSent != nullptr)
- {
- fileBeingSent->Close();
- fileBeingSent = nullptr;
- }
-
- OutputBuffer::ReleaseAll(sendBuffer);
- sendStack->ReleaseAll();
-
- // Unlink this transactions from the list of ready transactions and free it. It is then appended to the list of
- // free transactions because we don't want to risk reusing it when the ethernet ISR processes incoming data
- NetworkTransaction *previous = nullptr;
- for(NetworkTransaction *item = reprap.GetNetwork()->readyTransactions; item != nullptr; item = item->next)
- {
- if (item == this)
- {
- if (previous == nullptr)
- {
- reprap.GetNetwork()->readyTransactions = next;
- }
- else
- {
- previous->next = next;
- }
- break;
- }
- previous = item;
- }
- reprap.GetNetwork()->AppendTransaction(&reprap.GetNetwork()->freeTransactions, this);
- bool callDisconnectHandler = (cs != nullptr && status == disconnected);
- status = released;
-
- // Call disconnect event if this transaction indicates a graceful disconnect and if the connection
- // still persists (may not be the case if a RST packet was received before)
- if (callDisconnectHandler)
- {
- if (reprap.Debug(moduleNetwork))
- {
- reprap.GetPlatform()->Message(HOST_MESSAGE, "Network: Discard() is handling a graceful disconnect\n");
- }
- reprap.GetNetwork()->ConnectionClosed(cs, false);
- }
-}
-
-uint32_t NetworkTransaction::GetRemoteIP() const
-{
- return (cs != nullptr) ? cs->GetRemoteIP() : 0;
-}
-
-uint16_t NetworkTransaction::GetRemotePort() const
-{
- return (cs != nullptr) ? cs->GetRemotePort() : 0;
-}
-
-uint16_t NetworkTransaction::GetLocalPort() const
-{
- return (cs != nullptr) ? cs->GetLocalPort() : 0;
-}
-
-void NetworkTransaction::Close()
-{
- tcp_pcb *pcb = cs->pcb;
- tcp_recv(pcb, nullptr);
- closeRequested = true;
-}
-
-void NetworkTransaction::FreePbuf()
-{
- // See if we have to send an ACK to the client
- if (IsConnected() && pb != nullptr && !dataAcknowledged)
- {
- tcp_recved(cs->pcb, pb->tot_len);
- dataAcknowledged = true;
- }
-
- // Free all pbufs (pbufs are thread-safe)
- if (pb != nullptr)
- {
- pbuf_free(pb);
- pb = readingPb = nullptr;
- }
-}
+/*static*/ uint16_t Network::GetLocalPort(Connection conn) { return conn->GetLocalPort(); }
+/*static*/ uint16_t Network::GetRemotePort(Connection conn) { return conn->GetRemotePort(); }
+/*static*/ uint32_t Network::GetRemoteIP(Connection conn) { return conn->GetRemoteIP(); }
+/*static*/ bool Network::IsConnected(Connection conn) { return conn->IsConnected(); }
+/*static*/ bool Network::IsTerminated(Connection conn) { return conn->IsTerminated(); }
+/*static*/ void Network::Terminate(Connection conn) { conn->Terminate(); }
// End
diff --git a/src/Duet/Network.h b/src/Duet/Network.h
index d3d9904b..f31ccdd2 100644
--- a/src/Duet/Network.h
+++ b/src/Duet/Network.h
@@ -9,14 +9,17 @@ 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 <NetworkDefs.h>
+#include "RepRapFirmware.h"
+#include "MessageType.h"
-#include "lwipopts.h"
+#include "Lwip/lwip/src/include/lwip/err.h" // for err_t
-#include "OutputMemory.h"
+extern ConnectionState *sendingConnection;
+extern char * const sendingWindow;
+extern uint16_t sendingWindowSize, sentDataOutstanding;
+extern uint8_t sendingRetries;
+extern err_t writeResult, outputResult;
// This class handles the network - typically an Ethernet.
@@ -33,203 +36,96 @@ const size_t NETWORK_TRANSACTION_COUNT = 24; // Number of NetworkTransacti
const uint32_t TCP_WRITE_TIMEOUT = 4000; // Miliseconds to wait for data we have written to be acknowledged
const uint32_t TCP_MAX_SEND_RETRIES = 8; // How many times can we attempt to write data
-const uint8_t DefaultMacAddress[6] = { 0xBE, 0xEF, 0xDE, 0xAD, 0xFE, 0xED }; // Need some sort of default...
-const uint8_t DefaultIpAddress[4] = { 192, 168, 1, 10 };
-const uint8_t DefaultNetMask[4] = { 255, 255, 255, 0 };
-const uint8_t DefaultGateway[4] = { 192, 168, 1, 1 };
-
-const uint16_t DefaultHttpPort = 80;
-const uint16_t FTP_PORT = 21;
-const uint16_t TELNET_PORT = 23;
-
/****************************************************************************************************/
struct tcp_pcb;
struct pbuf;
-class NetworkTransaction;
-
-// ConnectionState structure that we use to track TCP connections. It is usually combined with NetworkTransactions.
-struct ConnectionState
-{
- tcp_pcb *volatile pcb; // Connection PCB
- uint16_t localPort, remotePort; // Copy of the local and remote ports, because the PCB may be unavailable
- uint32_t remoteIPAddress; // Same for the remote IP address
- NetworkTransaction * volatile sendingTransaction; // NetworkTransaction that is currently sending via this connection
- ConnectionState * volatile next; // Next ConnectionState in this list
- bool persistConnection; // Do we expect this connection to stay alive?
- volatile bool isTerminated; // Will be true if the connection has gone down unexpectedly (TCP RST)
-
- void Init(tcp_pcb *p);
- uint16_t GetLocalPort() const { return localPort; }
- uint32_t GetRemoteIP() const { return remoteIPAddress; }
- uint16_t GetRemotePort() const { return remotePort; }
- bool IsConnected() const { return pcb != nullptr; }
- bool IsTerminated() const { return isTerminated; }
- void Terminate();
-};
-
-// Assign a status to each NetworkTransaction
-enum TransactionStatus
-{
- released,
- connected,
- receiving,
- sending,
- disconnected,
- deferred,
- acquired
-};
-
-// How is a deferred request supposed to be handled?
-enum class DeferralMode
-{
- DeferOnly, // don't change anything, because we want to read more of it next time
- ResetData, // keep the data and reset all reading pointers allowing us to process it again
- DiscardData // discard all incoming data and re-enqueue the empty transaction
-};
-
-// Start with a class to hold input and output from the network that needs to be responded to.
-// This includes changes in the connection state, e.g. connects and disconnects.
-class NetworkTransaction
-{
- public:
- friend class Network;
-
- NetworkTransaction(NetworkTransaction* n);
- void Set(pbuf *p, ConnectionState* c, TransactionStatus s);
- TransactionStatus GetStatus() const { return status; }
- bool IsConnected() const;
-
- bool HasMoreDataToRead() const { return readingPb != nullptr; }
- bool Read(char& b);
- bool ReadBuffer(const char *&buffer, size_t &len);
- void Write(char b);
- void Write(const char* s);
- void Write(StringRef ref);
- void Write(const char* s, size_t len);
- void Write(OutputBuffer *buffer);
- void Write(OutputStack *stack);
- void Printf(const char *fmt, ...);
- void SetFileToWrite(FileStore *file);
-
- ConnectionState *GetConnection() const { return cs; }
- uint16_t GetLocalPort() const;
- uint32_t GetRemoteIP() const;
- uint16_t GetRemotePort() const;
-
- void Commit(bool keepConnectionAlive);
- void Defer(DeferralMode mode);
- void Discard();
-
- private:
- bool CanWrite() const;
- bool Send();
- void Close();
- void FreePbuf();
-
- ConnectionState* cs;
- NetworkTransaction* volatile next; // next NetworkTransaction in the list we are in
- NetworkTransaction* volatile nextWrite; // next NetworkTransaction queued to write to assigned connection
- pbuf *pb, *readingPb; // received packet queue and a pointer to the pbuf being read from
- size_t inputPointer; // amount of data already taken from the first packet buffer
-
- OutputBuffer *sendBuffer;
- OutputStack *sendStack;
- FileStore * volatile fileBeingSent;
-
- volatile TransactionStatus status;
- volatile bool closeRequested, dataAcknowledged;
-};
-
// The main network class that drives the network.
class Network
{
- public:
- friend class NetworkTransaction;
+public:
+ friend class NetworkTransaction;
- Network(Platform* p);
- void Init();
- void Exit() {}
- void Spin();
- void Interrupt();
- void Diagnostics(MessageType mtype);
+ Network(Platform* p);
+ void Init();
+ void Exit() {}
+ void Spin();
+ void Interrupt();
+ void Diagnostics(MessageType mtype);
- // Deal with LwIP
+ // Deal with LwIP
- void ResetCallback();
- bool ReceiveInput(pbuf *pb, ConnectionState *cs);
- ConnectionState *ConnectionAccepted(tcp_pcb *pcb);
- void ConnectionClosed(ConnectionState* cs, bool closeConnection);
- bool ConnectionClosedGracefully(ConnectionState *cs);
+ void ResetCallback();
+ bool ReceiveInput(pbuf *pb, ConnectionState *cs);
+ ConnectionState *ConnectionAccepted(tcp_pcb *pcb);
+ void ConnectionClosed(ConnectionState* cs, bool closeConnection);
+ bool ConnectionClosedGracefully(ConnectionState *cs);
- bool Lock();
- void Unlock();
- bool InLwip() const;
+ bool Lock();
+ void Unlock();
+ bool InLwip() const;
- // Global settings
+ // Global settings
- const uint8_t *GetIPAddress() const;
- void SetIPAddress(const uint8_t ipAddress[], const uint8_t netmask[], const uint8_t gateway[]);
- void SetHostname(const char *name);
+ const uint8_t *GetIPAddress() const;
+ void SetIPAddress(const uint8_t ipAddress[], const uint8_t netmask[], const uint8_t gateway[]);
+ void SetHostname(const char *name);
- void Enable();
- void Disable();
- bool IsEnabled() const { return isEnabled; }
+ void Enable();
+ void Disable();
+ bool IsEnabled() const { return isEnabled; }
- // Interfaces for the Webserver
+ // Interfaces for the Webserver
- NetworkTransaction *GetTransaction(const ConnectionState *cs = nullptr);
+ NetworkTransaction *GetTransaction(const ConnectionState *cs = nullptr);
- void OpenDataPort(uint16_t port);
- uint16_t GetDataPort() const;
- void CloseDataPort();
+ void OpenDataPort(uint16_t port);
+ uint16_t GetDataPort() const;
+ void CloseDataPort();
- void SetHttpPort(uint16_t port);
- uint16_t GetHttpPort() const;
+ void SetHttpPort(uint16_t port);
+ uint16_t GetHttpPort() const;
- void SaveDataConnection();
- void SaveFTPConnection();
- void SaveTelnetConnection();
+ void SaveDataConnection();
+ void SaveFTPConnection();
+ void SaveTelnetConnection();
- bool AcquireFTPTransaction();
- bool AcquireDataTransaction();
- bool AcquireTelnetTransaction();
+ bool AcquireFTPTransaction();
+ bool AcquireDataTransaction();
+ bool AcquireTelnetTransaction();
- private:
+ static uint16_t GetLocalPort(Connection conn);
+ static uint16_t GetRemotePort(Connection conn);
+ static uint32_t GetRemoteIP(Connection conn);
+ static bool IsConnected(Connection conn);
+ static bool IsTerminated(Connection conn);
+ static void Terminate(Connection conn);
- Platform* platform;
- float longWait;
+private:
- void AppendTransaction(NetworkTransaction* volatile * list, NetworkTransaction *r);
- void PrependTransaction(NetworkTransaction* volatile * list, NetworkTransaction *r);
- bool AcquireTransaction(ConnectionState *cs);
+ Platform* platform;
+ float longWait;
- NetworkTransaction * volatile freeTransactions;
- NetworkTransaction * volatile readyTransactions;
- NetworkTransaction * volatile writingTransactions;
+ void AppendTransaction(NetworkTransaction* volatile * list, NetworkTransaction *r);
+ void PrependTransaction(NetworkTransaction* volatile * list, NetworkTransaction *r);
+ bool AcquireTransaction(ConnectionState *cs);
- enum { NetworkInactive, NetworkEstablishingLink, NetworkObtainingIP, NetworkActive } state;
- bool isEnabled;
- volatile bool resetCallback;
- char hostname[16]; // Limit DHCP hostname to 15 characters + terminating 0
+ NetworkTransaction * volatile freeTransactions;
+ NetworkTransaction * volatile readyTransactions;
+ NetworkTransaction * volatile writingTransactions;
- ConnectionState * volatile dataCs;
- ConnectionState * volatile ftpCs;
- ConnectionState * volatile telnetCs;
+ enum { NetworkInactive, NetworkEstablishingLink, NetworkObtainingIP, NetworkActive } state;
+ bool isEnabled;
+ volatile bool resetCallback;
+ char hostname[16]; // Limit DHCP hostname to 15 characters + terminating 0
- ConnectionState * volatile freeConnections;
-};
-
-inline bool NetworkTransaction::IsConnected() const
-{
- return (cs != nullptr && cs->IsConnected());
-}
+ ConnectionState * volatile dataCs;
+ ConnectionState * volatile ftpCs;
+ ConnectionState * volatile telnetCs;
-inline bool NetworkTransaction::CanWrite() const
-{
- return (IsConnected() && status != released);
-}
+ ConnectionState * volatile freeConnections;
+};
#endif
diff --git a/src/Duet/NetworkDefs.h b/src/Duet/NetworkDefs.h
new file mode 100644
index 00000000..81f2d593
--- /dev/null
+++ b/src/Duet/NetworkDefs.h
@@ -0,0 +1,38 @@
+/*
+ * NetworkCommon.h
+ *
+ * Created on: 25 Dec 2016
+ * Author: David
+ */
+
+#ifndef SRC_DUETNG_DUETETHERNET_NETWORKCOMMON_H_
+#define SRC_DUETNG_DUETETHERNET_NETWORKCOMMON_H_
+
+#include <cstdint>
+
+class NetworkTransaction;
+class ConnectionState;
+class NetworkBuffer;
+
+// Definition of how a Connection is represented, for Webserver module
+typedef ConnectionState *Connection;
+const Connection NoConnection = nullptr;
+
+typedef uint8_t SocketNumber;
+const SocketNumber NoSocket = 255;
+
+typedef uint16_t Port;
+
+const uint8_t DefaultMacAddress[6] = { 0xBE, 0xEF, 0xDE, 0xAD, 0xFE, 0xED }; // Need some sort of default...
+const uint8_t DefaultIpAddress[4] = { 0, 0, 0, 0 }; // Need some sort of default...
+const uint8_t DefaultNetMask[4] = { 255, 255, 255, 0 };
+const uint8_t DefaultGateway[4] = { 0, 0, 0, 0 };
+
+const Port DefaultHttpPort = 80;
+const Port FTP_PORT = 21;
+const Port TELNET_PORT = 23;
+
+// MSS is defined in lwip
+#include "Lwip/lwipopts.h"
+
+#endif /* SRC_DUETNG_DUETETHERNET_NETWORKCOMMON_H_ */
diff --git a/src/Duet/NetworkTransaction.cpp b/src/Duet/NetworkTransaction.cpp
new file mode 100644
index 00000000..5c870f8a
--- /dev/null
+++ b/src/Duet/NetworkTransaction.cpp
@@ -0,0 +1,645 @@
+/*
+ * NetworkTransaction.cpp
+ *
+ * Created on: 25 Dec 2016
+ * Author: David
+ */
+
+#include "NetworkTransaction.h"
+
+#include "ConnectionState.h"
+#include "Network.h"
+#include "OutputMemory.h"
+#include "Platform.h"
+#include "RepRap.h"
+#include "Storage/FileStore.h"
+
+#include "lwip/src/include/lwip/tcp.h"
+#include "lwip/src/include/lwip/tcp_impl.h"
+
+extern "C" {
+
+static err_t conn_poll(void *arg, tcp_pcb *pcb)
+{
+ ConnectionState *cs = (ConnectionState*)arg;
+ if (cs == sendingConnection)
+ {
+ // Data could not be sent last time, check if the connection has to be timed out
+ sendingRetries++;
+ if (sendingRetries == TCP_MAX_SEND_RETRIES)
+ {
+ reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: Could not transmit data after %.1f seconds\n", (float)TCP_WRITE_TIMEOUT / 1000.0);
+ tcp_abort(pcb);
+ return ERR_ABRT;
+ }
+
+ // Try to write the remaining data once again (if required)
+ if (writeResult != ERR_OK)
+ {
+ writeResult = tcp_write(pcb, sendingWindow + (sendingWindowSize - sentDataOutstanding), sentDataOutstanding, 0);
+ if (ERR_IS_FATAL(writeResult))
+ {
+ reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: Failed to write data in conn_poll (code %d)\n", writeResult);
+ tcp_abort(pcb);
+ return ERR_ABRT;
+ }
+
+ if (writeResult != ERR_OK && reprap.Debug(moduleNetwork))
+ {
+ reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: tcp_write resulted in error code %d\n", writeResult);
+ }
+ }
+
+ // If that worked, try to output the remaining data (if required)
+ if (outputResult != ERR_OK)
+ {
+ outputResult = tcp_output(pcb);
+ if (ERR_IS_FATAL(outputResult))
+ {
+ reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: Failed to output data in conn_poll (code %d)\n", outputResult);
+ tcp_abort(pcb);
+ return ERR_ABRT;
+ }
+
+ if (outputResult != ERR_OK && reprap.Debug(moduleNetwork))
+ {
+ reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: tcp_output resulted in error code %d\n", outputResult);
+ }
+ }
+ }
+ else
+ {
+ reprap.GetPlatform()->Message(HOST_MESSAGE, "Network: Mismatched pcb in conn_poll!\n");
+ }
+ return ERR_OK;
+}
+
+static err_t conn_sent(void *arg, tcp_pcb *pcb, u16_t len)
+{
+ ConnectionState *cs = (ConnectionState*)arg;
+ if (cs == sendingConnection)
+ {
+ if (sentDataOutstanding > len)
+ {
+ sentDataOutstanding -= len;
+ }
+ else
+ {
+ tcp_poll(pcb, nullptr, TCP_WRITE_TIMEOUT / TCP_SLOW_INTERVAL / TCP_MAX_SEND_RETRIES);
+ sendingConnection = nullptr;
+ }
+ }
+ else
+ {
+ reprap.GetPlatform()->Message(HOST_MESSAGE, "Network: Mismatched pcb in conn_sent!\n");
+ }
+ return ERR_OK;
+}
+
+
+}
+
+//***************************************************************************************************
+// NetworkTransaction class
+
+NetworkTransaction::NetworkTransaction(NetworkTransaction *n) : next(n), status(released)
+{
+ sendStack = new OutputStack();
+}
+
+void NetworkTransaction::Set(pbuf *p, ConnectionState *c, TransactionStatus s)
+{
+ cs = c;
+ pb = readingPb = p;
+ status = s;
+ inputPointer = 0;
+ sendBuffer = nullptr;
+ fileBeingSent = nullptr;
+ closeRequested = false;
+ nextWrite = nullptr;
+ dataAcknowledged = false;
+}
+
+// Read one char from the NetworkTransaction
+bool NetworkTransaction::Read(char& b)
+{
+ if (readingPb == nullptr)
+ {
+ b = 0;
+ return false;
+ }
+
+ b = ((const char*)readingPb->payload)[inputPointer++];
+ if (inputPointer == readingPb->len)
+ {
+ readingPb = readingPb->next;
+ inputPointer = 0;
+ }
+ return true;
+}
+
+// Read data from the NetworkTransaction and return true on success
+bool NetworkTransaction::ReadBuffer(const char *&buffer, size_t &len)
+{
+ if (readingPb == nullptr)
+ {
+ return false;
+ }
+
+ if (inputPointer >= readingPb->len)
+ {
+ readingPb = readingPb->next;
+ inputPointer = 0;
+ if (readingPb == nullptr)
+ {
+ return false;
+ }
+ }
+
+ buffer = (const char*)readingPb->payload + inputPointer;
+ len = readingPb->len - inputPointer;
+ readingPb = readingPb->next;
+ inputPointer = 0;
+ return true;
+}
+
+void NetworkTransaction::Write(char b)
+{
+ if (CanWrite())
+ {
+ if (sendBuffer == nullptr && !OutputBuffer::Allocate(sendBuffer))
+ {
+ // Should never get here
+ return;
+ }
+ sendBuffer->cat(b);
+ }
+}
+
+void NetworkTransaction::Write(const char* s)
+{
+ if (CanWrite())
+ {
+ if (sendBuffer == nullptr && !OutputBuffer::Allocate(sendBuffer))
+ {
+ // Should never get here
+ return;
+ }
+ sendBuffer->cat(s);
+ }
+}
+
+void NetworkTransaction::Write(StringRef ref)
+{
+ Write(ref.Pointer(), ref.strlen());
+}
+
+void NetworkTransaction::Write(const char* s, size_t len)
+{
+ if (CanWrite())
+ {
+ if (sendBuffer == nullptr && !OutputBuffer::Allocate(sendBuffer))
+ {
+ // Should never get here
+ return;
+ }
+ sendBuffer->cat(s, len);
+ }
+}
+
+void NetworkTransaction::Write(OutputBuffer *buffer)
+{
+ if (CanWrite())
+ {
+ // Note we use an individual stack here, because we don't want to link different
+ // OutputBuffers for different destinations together...
+ sendStack->Push(buffer);
+ }
+ else
+ {
+ // Don't keep buffers we can't send...
+ OutputBuffer::ReleaseAll(buffer);
+ }
+}
+
+void NetworkTransaction::Write(OutputStack *stack)
+{
+ if (stack != nullptr)
+ {
+ if (CanWrite())
+ {
+ sendStack->Append(stack);
+ }
+ else
+ {
+ stack->ReleaseAll();
+ }
+ }
+}
+
+void NetworkTransaction::Printf(const char* fmt, ...)
+{
+ if (CanWrite() && (sendBuffer != nullptr || OutputBuffer::Allocate(sendBuffer)))
+ {
+ va_list p;
+ va_start(p, fmt);
+ sendBuffer->vprintf(fmt, p);
+ va_end(p);
+ }
+}
+
+void NetworkTransaction::SetFileToWrite(FileStore *file)
+{
+ if (CanWrite())
+ {
+ fileBeingSent = file;
+ }
+ else if (file != nullptr)
+ {
+ file->Close();
+ }
+}
+
+// Send exactly one TCP window of data and return true when this transaction can be released
+bool NetworkTransaction::Send()
+{
+ // Free up this transaction if the connection is supposed to be closed
+ if (closeRequested)
+ {
+ reprap.GetNetwork()->ConnectionClosed(cs, true); // This will release the transaction too
+ return false;
+ }
+
+ // Fill up the TCP window with some data chunks from our OutputBuffer instances
+ size_t bytesBeingSent = 0, bytesLeftToSend = TCP_WND;
+ while (sendBuffer != nullptr && bytesLeftToSend > 0)
+ {
+ size_t copyLength = min<size_t>(bytesLeftToSend, sendBuffer->BytesLeft());
+ memcpy(sendingWindow + bytesBeingSent, sendBuffer->Read(copyLength), copyLength);
+ bytesBeingSent += copyLength;
+ bytesLeftToSend -= copyLength;
+
+ if (sendBuffer->BytesLeft() == 0)
+ {
+ sendBuffer = OutputBuffer::Release(sendBuffer);
+ if (sendBuffer == nullptr)
+ {
+ sendBuffer = sendStack->Pop();
+ }
+ }
+ }
+
+ // We also intend to send a file, so check if we can fill up the TCP window
+ if (sendBuffer == nullptr && bytesLeftToSend != 0 && fileBeingSent != nullptr)
+ {
+ // For HSMCI efficiency, read from the file in multiples of 4 bytes except at the end.
+ // This ensures that the second and subsequent chunks can be DMA'd directly into sendingWindow.
+ size_t bytesToRead = bytesLeftToSend & (~3);
+ if (bytesToRead != 0)
+ {
+ int bytesRead = fileBeingSent->Read(sendingWindow + bytesBeingSent, bytesToRead);
+ if (bytesRead > 0)
+ {
+ bytesBeingSent += bytesRead;
+ }
+
+ if (bytesRead != (int)bytesToRead)
+ {
+ fileBeingSent->Close();
+ fileBeingSent = nullptr;
+ }
+ }
+ }
+
+ if (bytesBeingSent == 0)
+ {
+ // If we have no data to send, this connection can be closed next time
+ if (!cs->persistConnection && nextWrite == nullptr)
+ {
+ Close();
+ return false;
+ }
+
+ // We want to send data from another transaction as well, so only free up this one
+ cs->sendingTransaction = nextWrite;
+ return true;
+ }
+
+ // The TCP window has been filled up as much as possible, so send it now. There is no need to check
+ // the available space in the SNDBUF queue, because we really write only one TCP window at once.
+ writeResult = tcp_write(cs->pcb, sendingWindow, bytesBeingSent, 0);
+ if (ERR_IS_FATAL(writeResult))
+ {
+ reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: Failed to write data in Send (code %d)\n", writeResult);
+ tcp_abort(cs->pcb);
+ return false;
+ }
+
+ outputResult = tcp_output(cs->pcb);
+ if (ERR_IS_FATAL(outputResult))
+ {
+ reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: Failed to output data in Send (code %d)\n", outputResult);
+ tcp_abort(cs->pcb);
+ return false;
+ }
+
+ if (outputResult != ERR_OK && reprap.Debug(moduleNetwork))
+ {
+ reprap.GetPlatform()->MessageF(HOST_MESSAGE, "Network: tcp_output resulted in error code %d\n", outputResult);
+ }
+
+ // Set LwIP callbacks for ACK and retransmission handling
+ tcp_poll(cs->pcb, conn_poll, TCP_WRITE_TIMEOUT / TCP_SLOW_INTERVAL / TCP_MAX_SEND_RETRIES);
+ tcp_sent(cs->pcb, conn_sent);
+
+ // Set all values for the send process
+ sendingConnection = cs;
+ sendingRetries = 0;
+ sendingWindowSize = sentDataOutstanding = bytesBeingSent;
+ return false;
+}
+
+// This is called by the Webserver to send output data to a client. If keepConnectionAlive is set to false,
+// the current connection will be terminated once everything has been sent.
+void NetworkTransaction::Commit(bool keepConnectionAlive)
+{
+ // If the connection has been terminated (e.g. RST received while writing upload data), discard this transaction
+ if (!IsConnected() || status == released)
+ {
+ Discard();
+ return;
+ }
+
+ // Free buffer holding the incoming data and prepare some values for the sending process
+ FreePbuf();
+ cs->persistConnection = keepConnectionAlive;
+ if (sendBuffer == nullptr)
+ {
+ sendBuffer = sendStack->Pop();
+ }
+ status = sending;
+
+ // Unlink the item(s) from the list of ready transactions
+ if (keepConnectionAlive)
+ {
+ // Our connection is still of interest, remove only this transaction from the list
+ NetworkTransaction *previous = nullptr;
+ for(NetworkTransaction *item = reprap.GetNetwork()->readyTransactions; item != nullptr; item = item->next)
+ {
+ if (item == this)
+ {
+ if (previous == nullptr)
+ {
+ reprap.GetNetwork()->readyTransactions = next;
+ }
+ else
+ {
+ previous->next = next;
+ }
+ break;
+ }
+ previous = item;
+ }
+ }
+ else
+ {
+ // We will close this connection soon, stop receiving data from this PCB
+ tcp_recv(cs->pcb, nullptr);
+
+ // Also remove all ready transactions pointing to our ConnectionState
+ NetworkTransaction *previous = nullptr, *item = reprap.GetNetwork()->readyTransactions;
+ while (item != nullptr)
+ {
+ if (item->cs == cs)
+ {
+ if (item == this)
+ {
+ // Only unlink this item
+ if (previous == nullptr)
+ {
+ reprap.GetNetwork()->readyTransactions = next;
+ }
+ else
+ {
+ previous->next = next;
+ }
+ item = next;
+ }
+ else
+ {
+ // Remove all others
+ item->Discard();
+ item = (previous == nullptr) ? reprap.GetNetwork()->readyTransactions : previous->next;
+ }
+ }
+ else
+ {
+ previous = item;
+ item = item->next;
+ }
+ }
+ }
+
+ // Enqueue this transaction, so it's sent in the right order
+ NetworkTransaction *mySendingTransaction = cs->sendingTransaction;
+ if (mySendingTransaction == nullptr)
+ {
+ cs->sendingTransaction = this;
+ reprap.GetNetwork()->AppendTransaction(&reprap.GetNetwork()->writingTransactions, this);
+ }
+ else
+ {
+ while (mySendingTransaction->nextWrite != nullptr)
+ {
+ mySendingTransaction = mySendingTransaction->nextWrite;
+ }
+ mySendingTransaction->nextWrite = this;
+ }
+}
+
+// Call this to perform some networking tasks while processing deferred requests,
+// and to move this transaction and all transactions that are associated with its
+// connection to the end of readyTransactions. There are three ways to do this:
+//
+// 1) DeferOnly: Do not modify any of the processed data and don't send an ACK.
+// This will ensure that zero-window packets are sent back to the client
+// 2) ResetData: Reset the read pointers and acknowledge that the data has been processed
+// 3) DiscardData: Free the processed data, acknowledge it and append this transaction as
+// an empty item again without payload (i.e. without pbufs)
+//
+void NetworkTransaction::Defer(DeferralMode mode)
+{
+ if (mode == DeferralMode::ResetData)
+ {
+ // Reset the reading pointers and send an ACK
+ inputPointer = 0;
+ readingPb = pb;
+ if (IsConnected() && pb != nullptr && !dataAcknowledged)
+ {
+ tcp_recved(cs->pcb, pb->tot_len);
+ dataAcknowledged = true;
+ }
+ }
+ else if (mode == DeferralMode::DiscardData)
+ {
+ // Discard the incoming data, because we don't need to process it any more
+ FreePbuf();
+ }
+
+ status = deferred;
+
+ // Unlink this transaction from the list of ready transactions and append it again
+ Network *network = reprap.GetNetwork();
+ NetworkTransaction *item, *previous = nullptr;
+ for(item = network->readyTransactions; item != nullptr; item = item->next)
+ {
+ if (item == this)
+ {
+ if (previous == nullptr)
+ {
+ network->readyTransactions = next;
+ }
+ else
+ {
+ previous->next = next;
+ }
+ break;
+ }
+ previous = item;
+ }
+ network->AppendTransaction(&network->readyTransactions, this);
+
+ // Append all other transactions that are associated to this connection, so that the
+ // Webserver gets a chance to deal with all connected clients even while multiple
+ // deferred requests are present in the list.
+ item = network->readyTransactions;
+ previous = nullptr;
+ while (item != this)
+ {
+ if (item->cs == cs)
+ {
+ NetworkTransaction *nextItem = item->next;
+ if (previous == nullptr)
+ {
+ network->readyTransactions = item->next;
+ network->AppendTransaction(&network->readyTransactions, item);
+ }
+ else
+ {
+ previous->next = item->next;
+ network->AppendTransaction(&network->readyTransactions, item);
+ }
+ item = nextItem;
+ }
+ else
+ {
+ previous = item;
+ item = item->next;
+ }
+ }
+}
+
+
+// This method should be called if we don't want to send data to the client and if we
+// don't want to interfere with the connection state. May also be called from ISR!
+void NetworkTransaction::Discard()
+{
+ // Can we do anything?
+ if (status == released)
+ {
+ // No - don't free up released items multiple times
+ return;
+ }
+
+ // Free up some resources
+ FreePbuf();
+
+ if (fileBeingSent != nullptr)
+ {
+ fileBeingSent->Close();
+ fileBeingSent = nullptr;
+ }
+
+ OutputBuffer::ReleaseAll(sendBuffer);
+ sendStack->ReleaseAll();
+
+ // Unlink this transactions from the list of ready transactions and free it. It is then appended to the list of
+ // free transactions because we don't want to risk reusing it when the ethernet ISR processes incoming data
+ NetworkTransaction *previous = nullptr;
+ for(NetworkTransaction *item = reprap.GetNetwork()->readyTransactions; item != nullptr; item = item->next)
+ {
+ if (item == this)
+ {
+ if (previous == nullptr)
+ {
+ reprap.GetNetwork()->readyTransactions = next;
+ }
+ else
+ {
+ previous->next = next;
+ }
+ break;
+ }
+ previous = item;
+ }
+ reprap.GetNetwork()->AppendTransaction(&reprap.GetNetwork()->freeTransactions, this);
+ bool callDisconnectHandler = (cs != nullptr && status == disconnected);
+ status = released;
+
+ // Call disconnect event if this transaction indicates a graceful disconnect and if the connection
+ // still persists (may not be the case if a RST packet was received before)
+ if (callDisconnectHandler)
+ {
+ if (reprap.Debug(moduleNetwork))
+ {
+ reprap.GetPlatform()->Message(HOST_MESSAGE, "Network: Discard() is handling a graceful disconnect\n");
+ }
+ reprap.GetNetwork()->ConnectionClosed(cs, false);
+ }
+}
+
+uint32_t NetworkTransaction::GetRemoteIP() const
+{
+ return (cs != nullptr) ? cs->GetRemoteIP() : 0;
+}
+
+uint16_t NetworkTransaction::GetRemotePort() const
+{
+ return (cs != nullptr) ? cs->GetRemotePort() : 0;
+}
+
+uint16_t NetworkTransaction::GetLocalPort() const
+{
+ return (cs != nullptr) ? cs->GetLocalPort() : 0;
+}
+
+void NetworkTransaction::Close()
+{
+ tcp_pcb *pcb = cs->pcb;
+ tcp_recv(pcb, nullptr);
+ closeRequested = true;
+}
+
+void NetworkTransaction::FreePbuf()
+{
+ // See if we have to send an ACK to the client
+ if (IsConnected() && pb != nullptr && !dataAcknowledged)
+ {
+ tcp_recved(cs->pcb, pb->tot_len);
+ dataAcknowledged = true;
+ }
+
+ // Free all pbufs (pbufs are thread-safe)
+ if (pb != nullptr)
+ {
+ pbuf_free(pb);
+ pb = readingPb = nullptr;
+ }
+}
+
+bool NetworkTransaction::IsConnected() const
+{
+ return (cs != nullptr && cs->IsConnected());
+}
+
+// End
diff --git a/src/Duet/NetworkTransaction.h b/src/Duet/NetworkTransaction.h
new file mode 100644
index 00000000..e16567e4
--- /dev/null
+++ b/src/Duet/NetworkTransaction.h
@@ -0,0 +1,94 @@
+/*
+ * NetworkTransaction.h
+ *
+ * Created on: 25 Dec 2016
+ * Author: David
+ */
+
+#ifndef SRC_DUET_NETWORKTRANSACTION_H_
+#define SRC_DUET_NETWORKTRANSACTION_H_
+
+#include <NetworkDefs.h>
+#include "RepRapFirmware.h"
+
+class pbuf;
+
+// Assign a status to each NetworkTransaction
+enum TransactionStatus
+{
+ released,
+ connected,
+ receiving,
+ sending,
+ disconnected,
+ deferred,
+ acquired
+};
+
+// How is a deferred request supposed to be handled?
+enum class DeferralMode
+{
+ DeferOnly, // don't change anything, because we want to read more of it next time
+ ResetData, // keep the data and reset all reading pointers allowing us to process it again
+ DiscardData // discard all incoming data and re-enqueue the empty transaction
+};
+
+// Start with a class to hold input and output from the network that needs to be responded to.
+// This includes changes in the connection state, e.g. connects and disconnects.
+class NetworkTransaction
+{
+public:
+ friend class Network;
+
+ NetworkTransaction(NetworkTransaction* n);
+ void Set(pbuf *p, ConnectionState* c, TransactionStatus s);
+ TransactionStatus GetStatus() const { return status; }
+ bool IsConnected() const;
+
+ bool HasMoreDataToRead() const { return readingPb != nullptr; }
+ bool Read(char& b);
+ bool ReadBuffer(const char *&buffer, size_t &len);
+ void Write(char b);
+ void Write(const char* s);
+ void Write(StringRef ref);
+ void Write(const char* s, size_t len);
+ void Write(OutputBuffer *buffer);
+ void Write(OutputStack *stack);
+ void Printf(const char *fmt, ...);
+ void SetFileToWrite(FileStore *file);
+
+ ConnectionState *GetConnection() const { return cs; }
+ uint16_t GetLocalPort() const;
+ uint32_t GetRemoteIP() const;
+ uint16_t GetRemotePort() const;
+
+ void Commit(bool keepConnectionAlive);
+ void Defer(DeferralMode mode);
+ void Discard();
+
+private:
+ bool CanWrite() const;
+ bool Send();
+ void Close();
+ void FreePbuf();
+
+ ConnectionState* cs;
+ NetworkTransaction* volatile next; // next NetworkTransaction in the list we are in
+ NetworkTransaction* volatile nextWrite; // next NetworkTransaction queued to write to assigned connection
+ pbuf *pb, *readingPb; // received packet queue and a pointer to the pbuf being read from
+ size_t inputPointer; // amount of data already taken from the first packet buffer
+
+ OutputBuffer *sendBuffer;
+ OutputStack *sendStack;
+ FileStore * volatile fileBeingSent;
+
+ volatile TransactionStatus status;
+ volatile bool closeRequested, dataAcknowledged;
+};
+
+inline bool NetworkTransaction::CanWrite() const
+{
+ return (IsConnected() && status != released);
+}
+
+#endif /* SRC_DUET_NETWORKTRANSACTION_H_ */
diff --git a/src/Duet/Pins_Duet.h b/src/Duet/Pins_Duet.h
index d5af7f65..4234a869 100644
--- a/src/Duet/Pins_Duet.h
+++ b/src/Duet/Pins_Duet.h
@@ -24,8 +24,6 @@ const int8_t HEATERS = 7; // The number of heaters in the machine; 0 is the
const size_t MAX_AXES = 6; // The maximum number of movement axes in the machine, usually just X, Y and Z, <= DRIVES
const size_t MIN_AXES = 3; // The minimum and default number of axes
-const size_t DELTA_AXES = 3; // The number of axis involved in delta movement
-const size_t CART_AXES = 3; // The number of Cartesian axes
const size_t MaxExtruders = DRIVES - MIN_AXES; // The maximum number of extruders
const size_t NUM_SERIAL_CHANNELS = 3; // The number of serial IO channels (USB and two auxiliary UARTs)
diff --git a/src/Duet/Webserver.h b/src/Duet/Webserver.h
deleted file mode 100644
index d2bab356..00000000
--- a/src/Duet/Webserver.h
+++ /dev/null
@@ -1,382 +0,0 @@
-/****************************************************************************************************
-
-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
-
-#include "lwipopts.h"
-
-/* Generic values */
-
-const size_t gcodeBufferLength = 512; // size of our gcode ring buffer, preferably a power of 2
-
-/* HTTP */
-
-#define KO_START "rr_"
-#define KO_FIRST 3
-
-const uint16_t webMessageLength = TCP_MSS; // maximum length of the web message we accept after decoding
-const size_t minHttpResponseSize = 768; // minimum number of bytes required for an HTTP response
-
-const size_t maxCommandWords = 4; // max number of space-separated words in the command
-const size_t maxQualKeys = 5; // max number of key/value pairs in the qualifier
-const size_t maxHeaders = 16; // max number of key/value pairs in the headers
-
-const size_t maxHttpSessions = 8; // maximum number of simultaneous HTTP sessions
-const uint32_t httpSessionTimeout = 8000; // HTTP session timeout in milliseconds
-
-/* FTP */
-
-const uint16_t ftpMessageLength = 128; // maximum line length for incoming FTP commands
-const uint32_t ftpPasvPortTimeout = 10000; // maximum time to wait for an FTP data connection in milliseconds
-
-/* Telnet */
-
-const uint32_t telnetSetupDuration = 4000; // ignore the first Telnet request within this duration (in ms)
-
-
-class Webserver;
-
-// List of protocols that can execute G-Codes
-enum class WebSource
-{
- HTTP,
- Telnet
-};
-
-// This is the abstract class for all supported protocols
-// Any inherited class should implement a state machine to increase performance and reduce memory usage.
-class ProtocolInterpreter
-{
- public:
-
- ProtocolInterpreter(Platform *p, Webserver *ws, Network *n);
- virtual ~ProtocolInterpreter() { } // to keep Eclipse happy
- virtual void Diagnostics(MessageType mtype) = 0;
- virtual void Spin();
-
- virtual void ConnectionEstablished();
- virtual void ConnectionLost(const ConnectionState *cs) { }
- virtual bool CanParseData();
- virtual bool CharFromClient(const char c) = 0;
- virtual void NoMoreDataAvailable();
-
- virtual bool DoingFastUpload() const;
- virtual void DoFastUpload();
- void CancelUpload(); // may be called from ISR!
-
- protected:
-
- Platform *platform;
- Webserver *webserver;
- Network *network;
-
- // Information for file uploading
- enum UploadState
- {
- notUploading, // no upload in progress
- uploadOK, // upload in progress, no error so far
- uploadError // upload in progress but had error
- };
-
- UploadState uploadState;
- FileData fileBeingUploaded;
- char filenameBeingUploaded[FILENAME_LENGTH];
-
- bool StartUpload(FileStore *file, const char *fileName);
- bool IsUploading() const;
- bool FinishUpload(uint32_t fileLength);
-};
-
-class Webserver
-{
- public:
-
- friend class Platform;
- friend class ProtocolInterpreter;
-
- 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;
-
- // 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;
-
- void ConnectionLost(const ConnectionState *cs);
- void ConnectionError();
-
- protected:
-
- class HttpInterpreter : public ProtocolInterpreter
- {
- public:
-
- HttpInterpreter(Platform *p, Webserver *ws, Network *n);
- void Spin();
- void Diagnostics(MessageType mtype) override;
- void ConnectionLost(const ConnectionState *cs);
- bool CanParseData() override;
- bool CharFromClient(const char c) override;
- void NoMoreDataAvailable() override;
- void ResetState();
- void ResetSessions();
-
- bool DoingFastUpload() const override;
- void DoFastUpload();
-
- bool GCodeAvailable() const;
- char ReadGCode();
- void HandleGCodeReply(OutputBuffer *reply);
- void HandleGCodeReply(const char *reply);
- uint16_t GetGCodeBufferSpace() const;
- uint32_t GetReplySeq() const;
-
- private:
-
- // HTTP server state enumeration. The order is important, in particular xxxEsc1 must follow xxx, and xxxEsc2 must follow xxxEsc1.
- // We assume that qualifier keys do not contain escapes, because none of ours needs to be encoded. If we are sent escapes in the key,
- // it won't do any harm, but the key won't be recognised even if it would be valid were it decoded.
- enum HttpState
- {
- doingCommandWord, // receiving a word in the first line of the HTTP request
- 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
- doingHeaderKey, // receiving a header key
- expectingHeaderValue, // expecting a header value
- doingHeaderValue, // receiving a header value
- doingHeaderContinuation // received a newline after a header value
- };
- HttpState state;
-
- struct KeyValueIndices
- {
- const char* key;
- const char* value;
- };
-
- void SendFile(const char* nameOfFileToSend, bool isWebFile);
- void SendGCodeReply();
- void SendJsonResponse(const char* command);
- void GetJsonResponse(const char* request, OutputBuffer *&response, const char* key, const char* value, size_t valueLength, bool& keepOpen);
- bool ProcessMessage();
- bool RejectMessage(const char* s, unsigned int code = 500);
-
- // Buffers for processing HTTP input
- char clientMessage[webMessageLength + 3]; // holds the command, qualifier, and headers
- size_t clientPointer; // current index into clientMessage
- char decodeChar;
-
- const char* commandWords[maxCommandWords];
- KeyValueIndices qualifiers[maxQualKeys + 1]; // offsets into clientQualifier of the key/value pairs, the +1 is needed so that values can contain nulls
- KeyValueIndices headers[maxHeaders]; // offsets into clientHeader of the key/value pairs
- size_t numCommandWords;
- size_t numQualKeys; // number of qualifier keys we have found, <= maxQualKeys
- size_t numHeaderKeys; // number of keys we have found, <= maxHeaders
-
- // HTTP sessions
- struct HttpSession
- {
- uint32_t ip;
- uint32_t lastQueryTime;
- bool isPostUploading;
- uint16_t postPort;
- };
-
- HttpSession sessions[maxHttpSessions];
- uint8_t numSessions;
- uint8_t clientsServed;
-
- bool Authenticate();
- bool IsAuthenticated() const;
- void UpdateAuthentication();
- bool RemoveAuthentication();
-
- // Deal with incoming G-Codes
-
- char gcodeBuffer[gcodeBufferLength];
- uint16_t gcodeReadIndex, gcodeWriteIndex; // head and tail indices into gcodeBuffer
-
- void LoadGcodeBuffer(const char* gc);
- void ProcessGcode(const char* gc);
- void StoreGcodeData(const char* data, uint16_t len);
-
- // Responses from GCodes class
-
- uint32_t seq; // Sequence number for G-Code replies
- OutputStack *gcodeReply;
-
- // File uploads
- uint32_t postFileLength, uploadedBytes; // How many POST bytes do we expect and how many have already been written?
- time_t fileLastModified;
-
- // Deferred requests (rr_fileinfo)
- ConnectionState * volatile deferredRequestConnection; // Which connection expects a response for a deferred request?
- char filenameBeingProcessed[FILENAME_LENGTH]; // The filename being processed (for rr_fileinfo)
-
- void ProcessDeferredRequest();
- };
- HttpInterpreter *httpInterpreter;
-
- class FtpInterpreter : public ProtocolInterpreter
- {
- public:
-
- FtpInterpreter(Platform *p, Webserver *ws, Network *n);
- void Diagnostics(MessageType mtype) override;
-
- void ConnectionEstablished() override;
- void ConnectionLost(const ConnectionState *cs) override;
- bool CharFromClient(const char c) override;
- void ResetState();
-
- bool DoingFastUpload() const override;
-
- private:
-
- enum FtpState
- {
- idle, // no client connected
- authenticating, // not logged in
- authenticated, // logged in
- waitingForPasvPort, // waiting for connection to be established on PASV port
- pasvPortConnected, // client connected to PASV port, ready to send data
- doingPasvIO // client is connected and data is being transferred
- };
- FtpState state;
- uint8_t connectedClients;
-
- char clientMessage[ftpMessageLength];
- size_t clientPointer;
-
- char filename[FILENAME_LENGTH];
- char currentDir[FILENAME_LENGTH];
-
- uint32_t portOpenTime;
-
- void ProcessLine();
- void SendReply(int code, const char *message, bool keepConnection = true);
- void SendFeatures();
-
- void ReadFilename(uint16_t start);
- void ChangeDirectory(const char *newDirectory);
- };
- FtpInterpreter *ftpInterpreter;
-
- class TelnetInterpreter : public ProtocolInterpreter
- {
- public:
-
- TelnetInterpreter(Platform *p, Webserver *ws, Network *n);
- void Diagnostics(MessageType mtype) override;
-
- void ConnectionEstablished() override;
- void ConnectionLost(const ConnectionState *cs) override;
- bool CanParseData() override;
- bool CharFromClient(const char c) override;
- void ResetState();
-
- bool GCodeAvailable() const;
- char ReadGCode();
- void HandleGCodeReply(OutputBuffer *reply);
- void HandleGCodeReply(const char *reply);
- uint16_t GetGCodeBufferSpace() const;
-
- void SendGCodeReply();
-
- private:
-
- enum TelnetState
- {
- idle, // not connected
- justConnected, // not logged in, but the client has just connected
- authenticating, // not logged in
- authenticated // logged in
- };
- TelnetState state;
- uint8_t connectedClients;
- uint32_t connectTime;
-
- bool processNextLine;
- char clientMessage[GCODE_LENGTH];
- size_t clientPointer;
-
- bool ProcessLine();
-
- // Deal with incoming G-Codes
-
- char gcodeBuffer[gcodeBufferLength];
- uint16_t gcodeReadIndex, gcodeWriteIndex; // head and tail indices into gcodeBuffer
-
- void ProcessGcode(const char* gc);
- void StoreGcodeData(const char* data, uint16_t len);
-
- // Converted response from GCodes class (NL -> CRNL)
-
- OutputBuffer * volatile gcodeReply;
- };
- TelnetInterpreter *telnetInterpreter;
-
- private:
-
- Platform* platform;
- Network* network;
- bool webserverActive;
- NetworkTransaction *currentTransaction;
- ConnectionState * volatile readingConnection;
-
- float longWait;
-};
-
-inline bool ProtocolInterpreter::CanParseData() { return true; }
-inline bool ProtocolInterpreter::DoingFastUpload() const { return false; }
-inline bool ProtocolInterpreter::IsUploading() const { return uploadState != notUploading; }
-
-inline uint32_t Webserver::GetReplySeq() const { return httpInterpreter->GetReplySeq(); }
-
-inline uint16_t Webserver::HttpInterpreter::GetGCodeBufferSpace() const { return (gcodeReadIndex - gcodeWriteIndex - 1u) % gcodeBufferLength; }
-inline bool Webserver::HttpInterpreter::GCodeAvailable() const { return gcodeReadIndex != gcodeWriteIndex; }
-inline uint32_t Webserver::HttpInterpreter::GetReplySeq() const { return seq; }
-
-inline uint16_t Webserver::TelnetInterpreter::GetGCodeBufferSpace() const { return (gcodeReadIndex - gcodeWriteIndex - 1u) % gcodeBufferLength; }
-inline bool Webserver::TelnetInterpreter::GCodeAvailable() const { return gcodeReadIndex != gcodeWriteIndex; }
-
-#endif
diff --git a/src/DuetNG/DuetEthernet/Network.cpp b/src/DuetNG/DuetEthernet/Network.cpp
index e2c17ddf..4f742cb8 100644
--- a/src/DuetNG/DuetEthernet/Network.cpp
+++ b/src/DuetNG/DuetEthernet/Network.cpp
@@ -5,10 +5,9 @@
* Author: David
*/
-#include "RepRapFirmware.h"
-#include "compiler.h"
-#include "Pins.h"
-#include "IPAddress.h"
+#include "Network.h"
+#include "NetworkTransaction.h"
+#include "Platform.h"
#include "wizchip_conf.h"
#include "Wiznet/Internet/DHCP/dhcp.h"
@@ -35,6 +34,7 @@ void Network::Init()
state = NetworkState::disabled;
longWait = platform->Time();
lastTickMillis = millis();
+ InitSockets();
}
// This is called at the end of config.g processing.
@@ -78,6 +78,7 @@ void Network::Spin()
else
{
debugPrintf("Link established, network running\n");
+ InitSockets();
state = NetworkState::active;
}
}
@@ -100,12 +101,14 @@ void Network::Spin()
// Send mDNS announcement so that some routers can perform hostname mapping
// if this board is connected via a non-IGMP capable WiFi bridge (like the TP-Link WR701N)
//mdns_announce();
+ InitSockets();
state = NetworkState::active;
}
}
else
{
DHCP_stop();
+ TerminateSockets();
state = NetworkState::establishingLink;
}
break;
@@ -128,9 +131,13 @@ void Network::Spin()
}
}
- // See if we can read any packets
-// ethernet_task();
-
+ sockets[nextSocketToPoll].Poll();
+ ++nextSocketToPoll;
+ if (nextSocketToPoll == NumTcpSockets)
+ {
+ nextSocketToPoll = 0;
+ }
+#if 0
// See if we can send anything
NetworkTransaction *transaction = writingTransactions;
if (transaction != nullptr /*&& sendingConnection == nullptr*/ )
@@ -159,10 +166,12 @@ void Network::Spin()
}
}
}
+#endif
}
else
{
DHCP_stop();
+ TerminateSockets();
state = NetworkState::establishingLink;
}
break;
@@ -303,7 +312,7 @@ bool Network::InLwip() const
// This method also ensures that the retrieved transaction is moved to the first item of
// readyTransactions, so that a subsequent call with a NULL cs parameter will return exactly
// the same instance.
-NetworkTransaction *Network::GetTransaction(const ConnectionState *cs)
+NetworkTransaction *Network::GetTransaction(Connection conn)
{
#if 1
return nullptr;
@@ -416,26 +425,17 @@ void Network::CloseDataPort()
// These methods keep track of our connections in case we need to send to one of them
void Network::SaveDataConnection()
{
- //TODO
-#if 0
- dataCs = readyTransactions->cs;
-#endif
+// dataCs = readyTransactions->cs->GetNumber();
}
void Network::SaveFTPConnection()
{
- //TODO
-#if 0
- ftpCs = readyTransactions->cs;
-#endif
+// ftpCs = readyTransactions->cs->GetNumber();
}
void Network::SaveTelnetConnection()
{
- //TODO
-#if 0
- telnetCs = readyTransactions->cs;
-#endif
+// telnetCs = readyTransactions->cs->GetNumber();
}
bool Network::AcquireFTPTransaction()
@@ -465,4 +465,32 @@ bool Network::AcquireTelnetTransaction()
#endif
}
+void Network::InitSockets()
+{
+ for (SocketNumber skt = 0; skt < NumHttpSockets; ++skt)
+ {
+ sockets[skt].Init(skt, httpPort);
+ }
+ sockets[FtpSocketNumber].Init(FtpSocketNumber, FTP_PORT);
+ sockets[FtpDataSocketNumber].Init(FtpDataSocketNumber, 0); // FTP data port is allocated dynamically
+ sockets[TelnetSocketNumber].Init(TelnetSocketNumber, TELNET_PORT);
+ nextSocketToProcess = nextSocketToPoll = 0;
+}
+
+void Network::TerminateSockets()
+{
+ for (SocketNumber skt = 0; skt < NumTcpSockets; ++skt)
+ {
+ sockets[skt].Terminate();
+ }
+
+}
+
+/*static*/ uint16_t Network::GetLocalPort(Connection conn) { return conn->GetLocalPort(); }
+/*static*/ uint16_t Network::GetRemotePort(Connection conn) { return conn->GetRemotePort(); }
+/*static*/ uint32_t Network::GetRemoteIP(Connection conn) { return conn->GetRemoteIP(); }
+/*static*/ bool Network::IsConnected(Connection conn) { return conn->IsConnected(); }
+/*static*/ bool Network::IsTerminated(Connection conn) { return conn->IsTerminated(); }
+/*static*/ void Network::Terminate(Connection conn) { conn->Terminate(); }
+
// End
diff --git a/src/DuetNG/DuetEthernet/Network.h b/src/DuetNG/DuetEthernet/Network.h
index 36501dcc..d710205a 100644
--- a/src/DuetNG/DuetEthernet/Network.h
+++ b/src/DuetNG/DuetEthernet/Network.h
@@ -9,26 +9,18 @@ Separated out from Platform.h by dc42 and extended by zpl
#ifndef NETWORK_H
#define NETWORK_H
-#include <cstdint>
-#include <cctype>
-#include <cstring>
-#include <cstdlib>
-
+#include "NetworkDefs.h"
+#include "RepRapFirmware.h"
#include "MessageType.h"
-#include "NetworkTransaction.h"
-
-const uint8_t DefaultMacAddress[6] = { 0xBE, 0xEF, 0xDE, 0xAD, 0xFE, 0xED }; // Need some sort of default...
-const uint8_t DefaultIpAddress[4] = { 0, 0, 0, 0 }; // Need some sort of default...
-const uint8_t DefaultNetMask[4] = { 255, 255, 255, 0 };
-const uint8_t DefaultGateway[4] = { 0, 0, 0, 0 };
-
-const uint16_t DefaultHttpPort = 80;
-const uint16_t FTP_PORT = 21;
-const uint16_t TELNET_PORT = 23;
+#include "Socket.h"
-// We have 8 sockets available on the W5500. We reserve one for DHCP, leaving 7 for TCP/IP transactions i.e. HTTP, FTP and Telnet.
+// We have 8 sockets available on the W5500.
+const size_t NumHttpSockets = 4; // sockets 0-3 are for HTTP
+const SocketNumber FtpSocketNumber = 4;
+const SocketNumber FtpDataSocketNumber = 5; // TODO can we allocate this dynamically when required, to allow more http sockets most of the time?
+const SocketNumber TelnetSocketNumber = 6;
const size_t NumTcpSockets = 7;
-const uint8_t DhcpSocketNumber = 7;
+const SocketNumber DhcpSocketNumber = 7; // TODO can we allocate this dynamically when required, to allow more http sockets most of the time?
class Platform;
@@ -63,7 +55,7 @@ public:
// Interfaces for the Webserver
- NetworkTransaction *GetTransaction(const ConnectionState *cs = nullptr);
+ NetworkTransaction *GetTransaction(Connection conn = NoConnection);
void OpenDataPort(uint16_t port);
uint16_t GetDataPort() const;
@@ -77,6 +69,13 @@ public:
bool AcquireDataTransaction();
bool AcquireTelnetTransaction();
+ static uint16_t GetLocalPort(Connection conn);
+ static uint16_t GetRemotePort(Connection conn);
+ static uint32_t GetRemoteIP(Connection conn);
+ static bool IsConnected(Connection conn);
+ static bool IsTerminated(Connection conn);
+ static void Terminate(Connection conn);
+
private:
enum class NetworkState
{
@@ -87,13 +86,20 @@ private:
active
};
+ void AppendTransaction(NetworkTransaction* * list, NetworkTransaction *r);
+ void PrependTransaction(NetworkTransaction* * list, NetworkTransaction *r);
+ bool AcquireTransaction(Socket *cs);
+
+ void InitSockets();
+ void TerminateSockets();
+
Platform *platform;
float longWait;
uint32_t lastTickMillis;
- void AppendTransaction(NetworkTransaction* * list, NetworkTransaction *r);
- void PrependTransaction(NetworkTransaction* * list, NetworkTransaction *r);
- bool AcquireTransaction(ConnectionState *cs);
+ Socket sockets[NumTcpSockets];
+ size_t nextSocketToPoll; // next TCP socket number to poll for read/write operations
+ size_t nextSocketToProcess; // next TCP socket number to process an incoming request from
NetworkTransaction * freeTransactions;
NetworkTransaction * readyTransactions;
@@ -105,7 +111,7 @@ private:
uint8_t gateway[4];
char hostname[16]; // Limit DHCP hostname to 15 characters + terminating 0
- NetworkState state;
+ NetworkState state;
bool activated;
bool usingDhcp;
};
diff --git a/src/DuetNG/DuetEthernet/NetworkBuffer.cpp b/src/DuetNG/DuetEthernet/NetworkBuffer.cpp
new file mode 100644
index 00000000..8fad5241
--- /dev/null
+++ b/src/DuetNG/DuetEthernet/NetworkBuffer.cpp
@@ -0,0 +1,83 @@
+/*
+ * NetworkBuffer.cpp
+ *
+ * Created on: 24 Dec 2016
+ * Author: David
+ */
+
+#include "NetworkBuffer.h"
+#include <cstring>
+
+NetworkBuffer *NetworkBuffer::freelist = nullptr;
+
+NetworkBuffer::NetworkBuffer(NetworkBuffer *n) : next(n), dataLength(0), readPointer(0)
+{
+}
+
+// Release this buffer and return the next one in the chain
+NetworkBuffer *NetworkBuffer::Release()
+{
+ NetworkBuffer *ret = next;
+ next = freelist;
+ freelist = this;
+ return ret;
+}
+
+// Read 1 character, returning true if successful, false if no data left
+bool NetworkBuffer::ReadChar(char& b)
+{
+ if (readPointer < dataLength)
+ {
+ b = data[readPointer++];
+ return true;
+ }
+
+ b = 0;
+ return false;
+}
+
+// Read some data, but not more than the amount in the first buffer
+size_t NetworkBuffer::ReadBuffer(uint8_t *buffer, size_t maxLen)
+{
+ if (maxLen > Remaining())
+ {
+ maxLen = Remaining();
+ }
+ memcpy((void*)buffer, data + readPointer, maxLen);
+ readPointer += maxLen;
+ return maxLen;
+}
+
+// Return the amount of data available, including continuation buffers
+size_t NetworkBuffer::TotalRemaining() const
+{
+ const NetworkBuffer *b = this;
+ size_t ret = 0;
+ while (b != nullptr)
+ {
+ ret += b->Remaining();
+ }
+ return ret;
+}
+
+/*static*/ NetworkBuffer *NetworkBuffer::Allocate()
+{
+ NetworkBuffer *ret = freelist;
+ if (ret != nullptr)
+ {
+ freelist = ret->next;
+ ret->next = nullptr;
+ }
+ return ret;
+}
+
+/*static*/ void NetworkBuffer::AllocateBuffers(unsigned int number)
+{
+ while (number != 0)
+ {
+ freelist = new NetworkBuffer(freelist);
+ --number;
+ }
+}
+
+// End
diff --git a/src/DuetNG/DuetEthernet/NetworkBuffer.h b/src/DuetNG/DuetEthernet/NetworkBuffer.h
new file mode 100644
index 00000000..9e20c4bd
--- /dev/null
+++ b/src/DuetNG/DuetEthernet/NetworkBuffer.h
@@ -0,0 +1,54 @@
+/*
+ * NetworkBuffer.h
+ *
+ * Created on: 24 Dec 2016
+ * Author: David
+ */
+
+#ifndef SRC_DUETNG_DUETETHERNET_NETWORKBUFFER_H_
+#define SRC_DUETNG_DUETETHERNET_NETWORKBUFFER_H_
+
+#include <cstdint>
+#include <cstddef>
+
+// Network buffer class. These buffers are 2K long so that they can accept as much data as the W5500 can provide in one go.
+class NetworkBuffer
+{
+public:
+ friend class Socket;
+
+ // Release this buffer and return the next one in the chain
+ NetworkBuffer *Release();
+
+ // Read 1 character, returning true of successful, false if no data left
+ bool ReadChar(char& b);
+
+ // Read some data
+ size_t ReadBuffer(uint8_t *buffer, size_t maxLen);
+
+ // Return the amount of data available, not including continuation buffers
+ size_t Remaining() const { return dataLength - readPointer; }
+
+ // Return the amount of data available, including continuation buffers
+ size_t TotalRemaining() const;
+
+ bool IsEmpty() const { return readPointer == dataLength; }
+
+ static NetworkBuffer *Allocate();
+
+ static void AllocateBuffers(unsigned int number);
+
+ static const size_t bufferSize = 2048;
+
+private:
+ NetworkBuffer(NetworkBuffer *n);
+
+ NetworkBuffer *next;
+ size_t dataLength;
+ size_t readPointer;
+ uint8_t data[bufferSize];
+
+ static NetworkBuffer *freelist;
+};
+
+#endif /* SRC_DUETNG_DUETETHERNET_NETWORKBUFFER_H_ */
diff --git a/src/DuetNG/DuetEthernet/NetworkDefs.h b/src/DuetNG/DuetEthernet/NetworkDefs.h
new file mode 100644
index 00000000..a1225e63
--- /dev/null
+++ b/src/DuetNG/DuetEthernet/NetworkDefs.h
@@ -0,0 +1,35 @@
+/*
+ * NetworkCommon.h
+ *
+ * Created on: 25 Dec 2016
+ * Author: David
+ */
+
+#ifndef SRC_DUETNG_DUETETHERNET_NETWORKDEFS_H_
+#define SRC_DUETNG_DUETETHERNET_NETWORKDEFS_H_
+
+class NetworkTransaction;
+class Socket;
+class NetworkBuffer;
+
+// Definition of how a Connection is represented, for Webserver module
+typedef Socket *Connection;
+const Connection NoConnection = nullptr;
+
+typedef uint8_t SocketNumber;
+const SocketNumber NoSocket = 255;
+
+typedef uint16_t Port;
+
+const uint8_t DefaultMacAddress[6] = { 0xBE, 0xEF, 0xDE, 0xAD, 0xFE, 0xED }; // Need some sort of default...
+const uint8_t DefaultIpAddress[4] = { 0, 0, 0, 0 }; // Need some sort of default...
+const uint8_t DefaultNetMask[4] = { 255, 255, 255, 0 };
+const uint8_t DefaultGateway[4] = { 0, 0, 0, 0 };
+
+const Port DefaultHttpPort = 80;
+const Port FTP_PORT = 21;
+const Port TELNET_PORT = 23;
+
+const unsigned int TCP_MSS = 1460;
+
+#endif /* SRC_DUETNG_DUETETHERNET_NETWORKDEFS_H_ */
diff --git a/src/DuetNG/DuetEthernet/NetworkTransacrion.cpp b/src/DuetNG/DuetEthernet/NetworkTransaction.cpp
index e954723d..855c4c85 100644
--- a/src/DuetNG/DuetEthernet/NetworkTransacrion.cpp
+++ b/src/DuetNG/DuetEthernet/NetworkTransaction.cpp
@@ -6,44 +6,10 @@
*/
#include "NetworkTransaction.h"
+#include "Socket.h"
#include <cstdarg>
//***************************************************************************************************
-
-// ConnectionState class
-
-#if 0
-void ConnectionState::Init(tcp_pcb *p)
-{
- pcb = p;
- localPort = p->local_port;
- remoteIPAddress = p->remote_ip.addr;
- remotePort = p->remote_port;
- next = nullptr;
- sendingTransaction = nullptr;
- persistConnection = true;
- isTerminated = false;
-}
-#endif
-
-void ConnectionState::Terminate()
-{
- //TODO
-#if 0
- if (pcb != nullptr)
- {
- tcp_abort(pcb);
- }
-#endif
-}
-
-bool ConnectionState::IsConnected() const
-{
- //TODO
- return false;
-}
-
-//***************************************************************************************************
// NetworkTransaction class
NetworkTransaction::NetworkTransaction(NetworkTransaction *n) : next(n), status(released)
@@ -72,26 +38,26 @@ bool NetworkTransaction::HasMoreDataToRead() const
return false;
}
+bool NetworkTransaction::IsConnected() const
+{
+ return (cs != nullptr && cs->IsConnected());
+}
+
+bool NetworkTransaction::CanWrite() const
+{
+ return (IsConnected() && status != released);
+}
+
// Read one char from the NetworkTransaction
bool NetworkTransaction::Read(char& b)
{
-#if 1
- return false;
-#else
- if (readingPb == nullptr)
+ if (cs == nullptr)
{
b = 0;
return false;
}
- b = ((const char*)readingPb->payload)[inputPointer++];
- if (inputPointer == readingPb->len)
- {
- readingPb = readingPb->next;
- inputPointer = 0;
- }
- return true;
-#endif
+ return cs->ReadChar(b);
}
// Read data from the NetworkTransaction and return true on success
diff --git a/src/DuetNG/DuetEthernet/NetworkTransaction.h b/src/DuetNG/DuetEthernet/NetworkTransaction.h
index 91a9eedb..93ddc725 100644
--- a/src/DuetNG/DuetEthernet/NetworkTransaction.h
+++ b/src/DuetNG/DuetEthernet/NetworkTransaction.h
@@ -8,69 +8,13 @@
#ifndef SRC_DUETNG_DUETETHERNET_NETWORKTRANSACTION_H_
#define SRC_DUETNG_DUETETHERNET_NETWORKTRANSACTION_H_
+#include <NetworkDefs.h>
#include <cstdint>
#include <cstddef>
#include "Libraries/General/StringRef.h"
#include "OutputMemory.h"
#include "Storage/FileStore.h"
-
-typedef uint8_t SocketNumber;
-
-const SocketNumber NoSocket = 255;
-
-// Network buffer class. These buffers are 2K long so that they can accept as much data as the W5500 can provide in one go.
-class NetworkBuffer
-{
-public:
- friend class NetworkTransaction;
-
- NetworkBuffer();
-
- // Release this buffer and return the next one in the chain
- NetworkBuffer *Release();
-
- // Read 1 character, returning true of successful, false if no data left
- bool Read(char& b);
-
- // Read some data
- bool ReadBuffer(const char *&buffer, size_t &len);
-
- // Return the amount of data available, not including continuation buffers
- size_t Remaining() const;
-
- // Return the amount of data available, including continuation buffers
- size_t TotalRemaining() const;
-
-private:
- NetworkBuffer *next;
- size_t dataLength;
- size_t readPointer;
- uint8_t data[2048];
-};
-
-class NetworkTransaction;
-
-// ConnectionState structure that we use to track TCP connections. It is usually combined with NetworkTransactions.
-class ConnectionState
-{
-public:
- void Init(SocketNumber s);
- uint16_t GetLocalPort() const { return localPort; }
- uint32_t GetRemoteIP() const { return remoteIPAddress; }
- uint16_t GetRemotePort() const { return remotePort; }
- bool IsConnected() const; // { return pcb != nullptr; }
- bool IsTerminated() const { return isTerminated; }
- void Terminate();
-
-private:
- uint16_t localPort, remotePort; // Copy of the local and remote ports, because the PCB may be unavailable
- uint32_t remoteIPAddress; // Same for the remote IP address
- NetworkTransaction * volatile sendingTransaction; // NetworkTransaction that is currently sending via this connection
- ConnectionState * volatile next; // Next ConnectionState in this list
- bool persistConnection; // Do we expect this connection to stay alive?
- bool isTerminated; // Will be true if the connection has gone down unexpectedly (TCP RST)
- SocketNumber socketNum; // The W5500 socket number we are using
-};
+#include "NetworkBuffer.h"
// Assign a status to each NetworkTransaction
enum TransactionStatus
@@ -116,10 +60,10 @@ public:
void Printf(const char *fmt, ...);
void SetFileToWrite(FileStore *file);
- ConnectionState *GetConnection() const { return cs; }
- uint16_t GetLocalPort() const;
+ Connection GetConnection() const { return cs; }
+ Port GetLocalPort() const;
uint32_t GetRemoteIP() const;
- uint16_t GetRemotePort() const;
+ Port GetRemotePort() const;
bool Send();
void Commit(bool keepConnectionAlive);
@@ -133,28 +77,18 @@ private:
bool CanWrite() const;
void Close();
- ConnectionState* cs;
+ Socket* cs; // the network socket that this transaction cam from
NetworkTransaction* next; // next NetworkTransaction in the list we are in
NetworkTransaction* nextWrite; // next NetworkTransaction queued to write to assigned connection
- NetworkBuffer *pb, *readingPb; // received packet queue and a pointer to the pbuf being read from
+// NetworkBuffer *pb, *readingPb; // received packet queue and a pointer to the pbuf being read from
// size_t inputPointer; // amount of data already taken from the first packet buffer
OutputBuffer *sendBuffer;
OutputStack *sendStack;
FileStore * volatile fileBeingSent;
- volatile TransactionStatus status;
- volatile bool closeRequested, dataAcknowledged;
+ /*volatile*/ TransactionStatus status;
+ /*volatile*/ bool closeRequested, dataAcknowledged;
};
-inline bool NetworkTransaction::IsConnected() const
-{
- return (cs != nullptr && cs->IsConnected());
-}
-
-inline bool NetworkTransaction::CanWrite() const
-{
- return (IsConnected() && status != released);
-}
-
#endif /* SRC_DUETNG_DUETETHERNET_NETWORKTRANSACTION_H_ */
diff --git a/src/DuetNG/DuetEthernet/Socket.cpp b/src/DuetNG/DuetEthernet/Socket.cpp
new file mode 100644
index 00000000..7bfff537
--- /dev/null
+++ b/src/DuetNG/DuetEthernet/Socket.cpp
@@ -0,0 +1,166 @@
+/*
+ * Socket.cpp
+ *
+ * Created on: 25 Dec 2016
+ * Author: David
+ */
+
+#include "Socket.h"
+#include "NetworkTransaction.h"
+#include "NetworkBuffer.h"
+#include <socketlib.h>
+
+//***************************************************************************************************
+// Socket class
+
+Socket::Socket() : receivedData(nullptr), sendingTransaction(nullptr)
+{
+}
+
+// Initialise a TCP socket
+void Socket::Init(SocketNumber skt, Port serverPort)
+{
+ socketNum = skt;
+ localPort = serverPort;
+ ReInit();
+}
+
+void Socket::ReInit()
+{
+ // Discard any transactions that were queued to send
+ NetworkTransaction *st;
+ while ((st = sendingTransaction) != nullptr)
+ {
+ sendingTransaction = st->GetNext();
+ st->Discard();
+ }
+
+ // Discard any received data
+ while (receivedData != nullptr)
+ {
+ receivedData = receivedData->Release();
+ }
+
+ persistConnection = true;
+ isTerminated = false;
+ state = SocketState::inactive;
+
+ // Re-initialise the socket on the W5500
+ socket(socketNum, Sn_MR_TCP, localPort, 0x00);
+}
+
+// Terminate a connection
+void Socket::Terminate()
+{
+ disconnectNoWait(socketNum);
+ isTerminated = true;
+ state = SocketState::inactive;
+ while (receivedData != nullptr)
+ {
+ receivedData = receivedData->Release();
+ }
+}
+
+// Test whether we have a connection on this socket
+bool Socket::IsConnected() const
+{
+ return getSn_SR(socketNum) == SOCK_ESTABLISHED; //TODO is this right?
+}
+
+// Read 1 character from the receive buffers, returning true if successful
+bool Socket::ReadChar(char& c)
+{
+ if (receivedData == nullptr)
+ {
+ c = 0;
+ return false;
+ }
+
+ bool ret = receivedData->ReadChar(c);
+ if (receivedData->IsEmpty())
+ {
+ receivedData = receivedData->Release();
+ }
+ return ret;
+}
+
+// Poll a socket to see if it needs to be serviced
+void Socket::Poll()
+{
+ switch(getSn_SR(socketNum))
+ {
+ case SOCK_INIT:
+ // Socket has been initialised but is not listening yet
+ if (localPort != 0) // localPort for the FTP data socket is 0 until we have decided what port number to use
+ {
+ listen(socketNum);
+ }
+ break;
+
+ case SOCK_LISTEN: // Socket is listening but no client has connected to it yet
+ break;
+
+ case SOCK_ESTABLISHED: // A client is connected to this socket
+ if (getSn_IR(socketNum) & Sn_IR_CON)
+ {
+ // New connection, so retrieve the sending IP address and port, and clear the interrupt
+ getSn_DIPR(socketNum, reinterpret_cast<uint8_t*>(&remoteIPAddress));
+ remotePort = getSn_DPORT(socketNum);
+ setSn_IR(socketNum, Sn_IR_CON);
+ }
+
+ // See if the socket has received any data
+ {
+ const uint16_t len = getSn_RX_RSR(socketNum);
+ if (len != 0)
+ {
+ // There is data available, so allocate a buffer
+ NetworkBuffer * const buf = NetworkBuffer::Allocate();
+ if (buf != nullptr)
+ {
+ buf->dataLength = recv(socketNum, buf->data, min<size_t>(len, NetworkBuffer::bufferSize));
+ buf->readPointer = 0;
+ // Append the buffer to the list of receive buffers
+ NetworkBuffer** bufp = &receivedData;
+ while (*bufp != nullptr)
+ {
+ bufp = &((*bufp)->next);
+ }
+ *bufp = buf;
+ }
+ }
+ }
+
+ // See if we can send any data
+ //TODO
+ break;
+
+ case SOCK_CLOSE_WAIT: // A client has asked to disconnect
+#ifdef _HTTPSERVER_DEBUG_
+ printf("> HTTPSocket[%d] : ClOSE_WAIT\r\n", socketNum);
+#endif
+ disconnect(socketNum);
+ break;
+
+ case SOCK_CLOSED:
+#ifdef _HTTPSERVER_DEBUG_
+ printf("> HTTPSocket[%d] : CLOSED\r\n", s);
+#endif
+ if (socket(socketNum, Sn_MR_TCP, localPort, 0x00) == socketNum) // Reinitialize the socket
+ {
+#ifdef _HTTPSERVER_DEBUG_
+ printf("> HTTPSocket[%d] : OPEN\r\n", socketNum);
+#endif
+ }
+ break;
+
+ default:
+ break;
+ } // end of switch
+
+#ifdef _USE_WATCHDOG_
+ HTTPServer_WDT_Reset();
+#endif
+}
+
+// End
diff --git a/src/DuetNG/DuetEthernet/Socket.h b/src/DuetNG/DuetEthernet/Socket.h
new file mode 100644
index 00000000..59f04906
--- /dev/null
+++ b/src/DuetNG/DuetEthernet/Socket.h
@@ -0,0 +1,54 @@
+/*
+ * Socket.h
+ *
+ * Created on: 25 Dec 2016
+ * Author: David
+ */
+
+#ifndef SRC_DUETNG_DUETETHERNET_SOCKET_H_
+#define SRC_DUETNG_DUETETHERNET_SOCKET_H_
+
+#include <NetworkDefs.h>
+#include <cstdint>
+#include <cstddef>
+
+// Socket structure that we use to track TCP connections
+class Socket
+{
+public:
+ Socket();
+ void Init(SocketNumber s, Port serverPort);
+ void Poll();
+ Port GetLocalPort() const { return localPort; }
+ uint32_t GetRemoteIP() const { return remoteIPAddress; }
+ Port GetRemotePort() const { return remotePort; }
+ bool IsConnected() const;
+ bool IsTerminated() const { return isTerminated; }
+ void Terminate();
+ SocketNumber GetNumber() const { return socketNum; }
+ bool ReadChar(char& c);
+
+private:
+ enum class SocketState : uint8_t
+ {
+ inactive,
+ idle,
+ requestInProgress,
+ requestDone,
+ responseInProgress,
+ responseDone
+ };
+
+ void ReInit();
+
+ Port localPort, remotePort; // The local and remote ports
+ uint32_t remoteIPAddress; // The remote IP address
+ NetworkBuffer *receivedData; // Chain of buffers holding received data
+ NetworkTransaction * /*volatile*/ sendingTransaction; // NetworkTransaction that is currently sending via this connection
+ bool persistConnection; // Do we expect this connection to stay alive?
+ bool isTerminated; // Will be true if the connection has gone down unexpectedly (TCP RST)
+ SocketNumber socketNum; // The W5500 socket number we are using
+ SocketState state;
+};
+
+#endif /* SRC_DUETNG_DUETETHERNET_SOCKET_H_ */
diff --git a/src/DuetNG/DuetEthernet/Webserver.cpp b/src/DuetNG/DuetEthernet/Webserver.cpp
deleted file mode 100644
index af1bdcbe..00000000
--- a/src/DuetNG/DuetEthernet/Webserver.cpp
+++ /dev/null
@@ -1,2942 +0,0 @@
-/****************************************************************************************************
-
- 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. Times should be generally specified
- in the format YYYY-MM-DDTHH:MM:SS so the firmware can parse them.
-
- rr_connect?password=xxx&time=yyy
- 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_filelist?dir=xxx
- Returns a JSON-formatted list of all the files in xxx including the type and size in the
- following format: "files":[{"type":'f/d',"name":"xxx",size:yyy},...]
-
- rr_files?dir=xxx&flagDirs={1/0} [DEPRECATED]
- 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_configfile [DEPRECATED]
- Sends the config file as plain text (not encapsulated as JSON either).
-
- rr_download?name=xxx
- Download a specified file from the SD card
-
- rr_upload?name=xxx&time=yyy
- 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_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";
-
-
-//********************************************************************************************
-//
-//**************************** Generic Webserver implementation ******************************
-//
-//********************************************************************************************
-
-
-// Constructor and initialisation
-Webserver::Webserver(Platform* p, Network *n) : platform(p), network(n), webserverActive(false)
-{
- httpInterpreter = new HttpInterpreter(p, this, n);
- ftpInterpreter = new FtpInterpreter(p, this, n);
- telnetInterpreter = new TelnetInterpreter(p, this, n);
-}
-
-void Webserver::Init()
-{
- // initialise the webserver class
- longWait = platform->Time();
- webserverActive = true;
- readingConnection = nullptr;
-
- // initialise all protocol handlers
- httpInterpreter->ResetState();
- ftpInterpreter->ResetState();
- telnetInterpreter->ResetState();
-}
-
-// Deal with input/output from/to the client (if any)
-void Webserver::Spin()
-{
- // Check if we are enabled and we can actually send something back to the client
- if (webserverActive && OutputBuffer::GetBytesLeft(nullptr) != 0)
- {
- // We must ensure that we have exclusive access to LWIP
- if (!network->Lock())
- {
- // Allow each ProtocolInterpreter to do something
- httpInterpreter->Spin();
- ftpInterpreter->Spin();
- telnetInterpreter->Spin();
-
- // See if we have new data to process
- currentTransaction = network->GetTransaction(readingConnection);
- if (currentTransaction != nullptr)
- {
- // Take care of different protocol types here
- ProtocolInterpreter *interpreter;
- const uint16_t localPort = currentTransaction->GetLocalPort();
- switch (localPort)
- {
- case FTP_PORT: /* FTP */
- interpreter = ftpInterpreter;
- break;
-
- case TELNET_PORT: /* Telnet */
- interpreter = telnetInterpreter;
- break;
-
- default: /* HTTP and FTP data */
- if (localPort == network->GetHttpPort())
- {
- interpreter = httpInterpreter;
- }
- else
- {
- interpreter = ftpInterpreter;
- }
- break;
- }
-
- // See if we have to print some debug info
- if (reprap.Debug(moduleWebserver))
- {
- const char *type;
- switch (currentTransaction->GetStatus())
- {
- case released: type = "released"; break;
- case connected: type = "connected"; break;
- case receiving: type = "receiving"; break;
- case sending: type = "sending"; break;
- case disconnected: type = "disconnected"; break;
- case deferred: type = "deferred"; break;
- case acquired: type = "acquired"; break;
- default: type = "unknown"; break;
- }
- platform->MessageF(HOST_MESSAGE, "Incoming transaction: Type %s at local port %d (remote port %d)\n",
- type, localPort, currentTransaction->GetRemotePort());
- }
-
- // For protocols other than HTTP it is important to send a HELO message
- TransactionStatus status = currentTransaction->GetStatus();
- if (status == connected)
- {
- interpreter->ConnectionEstablished();
- }
- // Graceful disconnects are handled here, because prior NetworkTransactions might still contain valid
- // data. That's why it's a bad idea to close these connections immediately in the Network class.
- else if (status == disconnected)
- {
- // This will call the disconnect events and effectively close the connection
- currentTransaction->Discard();
- }
- // Check for fast uploads via this connection
- else if (interpreter->DoingFastUpload())
- {
- interpreter->DoFastUpload();
- }
- // Process other messages (if we can)
- else if (interpreter->CanParseData())
- {
- readingConnection = currentTransaction->GetConnection();
- for (size_t i = 0; i < TCP_MSS / 3; i++)
- {
- char c;
- if (currentTransaction->Read(c))
- {
- // Each ProtocolInterpreter must take care of the current NetworkTransaction by calling either Commit(), Discard() or Defer()
- if (interpreter->CharFromClient(c))
- {
- readingConnection = nullptr;
- break;
- }
- }
- else
- {
- // We ran out of data before finding a complete request. This happens when the incoming
- // message length exceeds the TCP MSS. Notify the current ProtocolInterpreter about this,
- // which will remove the current transaction too
- interpreter->NoMoreDataAvailable();
- readingConnection = nullptr;
- break;
- }
- }
- }
- }
- else if (readingConnection != nullptr)
- {
- // We failed to find a transaction for a reading connection.
- // This should never happen, but if it does, terminate this connection instantly
- platform->Message(HOST_MESSAGE, "Error: Transaction for reading connection not found\n");
- readingConnection->Terminate();
- }
- network->Unlock(); // unlock LWIP again
- }
- }
- platform->ClassReport(longWait);
-}
-
-void Webserver::Exit()
-{
- httpInterpreter->CancelUpload();
- ftpInterpreter->CancelUpload();
- //telnetInterpreter->CancelUpload(); // Telnet doesn't support fast file uploads
-
- platform->Message(HOST_MESSAGE, "Webserver class exited.\n");
- webserverActive = false;
-}
-
-void Webserver::Diagnostics(MessageType mtype)
-{
- platform->Message(mtype, "=== Webserver ===\n");
- httpInterpreter->Diagnostics(mtype);
- ftpInterpreter->Diagnostics(mtype);
- telnetInterpreter->Diagnostics(mtype);
-}
-
-bool Webserver::GCodeAvailable(const WebSource source) const
-{
- switch (source)
- {
- case WebSource::HTTP:
- return httpInterpreter->GCodeAvailable();
-
- case WebSource::Telnet:
- return telnetInterpreter->GCodeAvailable();
- }
-
- return false;
-}
-
-char Webserver::ReadGCode(const WebSource source)
-{
- switch (source)
- {
- case WebSource::HTTP:
- return httpInterpreter->ReadGCode();
-
- case WebSource::Telnet:
- return telnetInterpreter->ReadGCode();
- }
-
- return 0;
-}
-
-void Webserver::HandleGCodeReply(const WebSource source, OutputBuffer *reply)
-{
- switch (source)
- {
- case WebSource::HTTP:
- httpInterpreter->HandleGCodeReply(reply);
- break;
-
- case WebSource::Telnet:
- telnetInterpreter->HandleGCodeReply(reply);
- break;
- }
-}
-
-void Webserver::HandleGCodeReply(const WebSource source, const char *reply)
-{
- switch (source)
- {
- case WebSource::HTTP:
- httpInterpreter->HandleGCodeReply(reply);
- break;
-
- case WebSource::Telnet:
- telnetInterpreter->HandleGCodeReply(reply);
- break;
- }
-}
-
-uint16_t Webserver::GetGCodeBufferSpace(const WebSource source) const
-{
- switch (source)
- {
- case WebSource::HTTP:
- return httpInterpreter->GetGCodeBufferSpace();
-
- case WebSource::Telnet:
- return telnetInterpreter->GetGCodeBufferSpace();
- }
-
- return 0;
-}
-
-// Handle immediate disconnects here (cs will be freed after this call)
-// May be called by ISR, but not while LwIP is NOT locked
-void Webserver::ConnectionLost(const ConnectionState *cs)
-{
- // Inform protocol handlers that this connection has been lost
- uint16_t localPort = cs->GetLocalPort();
- ProtocolInterpreter *interpreter;
- switch (localPort)
- {
- case FTP_PORT: /* FTP */
- interpreter = ftpInterpreter;
- break;
-
- case TELNET_PORT: /* Telnet */
- interpreter = telnetInterpreter;
- break;
-
- default: /* HTTP and FTP data */
- if (localPort == network->GetHttpPort())
- {
- interpreter = httpInterpreter;
- break;
- }
- else if (localPort == network->GetDataPort())
- {
- interpreter = ftpInterpreter;
- break;
- }
-
- platform->MessageF(GENERIC_MESSAGE, "Error: Webserver should handle disconnect event at local port %d, but no handler was found!\n", localPort);
- return;
- }
-
- // Print some debug information and notify the protocol interpreter
- if (reprap.Debug(moduleWebserver))
- {
- platform->MessageF(HOST_MESSAGE, "ConnectionLost called for local port %d (remote port %d)\n", localPort, cs->GetRemotePort());
- }
- interpreter->ConnectionLost(cs);
-
- // Don't process any more data from this connection if has gone down
- if (readingConnection == cs)
- {
- readingConnection = nullptr;
- }
-}
-
-
-//********************************************************************************************
-//
-//********************** Generic Procotol Interpreter implementation *************************
-//
-//********************************************************************************************
-
-ProtocolInterpreter::ProtocolInterpreter(Platform *p, Webserver *ws, Network *n)
- : platform(p), webserver(ws), network(n)
-{
- uploadState = notUploading;
- filenameBeingUploaded[0] = 0;
-}
-
-void ProtocolInterpreter::Spin()
-{
- // Check if anything went wrong while writing upload data, and delete the file again if that is the case
- if (uploadState == uploadError)
- {
- if (fileBeingUploaded.IsLive())
- {
- fileBeingUploaded.Close();
- }
- if (filenameBeingUploaded[0] != 0)
- {
- platform->GetMassStorage()->Delete(FS_PREFIX, filenameBeingUploaded);
- }
-
- uploadState = notUploading;
- filenameBeingUploaded[0] = 0;
- }
-}
-
-void ProtocolInterpreter::ConnectionEstablished()
-{
- // Don't care about incoming connections by default
- webserver->currentTransaction->Discard();
-}
-
-void ProtocolInterpreter::NoMoreDataAvailable()
-{
- // Request is not complete yet, but don't care. Interpreters that do not explicitly
- // overwrite this method don't support more than one connected client anyway
- webserver->currentTransaction->Discard();
-}
-
-// Start writing to a new file
-bool ProtocolInterpreter::StartUpload(FileStore *file, const char *fileName)
-{
- if (file != nullptr)
- {
- fileBeingUploaded.Set(file);
- strncpy(filenameBeingUploaded, fileName, ARRAY_SIZE(filenameBeingUploaded));
- filenameBeingUploaded[ARRAY_UPB(filenameBeingUploaded)] = 0;
-
- uploadState = uploadOK;
- return true;
- }
-
- platform->Message(GENERIC_MESSAGE, "Error: Could not open file while starting upload!\n");
- return false;
-}
-
-void ProtocolInterpreter::CancelUpload()
-{
- if (uploadState == uploadOK)
- {
- // Do the file handling next time when Spin is called
- uploadState = uploadError;
- }
-}
-
-void ProtocolInterpreter::DoFastUpload()
-{
- NetworkTransaction *transaction = webserver->currentTransaction;
-
- const char *buffer;
- size_t len;
- if (transaction->ReadBuffer(buffer, len))
- {
- // See if we can output a debug message
- if (reprap.Debug(moduleWebserver))
- {
- platform->MessageF(HOST_MESSAGE, "Writing %u bytes of upload data\n", len);
- }
-
- // Writing data usually takes a while, so keep LwIP running while this is being done
- network->Unlock();
- if (!fileBeingUploaded.Write(buffer, len))
- {
- platform->Message(GENERIC_MESSAGE, "Error: Could not write upload data!\n");
- CancelUpload();
-
- while (!network->Lock());
- transaction->Commit(false);
- return;
- }
- while (!network->Lock());
- }
-
- if (uploadState != uploadOK || !transaction->HasMoreDataToRead())
- {
- transaction->Discard();
- }
-}
-
-bool ProtocolInterpreter::FinishUpload(uint32_t fileLength)
-{
- // Flush remaining data for FSO
- if (uploadState == uploadOK && !fileBeingUploaded.Flush())
- {
- uploadState = uploadError;
- platform->Message(GENERIC_MESSAGE, "Error: Could not flush remaining data while finishing upload!\n");
- }
-
- // Check the file length is as expected
- if (uploadState == uploadOK && fileLength != 0 && fileBeingUploaded.Length() != fileLength)
- {
- uploadState = uploadError;
- platform->MessageF(GENERIC_MESSAGE, "Error: Uploaded file size is different (%u vs. expected %u bytes)!\n", fileBeingUploaded.Length(), fileLength);
- }
-
- // Close the file
- if (fileBeingUploaded.IsLive())
- {
- fileBeingUploaded.Close();
- }
-
- // Delete the file again if an error has occurred
- if (uploadState == uploadError && filenameBeingUploaded[0] != 0)
- {
- platform->GetMassStorage()->Delete(FS_PREFIX, filenameBeingUploaded);
- }
-
- // Clean up again
- bool success = (uploadState == uploadOK);
- uploadState = notUploading;
- filenameBeingUploaded[0] = 0;
- return success;
-}
-
-
-//********************************************************************************************
-//
-// *********************** HTTP interpreter for the Webserver class **************************
-//
-//********************************************************************************************
-
-
-
-Webserver::HttpInterpreter::HttpInterpreter(Platform *p, Webserver *ws, Network *n)
- : ProtocolInterpreter(p, ws, n), state(doingCommandWord), numSessions(0), clientsServed(0)
-{
- gcodeReadIndex = gcodeWriteIndex = 0;
- gcodeReply = new OutputStack();
- deferredRequestConnection = nullptr;
- seq = 0;
-}
-
-void Webserver::HttpInterpreter::Diagnostics(MessageType mt)
-{
- platform->MessageF(mt, "HTTP sessions: %d of %d\n", numSessions, maxHttpSessions);
-}
-
-void Webserver::HttpInterpreter::Spin()
-{
- // Deal with aborted uploads
- ProtocolInterpreter::Spin();
-
- // Verify HTTP sessions
- const uint32_t now = millis();
- for(int i = numSessions - 1; i >= 0; i--)
- {
- if (sessions[i].isPostUploading)
- {
- // Check for cancelled POST uploads
- if (uploadState != uploadOK)
- {
- sessions[i].isPostUploading = false;
- sessions[i].lastQueryTime = millis();
- }
- }
- else if ((now - sessions[i].lastQueryTime) > httpSessionTimeout)
- {
- // Check for timed out sessions
- for(size_t k = i + 1; k < numSessions; k++)
- {
- memcpy(&sessions[k - 1], &sessions[k], sizeof(HttpSession));
- }
- numSessions--;
- 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;
- }
-
-}
-
-// File Uploads
-
-bool Webserver::HttpInterpreter::DoingFastUpload() const
-{
- uint32_t remoteIP = webserver->currentTransaction->GetRemoteIP();
- uint16_t remotePort = webserver->currentTransaction->GetRemotePort();
- for(size_t i = 0; i < numSessions; i++)
- {
- if (sessions[i].ip == remoteIP && sessions[i].isPostUploading)
- {
- // There is only one session per IP address...
- return (sessions[i].postPort == remotePort);
- }
- }
- return false;
-}
-
-void Webserver::HttpInterpreter::DoFastUpload()
-{
- NetworkTransaction *transaction = webserver->currentTransaction;
-
- // Write some data on the SD card
- const char *buffer;
- size_t len;
- if (transaction->ReadBuffer(buffer, len))
- {
- network->Unlock();
- // Write data in sector-aligned chunks. This also means that the buffer in fatfs is only used to hold the FAT.
- static const size_t writeBufLength = 2048; // use a multiple of the 512b sector size
- static uint32_t writeBufStorage[writeBufLength/4]; // aligned buffer for file writes
- static size_t writeBufIndex;
- char* const writeBuf = (char *)writeBufStorage;
-
- if (uploadedBytes == 0)
- {
- writeBufIndex = 0;
- }
-
- while (len != 0)
- {
- size_t lengthToCopy = min<size_t>(writeBufLength - writeBufIndex, len);
- memcpy(writeBuf + writeBufIndex, buffer, lengthToCopy);
- writeBufIndex += lengthToCopy;
- uploadedBytes += lengthToCopy;
- buffer += lengthToCopy;
- len -= lengthToCopy;
- if (writeBufIndex == writeBufLength || uploadedBytes >= postFileLength)
- {
- bool success = fileBeingUploaded.Write(writeBuf, writeBufIndex);
- writeBufIndex = 0;
- if (!success)
- {
- platform->Message(GENERIC_MESSAGE, "Error: Could not write upload data!\n");
- CancelUpload();
-
- while (!network->Lock());
- SendJsonResponse("upload");
- return;
- }
- }
- }
- while (!network->Lock());
- }
-
- // See if the upload has finished
- if (uploadState == uploadOK && uploadedBytes >= postFileLength)
- {
- // Reset POST upload state for this client
- uint32_t remoteIP = transaction->GetRemoteIP();
- for(size_t i = 0; i < numSessions; i++)
- {
- if (sessions[i].ip == remoteIP && sessions[i].isPostUploading)
- {
- sessions[i].isPostUploading = false;
- sessions[i].lastQueryTime = millis();
- break;
- }
- }
-
- // Grab a copy of the filename and finish this upload
- char filename[FILENAME_LENGTH];
- strncpy(filename, filenameBeingUploaded, FILENAME_LENGTH);
- FinishUpload(postFileLength);
-
- // Update the file timestamp if it was specified before
- if (fileLastModified != 0)
- {
- (void)platform->GetMassStorage()->SetLastModifiedTime(nullptr, filename, fileLastModified);
- }
-
- // Eventually send the JSON response
- SendJsonResponse("upload");
- }
- else if (uploadState != uploadOK || !transaction->HasMoreDataToRead())
- {
- // We cannot read any more, discard the transaction again
- transaction->Discard();
- }
-}
-
-// Output to the client
-
-// Start sending a file or a JSON response.
-void Webserver::HttpInterpreter::SendFile(const char* nameOfFileToSend, bool isWebFile)
-{
- NetworkTransaction *transaction = webserver->currentTransaction;
- FileStore *fileToSend;
- bool zip = false;
-
- if (isWebFile)
- {
- if (nameOfFileToSend[0] == '/')
- {
- ++nameOfFileToSend; // all web files are relative to the /www folder, so remove the leading '/'
- if (nameOfFileToSend[0] == 0)
- {
- nameOfFileToSend = INDEX_PAGE_FILE;
- }
- }
- fileToSend = platform->GetFileStore(platform->GetWebDir(), nameOfFileToSend, false);
-
- // If we failed to open the file, see if we can open a file with the same name and a ".gz" extension
- if (fileToSend == nullptr && !StringEndsWith(nameOfFileToSend, ".gz") && strlen(nameOfFileToSend) + 3 <= FILENAME_LENGTH)
- {
- char nameBuf[FILENAME_LENGTH + 1];
- strcpy(nameBuf, nameOfFileToSend);
- strcat(nameBuf, ".gz");
- fileToSend = platform->GetFileStore(platform->GetWebDir(), nameBuf, false);
- if (fileToSend != nullptr)
- {
- zip = true;
- }
- }
-
- // If we still couldn't find the file and it was an HTML file, return the 404 error page
- if (fileToSend == nullptr && (StringEndsWith(nameOfFileToSend, ".html") || StringEndsWith(nameOfFileToSend, ".htm")))
- {
- nameOfFileToSend = FOUR04_PAGE_FILE;
- fileToSend = platform->GetFileStore(platform->GetWebDir(), nameOfFileToSend, false);
- }
-
- if (fileToSend == nullptr)
- {
- RejectMessage("not found", 404);
- return;
- }
- transaction->SetFileToWrite(fileToSend);
- }
- else
- {
- fileToSend = platform->GetFileStore(FS_PREFIX, nameOfFileToSend, false);
- if (fileToSend == nullptr)
- {
- nameOfFileToSend = FOUR04_PAGE_FILE;
- fileToSend = platform->GetFileStore(platform->GetWebDir(), nameOfFileToSend, false);
- if (fileToSend == nullptr)
- {
- RejectMessage("not found", 404);
- return;
- }
- }
- transaction->SetFileToWrite(fileToSend);
- }
-
- transaction->Write("HTTP/1.1 200 OK\n");
-
- // Don't cache files served by rr_download
- if (!isWebFile)
- {
- transaction->Write("Cache-Control: no-cache, no-store, must-revalidate\n");
- transaction->Write("Pragma: no-cache\n");
- transaction->Write("Expires: 0\n");
- }
-
- const char* contentType;
- if (StringEndsWith(nameOfFileToSend, ".png"))
- {
- contentType = "image/png";
- }
- else if (StringEndsWith(nameOfFileToSend, ".ico"))
- {
- contentType = "image/x-icon";
- }
- else if (StringEndsWith(nameOfFileToSend, ".js"))
- {
- contentType = "application/javascript";
- }
- else if (StringEndsWith(nameOfFileToSend, ".css"))
- {
- contentType = "text/css";
- }
- else if (StringEndsWith(nameOfFileToSend, ".htm") || StringEndsWith(nameOfFileToSend, ".html"))
- {
- contentType = "text/html";
- }
- else if (StringEndsWith(nameOfFileToSend, ".zip"))
- {
- contentType = "application/zip";
- zip = true;
- }
- else if (StringEndsWith(nameOfFileToSend, ".g") || StringEndsWith(nameOfFileToSend, ".gc") || StringEndsWith(nameOfFileToSend, ".gcode"))
- {
- contentType = "text/plain";
- }
- else
- {
- contentType = "application/octet-stream";
- }
- transaction->Printf("Content-Type: %s\n", contentType);
-
- if (zip && fileToSend != nullptr)
- {
- transaction->Write("Content-Encoding: gzip\n");
- transaction->Printf("Content-Length: %lu\n", fileToSend->Length());
- }
-
- transaction->Write("Connection: close\n\n");
- transaction->Commit(false);
-}
-
-void Webserver::HttpInterpreter::SendGCodeReply()
-{
- // Do we need to keep the G-Code reply for other clients?
- bool clearReply = false;
- if (!gcodeReply->IsEmpty())
- {
- clientsServed++;
- if (clientsServed < numSessions)
- {
- // Yes - make sure the Network class doesn't discard its buffers yet
- // NB: This must happen here, because NetworkTransaction::Write() might already release OutputBuffers
- gcodeReply->IncreaseReferences(1);
- }
- else
- {
- // No - clean up again later
- clearReply = true;
- }
-
- if (reprap.Debug(moduleWebserver))
- {
- platform->MessageF(HOST_MESSAGE, "Sending G-Code reply to client %d of %d (length %u)\n", clientsServed, numSessions, gcodeReply->DataLength());
- }
- }
-
- // Send the whole G-Code reply as plain text to the client
- NetworkTransaction *transaction = webserver->currentTransaction;
- transaction->Write("HTTP/1.1 200 OK\n");
- transaction->Write("Cache-Control: no-cache, no-store, must-revalidate\n");
- transaction->Write("Pragma: no-cache\n");
- transaction->Write("Expires: 0\n");
- transaction->Write("Content-Type: text/plain\n");
- transaction->Printf("Content-Length: %u\n", gcodeReply->DataLength());
- transaction->Write("Connection: close\n\n");
- transaction->Write(gcodeReply);
- transaction->Commit(false);
-
- // Possibly clean up the G-code reply once again
- if (clearReply)
- {
- gcodeReply->Clear();
- }
-}
-
-void Webserver::HttpInterpreter::SendJsonResponse(const char* command)
-{
- // Try to authorize the user automatically to retain compatibility with the old web interface
- if (!IsAuthenticated() && reprap.NoPasswordSet())
- {
- Authenticate();
- }
-
- // Update the authentication status and try handle "text/plain" requests here
- if (IsAuthenticated())
- {
- UpdateAuthentication();
-
- if (StringEquals(command, "reply")) // rr_reply
- {
- SendGCodeReply();
- return;
- }
-
- if (StringEquals(command, "configfile")) // rr_configfile [DEPRECATED]
- {
- const char *configPath = platform->GetMassStorage()->CombineName(platform->GetSysDir(), platform->GetConfigFile());
- char fileName[FILENAME_LENGTH];
- strncpy(fileName, configPath, FILENAME_LENGTH);
-
- SendFile(fileName, false);
- return;
- }
-
- if (StringEquals(command, "download") && StringEquals(qualifiers[0].key, "name"))
- {
- SendFile(qualifiers[0].value, false);
- return;
- }
- }
-
- // Try to process a request for JSON responses
- OutputBuffer *jsonResponse;
- if (!OutputBuffer::Allocate(jsonResponse))
- {
- // Reset the connection immediately if we cannot write any data. Should never happen
- webserver->currentTransaction->GetConnection()->Terminate();
- return;
- }
-
- bool keepOpen = false;
- bool mayKeepOpen;
- if (numQualKeys == 0)
- {
- GetJsonResponse(command, jsonResponse, "", "", 0, mayKeepOpen);
- }
- else
- {
- GetJsonResponse(command, jsonResponse, qualifiers[0].key, qualifiers[0].value, qualifiers[1].key - qualifiers[0].value - 1, mayKeepOpen);
- }
-
- // Check special cases of deferred requests (rr_fileinfo) and rejected messages
- NetworkTransaction *transaction = webserver->currentTransaction;
- if (transaction->GetStatus() == deferred || transaction->GetStatus() == sending)
- {
- OutputBuffer::Release(jsonResponse);
- return;
- }
-
- // Send the JSON response
-
- if (mayKeepOpen)
- {
- // Check that the browser wants to persist the connection too
- for (size_t i = 0; i < numHeaderKeys; ++i)
- {
- if (StringEquals(headers[i].key, "Connection"))
- {
- // Comment out the following line to disable persistent connections
- keepOpen = StringEquals(headers[i].value, "keep-alive");
- break;
- }
- }
- }
-
- transaction->Write("HTTP/1.1 200 OK\n");
- transaction->Write("Cache-Control: no-cache, no-store, must-revalidate\n");
- transaction->Write("Pragma: no-cache\n");
- transaction->Write("Expires: 0\n");
- transaction->Write("Content-Type: application/json\n");
- transaction->Printf("Content-Length: %u\n", (jsonResponse != nullptr) ? jsonResponse->Length() : 0);
- transaction->Printf("Connection: %s\n\n", keepOpen ? "keep-alive" : "close");
- transaction->Write(jsonResponse);
-
- transaction->Commit(keepOpen);
-}
-
-//----------------------------------------------------------------------------------------------------
-
-// Input from the client
-
-// Get the Json response for this command.
-// 'value' is null-terminated, but we also pass its length in case it contains embedded nulls, which matters when uploading files.
-void Webserver::HttpInterpreter::GetJsonResponse(const char* request, OutputBuffer *&response, const char* key, const char* value, size_t valueLength, bool& keepOpen)
-{
- keepOpen = false; // assume we don't want to persist the connection
-
- if (StringEquals(request, "connect") && StringEquals(key, "password"))
- {
- if (IsAuthenticated() || reprap.CheckPassword(value))
- {
- // Password OK
- if (Authenticate())
- {
- // See if we can update the current RTC date and time
- if (numQualKeys > 1 && StringEquals(qualifiers[1].key, "time") && !platform->IsDateTimeSet())
- {
- struct tm timeInfo;
- memset(&timeInfo, 0, sizeof(timeInfo));
- if (strptime(qualifiers[1].value, "%Y-%m-%dT%H:%M:%S", &timeInfo) != nullptr)
- {
- time_t newTime = mktime(&timeInfo);
- platform->SetDateTime(newTime);
- }
- }
-
- // Client has been logged in
- response->printf("{\"err\":0,\"sessionTimeout\":%u,\"boardType\":\"%s\"}", httpSessionTimeout, platform->GetBoardString());
- }
- else
- {
- // No more HTTP sessions available
- response->copy("{\"err\":2}");
- }
- }
- else
- {
- // Wrong password
- response->copy("{\"err\":1}");
- }
- }
- else if (!IsAuthenticated())
- {
- RejectMessage("Not authorized", 500);
- }
- else if (StringEquals(request, "disconnect"))
- {
- response->printf("{\"err\":%d}", RemoveAuthentication() ? 0 : 1);
- }
- else if (StringEquals(request, "status"))
- {
- int type = 0;
- if (StringEquals(key, "type"))
- {
- // New-style JSON status responses
- type = atoi(value);
- if (type < 1 || type > 3)
- {
- type = 1;
- }
-
- OutputBuffer::Release(response);
- response = reprap.GetStatusResponse(type, ResponseSource::HTTP);
- }
- else
- {
- // Deprecated
- OutputBuffer::Release(response);
- response = reprap.GetLegacyStatusResponse(1, 0);
- }
- }
- else if (StringEquals(request, "gcode") && StringEquals(key, "gcode"))
- {
- LoadGcodeBuffer(value);
- response->printf("{\"buff\":%u}", GetGCodeBufferSpace());
- }
- else if (StringEquals(request, "upload"))
- {
- response->printf("{\"err\":%d}", (uploadedBytes == postFileLength) ? 0 : 1);
- }
- else if (StringEquals(request, "delete") && StringEquals(key, "name"))
- {
- bool ok = platform->GetMassStorage()->Delete(FS_PREFIX, value);
- response->printf("{\"err\":%d}", (ok) ? 0 : 1);
- }
- else if (StringEquals(request, "filelist"))
- {
- OutputBuffer::Release(response);
- response = reprap.GetFilelistResponse(value);
- }
- else if (StringEquals(request, "files"))
- {
- const char* dir = (StringEquals(key, "dir")) ? value : platform->GetGCodeDir();
- bool flagDirs = false;
- if (numQualKeys >= 2)
- {
- if (StringEquals(qualifiers[1].key, "flagDirs"))
- {
- flagDirs = StringEquals(qualifiers[1].value, "1");
- }
- }
- OutputBuffer::Release(response);
- response = reprap.GetFilesResponse(dir, flagDirs);
- }
- else if (StringEquals(request, "fileinfo"))
- {
- if (deferredRequestConnection != nullptr)
- {
- // Don't allow multiple deferred requests to be processed at once
- webserver->currentTransaction->Defer(DeferralMode::ResetData);
- }
- else
- {
- if (StringEquals(qualifiers[0].key, "name"))
- {
- // Regular rr_fileinfo?name=xxx call
- strncpy(filenameBeingProcessed, value, ARRAY_SIZE(filenameBeingProcessed));
- filenameBeingProcessed[ARRAY_UPB(filenameBeingProcessed)] = 0;
- }
- else
- {
- // Simple rr_fileinfo call to get info about the file being printed
- filenameBeingProcessed[0] = 0;
- }
-
- deferredRequestConnection = webserver->currentTransaction->GetConnection();
- ProcessDeferredRequest();
- }
- }
- else if (StringEquals(request, "move"))
- {
- if (numQualKeys >= 2)
- {
- if (StringEquals(key, "old") && StringEquals(qualifiers[1].key, "new"))
- {
- response->printf("{\"err\":%d}", platform->GetMassStorage()->Rename(value, qualifiers[1].value) ? 0 : 1);
- }
- else
- {
- response->printf("{\"err\":1}");
- }
- }
- else
- {
- response->printf("{\"err\":1}");
- }
- }
- else if (StringEquals(request, "mkdir") && StringEquals(key, "dir"))
- {
- bool ok = (platform->GetMassStorage()->MakeDirectory(value));
- response->printf("{\"err\":%d}", (ok) ? 0 : 1);
- }
- else if (StringEquals(request, "config"))
- {
- OutputBuffer::Release(response);
- response = reprap.GetConfigResponse();
- }
- else
- {
- RejectMessage("Unknown request", 500);
- }
-}
-
-void Webserver::HttpInterpreter::ResetState()
-{
- clientPointer = 0;
- state = doingCommandWord;
- numCommandWords = 0;
- numQualKeys = 0;
- numHeaderKeys = 0;
- commandWords[0] = clientMessage;
-}
-
-void Webserver::HttpInterpreter::NoMoreDataAvailable()
-{
- RejectMessage("Incomplete or too long HTTP request", 500);
-}
-
-// May be called from ISR!
-void Webserver::HttpInterpreter::ConnectionLost(const ConnectionState *cs)
-{
- // Make sure deferred requests are cancelled
- if (deferredRequestConnection == cs)
- {
- reprap.GetPrintMonitor()->StopParsing(filenameBeingProcessed);
- deferredRequestConnection = nullptr;
- }
-
- // If we couldn't read an entire request from a connection, reset our state here again
- if (webserver->readingConnection == cs)
- {
- ResetState();
- }
-
- // Deal with aborted POST uploads. Note that we also check the remote port here,
- // because the client *might* have two instances of the web interface running.
- if (uploadState == uploadOK)
- {
- const uint32_t remoteIP = cs->GetRemoteIP();
- const uint16_t remotePort = cs->GetRemotePort();
- for(size_t i = 0; i < numSessions; i++)
- {
- if (sessions[i].ip == remoteIP && sessions[i].isPostUploading && sessions[i].postPort == remotePort)
- {
- if (reprap.Debug(moduleWebserver))
- {
- platform->MessageF(HOST_MESSAGE, "POST upload for '%s' has been cancelled!\n", filenameBeingUploaded);
- }
- sessions[i].isPostUploading = false;
- CancelUpload();
- break;
- }
- }
- }
-}
-
-bool Webserver::HttpInterpreter::CanParseData()
-{
- // We want to send a response, but we need memory for that. Check if we have to truncate the G-Code reply
- while (OutputBuffer::GetBytesLeft(nullptr) < minHttpResponseSize)
- {
- if (gcodeReply->IsEmpty())
- {
- // We cannot truncate any G-Code reply and don't have enough free space, try again later
- return false;
- }
-
- if (OutputBuffer::Truncate(gcodeReply->GetFirstItem(), minHttpResponseSize) == 0)
- {
- // Truncating didn't work out, but see if we can free up a few more bytes by releasing the first reply item
- OutputBuffer::ReleaseAll(gcodeReply->Pop());
- }
- }
-
- // Are we still processing a deferred request?
- if (deferredRequestConnection == webserver->currentTransaction->GetConnection())
- {
- if (deferredRequestConnection->IsConnected())
- {
- // Process more of this request. If it doesn't finish this time, it will be appended to the list
- // of ready transactions again, which will ensure it can be processed later again
- ProcessDeferredRequest();
- }
- else
- {
- // Don't bother with this request if the connection has been closed.
- // We expect a "disconnected" transaction to report this later, so don't clean up anything here
- webserver->currentTransaction->Discard();
- }
- return false;
- }
-
- return true;
-}
-
-// 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 processed the message and sent the reply. 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::HttpInterpreter::CharFromClient(char c)
-{
- switch(state)
- {
- case doingCommandWord:
- switch(c)
- {
- case '\n':
- clientMessage[clientPointer++] = 0;
- ++numCommandWords;
- numHeaderKeys = 0;
- headers[0].key = clientMessage + clientPointer;
- state = doingHeaderKey;
- break;
- case '\r':
- break;
- case ' ':
- case '\t':
- clientMessage[clientPointer++] = 0;
- if (numCommandWords < maxCommandWords)
- {
- ++numCommandWords;
- commandWords[numCommandWords] = clientMessage + clientPointer;
- if (numCommandWords == 1)
- {
- state = doingFilename;
- }
- }
- else
- {
- return RejectMessage("too many command words");
- }
- break;
- default:
- clientMessage[clientPointer++] = c;
- break;
- }
- break;
-
- case doingFilename:
- switch(c)
- {
- case '\n':
- clientMessage[clientPointer++] = 0;
- ++numCommandWords;
- numQualKeys = 0;
- numHeaderKeys = 0;
- headers[0].key = clientMessage + clientPointer;
- state = doingHeaderKey;
- break;
- case '?':
- clientMessage[clientPointer++] = 0;
- ++numCommandWords;
- numQualKeys = 0;
- qualifiers[0].key = clientMessage + clientPointer;
- state = doingQualifierKey;
- break;
- case '%':
- state = doingFilenameEsc1;
- break;
- case '\r':
- break;
- case ' ':
- case '\t':
- clientMessage[clientPointer++] = 0;
- if (numCommandWords < maxCommandWords)
- {
- ++numCommandWords;
- commandWords[numCommandWords] = clientMessage + clientPointer;
- state = doingCommandWord;
- }
- else
- {
- return RejectMessage("too many command words");
- }
- 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
- return RejectMessage("bad qualifier key");
- default:
- clientMessage[clientPointer++] = c;
- break;
- }
- break;
-
- case doingQualifierValue:
- switch(c)
- {
- case '\n':
- clientMessage[clientPointer++] = 0;
- qualifiers[numQualKeys].key = clientMessage + clientPointer; // so that we can read the whole value even if it contains a null
- numHeaderKeys = 0;
- headers[0].key = clientMessage + clientPointer;
- state = doingHeaderKey;
- break;
- case ' ':
- case '\t':
- clientMessage[clientPointer++] = 0;
- qualifiers[numQualKeys].key = clientMessage + clientPointer; // so that we can read the whole value even if it contains a null
- commandWords[numCommandWords] = clientMessage + clientPointer;
- state = doingCommandWord;
- break;
- case '\r':
- break;
- 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
- {
- return RejectMessage("too many keys in qualifier");
- }
- 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
- {
- return RejectMessage(badEscapeResponse);
- }
- 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
- {
- return RejectMessage(badEscapeResponse);
- }
- break;
-
- case doingHeaderKey:
- switch(c)
- {
- case '\n':
- if (clientMessage + clientPointer == headers[numHeaderKeys].key) // if the key hasn't started yet, then this is the blank line at the end
- {
- if (ProcessMessage())
- {
- return true;
- }
- }
- else
- {
- return RejectMessage("unexpected newline");
- }
- break;
- case '\r':
- break;
- case ':':
- if (numHeaderKeys == maxHeaders - 1)
- {
- return RejectMessage("too many header key-value pairs");
- }
- clientMessage[clientPointer++] = 0;
- headers[numHeaderKeys].value = clientMessage + clientPointer;
- ++numHeaderKeys;
- state = expectingHeaderValue;
- break;
- default:
- clientMessage[clientPointer++] = c;
- break;
- }
- break;
-
- case expectingHeaderValue:
- if (c == ' ' || c == '\t')
- {
- break; // ignore spaces between header key and value
- }
- state = doingHeaderValue;
- // no break
-
- case doingHeaderValue:
- if (c == '\n')
- {
- state = doingHeaderContinuation;
- }
- else if (c != '\r')
- {
- clientMessage[clientPointer++] = c;
- }
- break;
-
- case doingHeaderContinuation:
- switch(c)
- {
- case ' ':
- case '\t':
- // It's a continuation of the previous value
- clientMessage[clientPointer++] = c;
- state = doingHeaderValue;
- break;
- case '\n':
- // It's the blank line
- clientMessage[clientPointer] = 0;
- if (ProcessMessage())
- {
- return true;
- }
- // no break
- case '\r':
- break;
- default:
- // It's a new key
- if (clientPointer + 3 <= ARRAY_SIZE(clientMessage))
- {
- clientMessage[clientPointer++] = 0;
- headers[numHeaderKeys].key = clientMessage + clientPointer;
- clientMessage[clientPointer++] = c;
- state = doingHeaderKey;
- }
- else
- {
- return RejectMessage(overflowResponse);
- }
- break;
- }
- break;
-
- default:
- break;
- }
-
- if (clientPointer == ARRAY_SIZE(clientMessage))
- {
- return RejectMessage(overflowResponse);
- }
- return false;
-}
-
-// Process the message received so far. We have reached the end of the headers.
-// Return true if the message is complete, false if we want to continue receiving data (i.e. postdata)
-bool Webserver::HttpInterpreter::ProcessMessage()
-{
- if (reprap.Debug(moduleWebserver))
- {
- platform->MessageF(HOST_MESSAGE, "HTTP req, command words {", numCommandWords);
- for (size_t i = 0; i < numCommandWords; ++i)
- {
- platform->MessageF(HOST_MESSAGE, " %s", commandWords[i]);
- }
- platform->Message(HOST_MESSAGE, " }, parameters {");
-
- for (size_t i = 0; i < numQualKeys; ++i)
- {
- platform->MessageF(HOST_MESSAGE, " %s=%s", qualifiers[i].key, qualifiers[i].value);
- }
- platform->Message(HOST_MESSAGE, " }\n");
- }
-
- if (numCommandWords < 2)
- {
- return RejectMessage("too few command words");
- }
-
- if (StringEquals(commandWords[0], "GET"))
- {
- if (StringStartsWith(commandWords[1], KO_START))
- {
- SendJsonResponse(commandWords[1] + KO_FIRST);
- }
- else if (commandWords[1][0] == '/' && StringStartsWith(commandWords[1] + 1, KO_START))
- {
- SendJsonResponse(commandWords[1] + 1 + KO_FIRST);
- }
- else
- {
- SendFile(commandWords[1], true);
- }
-
- ResetState();
- return true;
- }
- else if (IsAuthenticated() && StringEquals(commandWords[0], "POST"))
- {
- bool isUploadRequest = (StringEquals(commandWords[1], KO_START "upload"));
- isUploadRequest |= (commandWords[1][0] == '/' && StringEquals(commandWords[1] + 1, KO_START "upload"));
- if (isUploadRequest)
- {
- if (numQualKeys > 0 && StringEquals(qualifiers[0].key, "name"))
- {
- // We cannot upload more than one file at once
- if (IsUploading())
- {
- return RejectMessage("cannot upload more than one file at once");
- }
-
- // See how many bytes we expect to read
- bool contentLengthFound = false;
- for(size_t i=0; i<numHeaderKeys; i++)
- {
- if (StringEquals(headers[i].key, "Content-Length"))
- {
- postFileLength = atoi(headers[i].value);
- contentLengthFound = true;
- break;
- }
- }
-
- // Start POST file upload
- if (!contentLengthFound)
- {
- return RejectMessage("invalid POST upload request");
- }
-
- // Start a new file upload
- FileStore *file = platform->GetFileStore(FS_PREFIX, qualifiers[0].value, true);
- if (!StartUpload(file, qualifiers[0].value))
- {
- return RejectMessage("could not start file upload");
- }
-
- // Try to get the last modified file date and time
- if (numQualKeys > 1 && StringEquals(qualifiers[1].key, "time"))
- {
- struct tm timeInfo;
- memset(&timeInfo, 0, sizeof(timeInfo));
- if (strptime(qualifiers[1].value, "%Y-%m-%dT%H:%M:%S", &timeInfo) != nullptr)
- {
- fileLastModified = mktime(&timeInfo);
- }
- else
- {
- fileLastModified = 0;
- }
- }
- else
- {
- fileLastModified = 0;
- }
-
- if (reprap.Debug(moduleWebserver))
- {
- platform->MessageF(HOST_MESSAGE, "Start uploading file %s length %lu\n", qualifiers[0].value, postFileLength);
- }
- uploadedBytes = 0;
-
- // Keep track of the connection that is now uploading
- uint32_t remoteIP = webserver->currentTransaction->GetRemoteIP();
- uint16_t remotePort = webserver->currentTransaction->GetRemotePort();
- for(size_t i = 0; i < numSessions; i++)
- {
- if (sessions[i].ip == remoteIP)
- {
- sessions[i].postPort = remotePort;
- sessions[i].isPostUploading = true;
- break;
- }
- }
-
- ResetState();
- return true;
- }
- }
- return RejectMessage("only rr_upload is supported for POST requests");
- }
- else
- {
- return RejectMessage("Unknown message type or not authenticated");
- }
-}
-
-// Reject the current message. Always returns true to indicate that we should stop reading the message.
-bool Webserver::HttpInterpreter::RejectMessage(const char* response, unsigned int code)
-{
- platform->MessageF(HOST_MESSAGE, "Webserver: rejecting message with: %s\n", response);
-
- NetworkTransaction *transaction = webserver->currentTransaction;
- transaction->Printf("HTTP/1.1 %u %s\nConnection: close\n\n", code, response);
- transaction->Commit(false);
-
- ResetState();
-
- return true;
-}
-
-// Authenticate current IP and return true on success
-bool Webserver::HttpInterpreter::Authenticate()
-{
- if (numSessions < maxHttpSessions)
- {
- sessions[numSessions].ip = webserver->currentTransaction->GetRemoteIP();
- sessions[numSessions].lastQueryTime = millis();
- sessions[numSessions].isPostUploading = false;
- numSessions++;
- return true;
- }
- return false;
-}
-
-bool Webserver::HttpInterpreter::IsAuthenticated() const
-{
- const uint32_t remoteIP = webserver->currentTransaction->GetRemoteIP();
- for(size_t i = 0; i < numSessions; i++)
- {
- if (sessions[i].ip == remoteIP)
- {
- return true;
- }
- }
- return false;
-}
-
-void Webserver::HttpInterpreter::UpdateAuthentication()
-{
- const uint32_t remoteIP = webserver->currentTransaction->GetRemoteIP();
- for(size_t i = 0; i < numSessions; i++)
- {
- if (sessions[i].ip == remoteIP)
- {
- sessions[i].lastQueryTime = millis();
- break;
- }
- }
-}
-
-bool Webserver::HttpInterpreter::RemoveAuthentication()
-{
- const uint32_t remoteIP = webserver->currentTransaction->GetRemoteIP();
- for(int i=(int)numSessions - 1; i>=0; i--)
- {
- if (sessions[i].ip == remoteIP)
- {
- if (sessions[i].isPostUploading)
- {
- // Don't allow sessions with active POST uploads to be removed
- return false;
- }
-
- for (size_t k = i + 1; k < numSessions; ++k)
- {
- memcpy(&sessions[k - 1], &sessions[k], sizeof(HttpSession));
- }
- numSessions--;
- return true;
- }
- }
- return false;
-}
-
-// Process a received string of gcodes
-void Webserver::HttpInterpreter::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::HttpInterpreter::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::HttpInterpreter::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;
- }
-}
-
-// Feeding G Codes to the GCodes class
-char Webserver::HttpInterpreter::ReadGCode()
-{
- char c;
- if (gcodeReadIndex == gcodeWriteIndex)
- {
- c = 0;
- }
- else
- {
- c = gcodeBuffer[gcodeReadIndex];
- gcodeReadIndex = (gcodeReadIndex + 1u) % gcodeBufferLength;
- }
- return c;
-}
-
-// Handle a G Code reply from the GCodes class
-void Webserver::HttpInterpreter::HandleGCodeReply(OutputBuffer *reply)
-{
- 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);
- }
- }
-}
-
-void Webserver::HttpInterpreter::HandleGCodeReply(const char *reply)
-{
- 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);
- clientsServed = 0;
- seq++;
- }
-}
-
-// Called to process a deferred request and takes care of the current Webserver transaction
-void Webserver::HttpInterpreter::ProcessDeferredRequest()
-{
- OutputBuffer *jsonResponse = nullptr;
- const ConnectionState *lastDeferredConnection = deferredRequestConnection;
-
- // At the moment only file info requests are deferred.
- // Parsing the file may take a while, so keep LwIP running while we're waiting
- network->Unlock();
- bool gotFileInfo = reprap.GetPrintMonitor()->GetFileInfoResponse(filenameBeingProcessed, jsonResponse);
- while (!network->Lock());
-
- // Because LwIP was unlocked before, there is a chance that the ConnectionLost() call has already
- // stopped the file parsing. Check this special case here
- if (lastDeferredConnection == deferredRequestConnection)
- {
- NetworkTransaction *transaction = webserver->currentTransaction;
- if (gotFileInfo)
- {
- deferredRequestConnection = nullptr;
-
- // Got it - send the response now
- transaction->Write("HTTP/1.1 200 OK\n");
- transaction->Write("Cache-Control: no-cache, no-store, must-revalidate\n");
- transaction->Write("Pragma: no-cache\n");
- transaction->Write("Expires: 0\n");
- transaction->Write("Content-Type: application/json\n");
- transaction->Printf("Content-Length: %u\n", (jsonResponse != nullptr) ? jsonResponse->Length() : 0);
- transaction->Printf("Connection: close\n\n");
- transaction->Write(jsonResponse);
-
- transaction->Commit(false);
- }
- else
- {
- // File hasn't been fully parsed yet, try again later
- transaction->Defer(DeferralMode::DiscardData);
- }
- }
- else
- {
- // Clean up again if we cannot send the response at all
- OutputBuffer::ReleaseAll(jsonResponse);
- }
-}
-
-//********************************************************************************************
-//
-//************************* FTP interpreter for the Webserver class **************************
-//
-//********************************************************************************************
-
-Webserver::FtpInterpreter::FtpInterpreter(Platform *p, Webserver *ws, Network *n)
- : ProtocolInterpreter(p, ws, n), state(authenticating), clientPointer(0)
-{
- connectedClients = 0;
- strcpy(currentDir, "/");
-}
-
-void Webserver::FtpInterpreter::Diagnostics(MessageType mt)
-{
- platform->MessageF(mt, "FTP connections: %d, state %d\n", connectedClients, state);
-}
-
-void Webserver::FtpInterpreter::ConnectionEstablished()
-{
- connectedClients++;
- if (reprap.Debug(moduleWebserver))
- {
- platform->Message(HOST_MESSAGE, "Webserver: FTP connection established!\n");
- }
-
- // Is this a new connection on the data port?
- NetworkTransaction *transaction = webserver->currentTransaction;
- if (transaction->GetLocalPort() != FTP_PORT)
- {
- if (state == waitingForPasvPort)
- {
- // Yes - save it for the main request
- network->SaveDataConnection();
- state = pasvPortConnected;
- transaction->Discard();
- }
- else
- {
- // Should never get here...
- transaction->Commit(false);
- }
- return;
- }
-
- // A client is trying to connect to the main FTP port
- switch (state)
- {
- case idle:
- case authenticated: // added by DC because without it, we can't transfer any files with FileZilla
- // We can safely deal with one connection on the main FTP port
- state = authenticating;
- SendReply(220, "RepRapFirmware FTP server", true);
- break;
-
- default:
- // But don't allow multiple ones, this could mess things up
- SendReply(421, "Only one client can be connected at a time.", false);
- return;
- }
-}
-
-// May be called from ISR!
-void Webserver::FtpInterpreter::ConnectionLost(const ConnectionState *cs)
-{
- connectedClients--;
-
- if (cs->GetLocalPort() != FTP_PORT)
- {
- // Did everything work out? Usually this is only called for uploads
- if (network->AcquireFTPTransaction())
- {
- webserver->currentTransaction = network->GetTransaction();
- if (state == doingPasvIO)
- {
- if (uploadState != uploadError && !cs->IsTerminated())
- {
- SendReply(226, "Transfer complete.");
- FinishUpload(0);
- }
- else
- {
- SendReply(526, "Transfer failed!");
- }
- }
- else
- {
- SendReply(550, "Lost data connection!");
- }
- }
-
- // Close the data port and reset our state again
- network->CloseDataPort();
- CancelUpload();
- state = authenticated;
- }
-
- if (connectedClients == 0)
- {
- // Last one gone now...
- ResetState();
- }
-}
-
-bool Webserver::FtpInterpreter::CharFromClient(char c)
-{
- if (clientPointer == ARRAY_UPB(clientMessage))
- {
- clientPointer = 0;
- platform->Message(HOST_MESSAGE, "Webserver: Buffer overflow in FTP server!\n");
- return true;
- }
-
- switch (c)
- {
- case 0:
- break;
-
- case '\r':
- case '\n':
- clientMessage[clientPointer++] = 0;
-
- if (reprap.Debug(moduleWebserver))
- {
- platform->MessageF(HOST_MESSAGE, "FtpInterpreter::ProcessLine called with state %d:\n%s\n", state, clientMessage);
- }
-
- if (clientPointer > 1) // only process a new line if we actually received data
- {
- ProcessLine();
- clientPointer = 0;
- return true;
- }
-
- if (reprap.Debug(moduleWebserver))
- {
- platform->Message(HOST_MESSAGE, "FtpInterpreter::ProcessLine call finished.\n");
- }
-
- clientPointer = 0;
- break;
-
- default:
- clientMessage[clientPointer++] = c;
- break;
- }
-
- return false;
-}
-
-void Webserver::FtpInterpreter::ResetState()
-{
- clientPointer = 0;
- strcpy(currentDir, "/");
-
- network->CloseDataPort();
- CancelUpload();
-
- state = idle;
-}
-
-bool Webserver::FtpInterpreter::DoingFastUpload() const
-{
- return (IsUploading() && webserver->currentTransaction->GetLocalPort() == network->GetDataPort());
-}
-
-// return true if an error has occurred, false otherwise
-void Webserver::FtpInterpreter::ProcessLine()
-{
- switch (state)
- {
- case idle:
- case authenticating:
- // don't check the user name
- if (StringStartsWith(clientMessage, "USER"))
- {
- SendReply(331, "Please specify the password.");
- }
- // but check the password
- else if (StringStartsWith(clientMessage, "PASS"))
- {
- char pass[PASSWORD_LENGTH];
- int pass_length = 0;
- bool reading_pass = false;
- for(size_t i = 4; i < clientPointer && i < PASSWORD_LENGTH + 3; i++)
- {
- reading_pass |= (clientMessage[i] != ' ' && clientMessage[i] != '\t');
- if (reading_pass)
- {
- pass[pass_length++] = clientMessage[i];
- }
- }
- pass[pass_length] = 0;
-
- if (reprap.CheckPassword(pass))
- {
- state = authenticated;
- SendReply(230, "Login successful.");
- }
- else
- {
- SendReply(530, "Login incorrect.", false);
- }
- }
- // if it's different, send response 500 to indicate we don't know the code (might be AUTH or so)
- else
- {
- SendReply(500, "Unknown login command.");
- }
-
- break;
-
- case authenticated:
- // get system type
- if (StringEquals(clientMessage, "SYST"))
- {
- SendReply(215, "UNIX Type: L8");
- }
- // get features
- else if (StringEquals(clientMessage, "FEAT"))
- {
- SendFeatures();
- }
- // get current dir
- else if (StringEquals(clientMessage, "PWD"))
- {
- NetworkTransaction *transaction = webserver->currentTransaction;
- transaction->Printf("257 \"%s\"\r\n", currentDir);
- transaction->Commit(true);
- }
- // set current dir
- else if (StringStartsWith(clientMessage, "CWD"))
- {
- ReadFilename(3);
- ChangeDirectory(filename);
- }
- // change to parent of current directory
- else if (StringEquals(clientMessage, "CDUP"))
- {
- ChangeDirectory("..");
- }
- // switch transfer mode (sends response, but doesn't have any effects)
- else if (StringStartsWith(clientMessage, "TYPE"))
- {
- for(size_t i = 4; i < clientPointer; i++)
- {
- if (clientMessage[i] == 'I')
- {
- SendReply(200, "Switching to Binary mode.");
- return;
- }
-
- if (clientMessage[i] == 'A')
- {
- SendReply(200, "Switching to ASCII mode.");
- return;
- }
- }
-
- SendReply(500, "Unknown command.");
- }
- // enter passive mode mode
- else if (StringEquals(clientMessage, "PASV"))
- {
- /* get local IP address */
- const uint8_t * const ip_address = network->GetIPAddress();
-
- /* open random port > 1023 */
- //rand(); // TRNG doesn't require this
- uint16_t pasv_port = random(1024, 65535);
- network->OpenDataPort(pasv_port);
- portOpenTime = millis();
- state = waitingForPasvPort;
-
- /* send FTP response */
- NetworkTransaction *transaction = webserver->currentTransaction;
- transaction->Printf("227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\r\n",
- ip_address[0], ip_address[1], ip_address[2], ip_address[3],
- pasv_port / 256, pasv_port % 256);
- transaction->Commit(true);
- }
- // PASV commands are not supported in this state
- else if (StringEquals(clientMessage, "LIST") || StringStartsWith(clientMessage, "RETR") || StringStartsWith(clientMessage, "STOR"))
- {
- SendReply(425, "Use PASV first.");
- }
- // delete file
- else if (StringStartsWith(clientMessage, "DELE"))
- {
- ReadFilename(4);
- if (platform->GetMassStorage()->Delete(currentDir, filename))
- {
- SendReply(250, "Delete operation successful.");
- }
- else
- {
- SendReply(550, "Delete operation failed.");
- }
- }
- // delete directory
- else if (StringStartsWith(clientMessage, "RMD"))
- {
- ReadFilename(3);
- if (platform->GetMassStorage()->Delete(currentDir, filename))
- {
- SendReply(250, "Remove directory operation successful.");
- }
- else
- {
- SendReply(550, "Remove directory operation failed.");
- }
- }
- // make new directory
- else if (StringStartsWith(clientMessage, "MKD"))
- {
- ReadFilename(3);
- const char *location = (filename[0] == '/')
- ? filename
- : platform->GetMassStorage()->CombineName(currentDir, filename);
-
- if (platform->GetMassStorage()->MakeDirectory(location))
- {
- NetworkTransaction *transaction = webserver->currentTransaction;
- transaction->Printf("257 \"%s\" created\r\n", location);
- transaction->Commit(true);
- }
- else
- {
- SendReply(550, "Create directory operation failed.");
- }
- }
- // rename file or directory
- else if (StringStartsWith(clientMessage, "RNFR"))
- {
- ReadFilename(4);
- if (filename[0] != '/')
- {
- const char *temp = platform->GetMassStorage()->CombineName(currentDir, filename);
- strncpy(filename, temp, FILENAME_LENGTH);
- filename[FILENAME_LENGTH - 1] = 0;
- }
-
- if (platform->GetMassStorage()->FileExists(filename))
- {
- SendReply(350, "Ready to RNTO.");
- }
- else
- {
- SendReply(550, "Invalid file or directory.");
- }
- }
- else if (StringStartsWith(clientMessage, "RNTO"))
- {
- // Copy origin path to temp oldFilename and read new path
- char oldFilename[FILENAME_LENGTH];
- strncpy(oldFilename, filename, FILENAME_LENGTH);
- oldFilename[FILENAME_LENGTH - 1] = 0;
- ReadFilename(4);
-
- const char *newFilename = platform->GetMassStorage()->CombineName(currentDir, filename);
- if (platform->GetMassStorage()->Rename(oldFilename, newFilename))
- {
- SendReply(250, "Rename successful.");
- }
- else
- {
- SendReply(500, "Could not rename file or directory.");
- }
- }
- // no op
- else if (StringEquals(clientMessage, "NOOP"))
- {
- SendReply(200, "NOOP okay.");
- }
- // end connection
- else if (StringEquals(clientMessage, "QUIT"))
- {
- SendReply(221, "Goodbye.", false);
- ResetState();
- }
- // unknown
- else
- {
- SendReply(500, "Unknown command.");
- }
-
- break;
-
- case waitingForPasvPort:
- if (millis() - portOpenTime > ftpPasvPortTimeout)
- {
- SendReply(425, "Failed to establish connection.");
-
- network->CloseDataPort();
- state = authenticated;
- }
- else
- {
- webserver->currentTransaction->Defer(DeferralMode::ResetData);
- }
-
- break;
-
- case pasvPortConnected:
- // save current connection state so we can send '226 Transfer complete.' when ConnectionLost() is called
- network->SaveFTPConnection();
-
- // list directory entries
- if (StringEquals(clientMessage, "LIST"))
- {
- if (network->AcquireDataTransaction())
- {
- // send announcement via ftp main port
- SendReply(150, "Here comes the directory listing.");
-
- // send directory listing via data port
- NetworkTransaction *dataTransaction = network->GetTransaction();
-
- FileInfo fileInfo;
- if (platform->GetMassStorage()->FindFirst(currentDir, fileInfo))
- {
- do {
- // Example for a typical UNIX-like file list:
- // "drwxr-xr-x 2 ftp ftp 0 Apr 11 2013 bin\r\n"
- const char dirChar = (fileInfo.isDirectory) ? 'd' : '-';
- const struct tm * const timeInfo = gmtime(&fileInfo.lastModified);
- dataTransaction->Printf("%crw-rw-rw- 1 ftp ftp %13lu %s %02d %04d %s\r\n",
- dirChar, fileInfo.size, platform->GetMassStorage()->GetMonthName(timeInfo->tm_mon + 1),
- timeInfo->tm_mday, timeInfo->tm_year + 1900, fileInfo.fileName);
- } while (platform->GetMassStorage()->FindNext(fileInfo));
- }
-
- dataTransaction->Commit(false);
- state = doingPasvIO;
- }
- else
- {
- SendReply(500, "Unknown error.");
- network->CloseDataPort();
- state = authenticated;
- }
- }
- // upload a file
- else if (StringStartsWith(clientMessage, "STOR"))
- {
- ReadFilename(4);
-
- FileStore *file = platform->GetFileStore(currentDir, filename, true);
- if (StartUpload(file, filename))
- {
- SendReply(150, "OK to send data.");
- state = doingPasvIO;
- }
- else
- {
- SendReply(550, "Failed to open file.");
- network->CloseDataPort();
- state = authenticated;
- }
- }
- // download a file
- else if (StringStartsWith(clientMessage, "RETR"))
- {
- ReadFilename(4);
-
- FileStore *file = platform->GetFileStore(currentDir, filename, false);
- if (file == nullptr)
- {
- SendReply(550, "Failed to open file.");
- }
- else
- {
- if (network->AcquireDataTransaction())
- {
- // send announcement via main ftp port
- NetworkTransaction *transaction = webserver->currentTransaction;
- transaction->Printf("150 Opening data connection for %s (%lu bytes).\r\n", filename, file->Length());
- transaction->Commit(true);
-
- // send the file via data port
- NetworkTransaction *dataTransaction = network->GetTransaction();
- dataTransaction->SetFileToWrite(file);
- dataTransaction->Commit(false);
- state = doingPasvIO;
- }
- else
- {
- file->Close();
- SendReply(500, "Unknown error.");
- network->CloseDataPort();
- state = authenticated;
- }
- }
- }
- // unknown command
- else
- {
- SendReply(500, "Unknown command.");
- network->CloseDataPort();
- state = authenticated;
- }
-
- break;
-
- case doingPasvIO:
- // abort current transfer
- if (StringEquals(clientMessage, "ABOR"))
- {
- if (IsUploading())
- {
- CancelUpload();
- SendReply(226, "ABOR successful.");
- }
- else
- {
- network->CloseDataPort();
- SendReply(226, "ABOR successful.");
- }
- }
- // unknown command
- else
- {
- SendReply(500, "Unknown command.");
- network->CloseDataPort();
- state = authenticated;
- }
-
- break;
- }
-}
-
-void Webserver::FtpInterpreter::SendReply(int code, const char *message, bool keepConnection)
-{
- NetworkTransaction *transaction = webserver->currentTransaction;
- transaction->Printf("%d %s\r\n", code, message);
- transaction->Commit(keepConnection);
-}
-
-void Webserver::FtpInterpreter::SendFeatures()
-{
- NetworkTransaction *transaction = webserver->currentTransaction;
- transaction->Write("211-Features:\r\n");
- transaction->Write("PASV\r\n"); // support PASV mode
- transaction->Write("211 End\r\n");
- transaction->Commit(true);
-}
-
-void Webserver::FtpInterpreter::ReadFilename(uint16_t start)
-{
- int filenameLength = 0;
- bool readingPath = false;
- for(int i = start; i < (int)clientPointer && filenameLength < (int)(FILENAME_LENGTH - 1); i++)
- {
- switch (clientMessage[i])
- {
- // ignore quotes
- case '"':
- case '\'':
- break;
-
- // skip whitespaces unless the actual filename is being read
- case ' ':
- case '\t':
- if (readingPath)
- {
- filename[filenameLength++] = clientMessage[i];
- }
- break;
-
- // read path name
- default:
- readingPath = true;
- filename[filenameLength++] = clientMessage[i];
- break;
- }
- }
- filename[filenameLength] = 0;
-}
-
-void Webserver::FtpInterpreter::ChangeDirectory(const char *newDirectory)
-{
- char combinedPath[FILENAME_LENGTH];
-
- if (newDirectory[0] != 0)
- {
- /* Prepare the new directory path */
- if (newDirectory[0] == '/') // absolute path
- {
- strncpy(combinedPath, newDirectory, FILENAME_LENGTH);
- combinedPath[FILENAME_LENGTH - 1] = 0;
- }
- else // relative path
- {
- if (StringEquals(newDirectory, "..")) // go up
- {
- if (StringEquals(currentDir, "/"))
- {
- // we're already at the root, so we can't go up any more
- SendReply(550, "Failed to change directory.");
- return;
- }
- else
- {
- strncpy(combinedPath, currentDir, FILENAME_LENGTH);
- for(int i=strlen(combinedPath) -2; i>=0; i--)
- {
- if (combinedPath[i] == '/')
- {
- combinedPath[i +1] = 0;
- break;
- }
- }
- }
- }
- else // go to child directory
- {
- strncpy(combinedPath, currentDir, FILENAME_LENGTH);
- if (strlen(currentDir) > 1)
- {
- strncat(combinedPath, "/", FILENAME_LENGTH - strlen(combinedPath) - 1);
- }
- strncat(combinedPath, newDirectory, FILENAME_LENGTH - strlen(combinedPath) - 1);
- }
- }
-
- /* Make sure the new path does not end with a '/', because FatFs won't see the directory otherwise */
- if (StringEndsWith(combinedPath, "/") && strlen(combinedPath) > 1)
- {
- combinedPath[strlen(combinedPath) -1] = 0;
- }
-
- /* Verify path and change it */
- if (platform->GetMassStorage()->DirectoryExists(combinedPath))
- {
- strncpy(currentDir, combinedPath, FILENAME_LENGTH);
- SendReply(250, "Directory successfully changed.");
- }
- else
- {
- SendReply(550, "Failed to change directory.");
- }
- }
- else
- {
- SendReply(550, "Failed to change directory.");
- }
-}
-
-
-//********************************************************************************************
-//
-//*********************** Telnet interpreter for the Webserver class *************************
-//
-//********************************************************************************************
-
-Webserver::TelnetInterpreter::TelnetInterpreter(Platform *p, Webserver *ws, Network *n)
- : ProtocolInterpreter(p, ws, n), connectedClients(0), processNextLine(false), gcodeReadIndex(0), gcodeWriteIndex(0), gcodeReply(nullptr)
-{
- ResetState();
-}
-
-void Webserver::TelnetInterpreter::Diagnostics(MessageType mt)
-{
- platform->MessageF(mt, "Telnet connections: %d, state %d\n", connectedClients, state);
-}
-
-void Webserver::TelnetInterpreter::ConnectionEstablished()
-{
- connectedClients++;
- NetworkTransaction *transaction = network->GetTransaction();
-
- // Only one client may be connected via Telnet at once, so check this first
- if (state != idle)
- {
- transaction->Write("Sorry, only one client may be connected via Telnet at once.\r\n");
- transaction->Commit(false);
- return;
- }
- state = justConnected;
- connectTime = millis();
-
- // Check whether we need a password to log in
- if (reprap.NoPasswordSet())
- {
- // Don't send a login prompt if no password is set, so we don't mess up Pronterface
- transaction->Discard();
- }
- else
- {
- transaction->Write("RepRapFirmware Telnet interface\r\n\r\n");
- transaction->Write("Please enter your password:\r\n");
- transaction->Write("> ");
- transaction->Commit(true);
- }
-}
-
-// May be called from ISR!
-void Webserver::TelnetInterpreter::ConnectionLost(const ConnectionState *cs)
-{
- connectedClients--;
- if (connectedClients == 0)
- {
- ResetState();
-
- // Don't save up output buffers if they can't be sent
- OutputBuffer::ReleaseAll(gcodeReply);
- gcodeReply = nullptr;
- }
-}
-
-bool Webserver::TelnetInterpreter::CanParseData()
-{
- // Is this an acquired transaction using which we can send the G-code reply?
- TransactionStatus status = webserver->currentTransaction->GetStatus();
- if (status == acquired)
- {
- SendGCodeReply();
- return false;
- }
-
- // Is this connection still live? Check that for deferred requests
- if (status == deferred && !webserver->currentTransaction->IsConnected())
- {
- webserver->currentTransaction->Discard();
- return false;
- }
-
- // In order to support TCP streaming mode, check if we can store any more data at this time
- if (GetGCodeBufferSpace() < clientPointer + 1)
- {
- webserver->currentTransaction->Defer(DeferralMode::DeferOnly);
- return false;
- }
-
- // If that works and if the next line hasn't been processed yet, do it now
- if (processNextLine)
- {
- return !ProcessLine();
- }
-
- // Otherwise just parse the next request
- return true;
-}
-
-bool Webserver::TelnetInterpreter::CharFromClient(char c)
-{
- // If this is likely to be a Telnet setup message (with some garbage in it), dump the first
- // received packet and move on to the next state
- if (state == justConnected)
- {
- if (reprap.NoPasswordSet())
- {
- state = authenticated;
- network->SaveTelnetConnection();
- }
- else
- {
- state = authenticating;
- }
-
- if (millis() - connectTime < telnetSetupDuration)
- {
- network->GetTransaction()->Discard();
- return true;
- }
- }
-
- // Otherwise try to read one line at a time
- switch (c)
- {
- case 0:
- break;
-
- case '\b':
- // Allow backspace for pure Telnet clients like PuTTY
- if (clientPointer != 0)
- {
- clientPointer--;
- }
- break;
-
- case '\r':
- case '\n':
- if (clientPointer != 0)
- {
- // This line is complete, do we have enough space left to store it?
- clientMessage[clientPointer] = 0;
- if (GetGCodeBufferSpace() < clientPointer + 1)
- {
- // No - defer this transaction, so we can process more of it next time
- webserver->currentTransaction->Defer(DeferralMode::DeferOnly);
- processNextLine = true;
- return true;
- }
-
- // Yes - try to process it
- return ProcessLine();
- }
- break;
-
- default:
- clientMessage[clientPointer++] = c;
-
- // Make sure we don't overflow the line buffer
- if (clientPointer == ARRAY_UPB(clientMessage))
- {
- clientPointer = 0;
- platform->Message(HOST_MESSAGE, "Webserver: Buffer overflow in Telnet server!\n");
- return true;
- }
- break;
- }
-
- return false;
-}
-
-void Webserver::TelnetInterpreter::ResetState()
-{
- state = idle;
- connectTime = 0;
- clientPointer = 0;
- gcodeReadIndex = gcodeWriteIndex; // clear the buffer
-}
-
-// Usually we should not try to send any data here, because that would purge the packet's
-// payload and mess with TCP streaming mode if Pronterface is used. However, under special
-// circumstances this must happen and in this case this method must always return true.
-bool Webserver::TelnetInterpreter::ProcessLine()
-{
- processNextLine = false;
- clientPointer = 0;
-
- NetworkTransaction *transaction = network->GetTransaction();
- switch (state)
- {
- case idle:
- case justConnected:
- // Should never get here...
- // no break
-
- case authenticating:
- if (reprap.CheckPassword(clientMessage))
- {
- network->SaveTelnetConnection();
- state = authenticated;
-
- transaction->Write("Log in successful!\r\n");
- transaction->Commit(true);
- }
- else
- {
- transaction->Write("Invalid password.\r\n> ");
- transaction->Commit(true);
- }
- return true;
-
- case authenticated:
- // Special commands for Telnet
- if (StringEquals(clientMessage, "exit") || StringEquals(clientMessage, "quit"))
- {
- transaction->Write("Goodbye.\r\n");
- transaction->Commit(false);
- return true;
- }
- // All other codes are stored for the GCodes class
- ProcessGcode(clientMessage);
- break;
- }
- return false;
-}
-
-// 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::TelnetInterpreter::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::TelnetInterpreter::StoreGcodeData(const char* data, uint16_t len)
-{
- if (len > GetGCodeBufferSpace())
- {
- platform->Message(HOST_MESSAGE, "Error: GCode buffer overflow in Telnet 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;
- }
-}
-
-// Feeding G Codes to the GCodes class
-char Webserver::TelnetInterpreter::ReadGCode()
-{
- char c;
- if (gcodeReadIndex == gcodeWriteIndex)
- {
- c = 0;
- }
- else
- {
- c = gcodeBuffer[gcodeReadIndex];
- gcodeReadIndex = (gcodeReadIndex + 1u) % gcodeBufferLength;
- }
- return c;
-}
-
-// Handle a G-Code reply from the GCodes class; replace \n with \r\n
-void Webserver::TelnetInterpreter::HandleGCodeReply(OutputBuffer *reply)
-{
- if (reply != nullptr && state >= authenticated)
- {
- if (!network->AcquireTelnetTransaction())
- {
- // We must be able to send the response to the client on the next Spin call
- return;
- }
-
- // We need a valid OutputBuffer to start the conversion from NL to CRNL
- if (gcodeReply == nullptr)
- {
- OutputBuffer *buffer;
- if (!OutputBuffer::Allocate(buffer))
- {
- OutputBuffer::Truncate(reply, OUTPUT_BUFFER_SIZE);
- if (!OutputBuffer::Allocate(buffer))
- {
- // If we're really short on memory, release the G-Code reply instantly
- OutputBuffer::ReleaseAll(reply);
- return;
- }
- }
- gcodeReply = buffer;
- }
-
- // Write entire content to new output buffers, but this time with \r\n instead of \n
- do {
- const char *data = reply->Data();
- for(size_t i = 0; i < reply->DataLength(); i++)
- {
- if (*data == '\n')
- {
- gcodeReply->cat('\r');
- }
-
- gcodeReply->cat(*data);
- data++;
- }
- reply = OutputBuffer::Release(reply);
- } while (reply != nullptr);
- }
- else
- {
- // Don't store buffers that may never get released...
- OutputBuffer::ReleaseAll(reply);
- }
-}
-
-void Webserver::TelnetInterpreter::HandleGCodeReply(const char *reply)
-{
- if (reply != nullptr && state >= authenticated)
- {
- if (!network->AcquireTelnetTransaction())
- {
- // We must be able to send the response to the client on the next Spin call
- return;
- }
-
- // We need a valid OutputBuffer to start the conversion from NL to CRNL
- if (gcodeReply == nullptr)
- {
- OutputBuffer *buffer;
- if (!OutputBuffer::Allocate(buffer))
- {
- // No more space available to store this reply, stop here
- return;
- }
- gcodeReply = buffer;
- }
-
- // Write entire content to new output buffers, but this time with \r\n instead of \n
- while (*reply != 0)
- {
- if (*reply == '\n' && gcodeReply->cat('\r') == 0)
- {
- // No more space available, stop here
- return;
- }
- if (gcodeReply->cat(*reply) == 0)
- {
- // No more space available, stop here
- return;
- }
- reply++;
- };
- }
-}
-
-void Webserver::TelnetInterpreter::SendGCodeReply()
-{
- NetworkTransaction *transaction = webserver->currentTransaction;
-
- if (gcodeReply == nullptr)
- {
- transaction->Discard();
- }
- else
- {
- transaction->Write(gcodeReply);
- transaction->Commit(true);
- }
-
- gcodeReply = nullptr;
-}
-
-// vim: ts=4:sw=4
diff --git a/src/DuetNG/DuetEthernet/Webserver.h b/src/DuetNG/DuetEthernet/Webserver.h
deleted file mode 100644
index 1b4975bc..00000000
--- a/src/DuetNG/DuetEthernet/Webserver.h
+++ /dev/null
@@ -1,384 +0,0 @@
-/****************************************************************************************************
-
-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
-
-/* Generic values */
-
-const size_t gcodeBufferLength = 512; // size of our gcode ring buffer, preferably a power of 2
-const unsigned int TCP_MSS = 1460;
-
-class ConnectionState;
-class NetworkTransaction;
-
-/* HTTP */
-
-#define KO_START "rr_"
-#define KO_FIRST 3
-
-const uint16_t webMessageLength = TCP_MSS; // maximum length of the web message we accept after decoding
-const size_t minHttpResponseSize = 768; // minimum number of bytes required for an HTTP response
-
-const size_t maxCommandWords = 4; // max number of space-separated words in the command
-const size_t maxQualKeys = 5; // max number of key/value pairs in the qualifier
-const size_t maxHeaders = 16; // max number of key/value pairs in the headers
-
-const size_t maxHttpSessions = 8; // maximum number of simultaneous HTTP sessions
-const uint32_t httpSessionTimeout = 8000; // HTTP session timeout in milliseconds
-
-/* FTP */
-
-const uint16_t ftpMessageLength = 128; // maximum line length for incoming FTP commands
-const uint32_t ftpPasvPortTimeout = 10000; // maximum time to wait for an FTP data connection in milliseconds
-
-/* Telnet */
-
-const uint32_t telnetSetupDuration = 4000; // ignore the first Telnet request within this duration (in ms)
-
-
-class Webserver;
-
-// List of protocols that can execute G-Codes
-enum class WebSource
-{
- HTTP,
- Telnet
-};
-
-// This is the abstract class for all supported protocols
-// Any inherited class should implement a state machine to increase performance and reduce memory usage.
-class ProtocolInterpreter
-{
- public:
-
- ProtocolInterpreter(Platform *p, Webserver *ws, Network *n);
- virtual ~ProtocolInterpreter() { } // to keep Eclipse happy
- virtual void Diagnostics(MessageType mtype) = 0;
- virtual void Spin();
-
- virtual void ConnectionEstablished();
- virtual void ConnectionLost(const ConnectionState *cs) { }
- virtual bool CanParseData();
- virtual bool CharFromClient(const char c) = 0;
- virtual void NoMoreDataAvailable();
-
- virtual bool DoingFastUpload() const;
- virtual void DoFastUpload();
- void CancelUpload(); // may be called from ISR!
-
- protected:
-
- Platform *platform;
- Webserver *webserver;
- Network *network;
-
- // Information for file uploading
- enum UploadState
- {
- notUploading, // no upload in progress
- uploadOK, // upload in progress, no error so far
- uploadError // upload in progress but had error
- };
-
- UploadState uploadState;
- FileData fileBeingUploaded;
- char filenameBeingUploaded[FILENAME_LENGTH];
-
- bool StartUpload(FileStore *file, const char *fileName);
- bool IsUploading() const;
- bool FinishUpload(uint32_t fileLength);
-};
-
-class Webserver
-{
- public:
-
- friend class Platform;
- friend class ProtocolInterpreter;
-
- 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;
-
- // 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;
-
- void ConnectionLost(const ConnectionState *cs);
- void ConnectionError();
-
- protected:
-
- class HttpInterpreter : public ProtocolInterpreter
- {
- public:
-
- HttpInterpreter(Platform *p, Webserver *ws, Network *n);
- void Spin();
- void Diagnostics(MessageType mtype) override;
- void ConnectionLost(const ConnectionState *cs);
- bool CanParseData() override;
- bool CharFromClient(const char c) override;
- void NoMoreDataAvailable() override;
- void ResetState();
- void ResetSessions();
-
- bool DoingFastUpload() const override;
- void DoFastUpload();
-
- bool GCodeAvailable() const;
- char ReadGCode();
- void HandleGCodeReply(OutputBuffer *reply);
- void HandleGCodeReply(const char *reply);
- uint16_t GetGCodeBufferSpace() const;
- uint32_t GetReplySeq() const;
-
- private:
-
- // HTTP server state enumeration. The order is important, in particular xxxEsc1 must follow xxx, and xxxEsc2 must follow xxxEsc1.
- // We assume that qualifier keys do not contain escapes, because none of ours needs to be encoded. If we are sent escapes in the key,
- // it won't do any harm, but the key won't be recognised even if it would be valid were it decoded.
- enum HttpState
- {
- doingCommandWord, // receiving a word in the first line of the HTTP request
- 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
- doingHeaderKey, // receiving a header key
- expectingHeaderValue, // expecting a header value
- doingHeaderValue, // receiving a header value
- doingHeaderContinuation // received a newline after a header value
- };
- HttpState state;
-
- struct KeyValueIndices
- {
- const char* key;
- const char* value;
- };
-
- void SendFile(const char* nameOfFileToSend, bool isWebFile);
- void SendGCodeReply();
- void SendJsonResponse(const char* command);
- void GetJsonResponse(const char* request, OutputBuffer *&response, const char* key, const char* value, size_t valueLength, bool& keepOpen);
- bool ProcessMessage();
- bool RejectMessage(const char* s, unsigned int code = 500);
-
- // Buffers for processing HTTP input
- char clientMessage[webMessageLength + 3]; // holds the command, qualifier, and headers
- size_t clientPointer; // current index into clientMessage
- char decodeChar;
-
- const char* commandWords[maxCommandWords];
- KeyValueIndices qualifiers[maxQualKeys + 1]; // offsets into clientQualifier of the key/value pairs, the +1 is needed so that values can contain nulls
- KeyValueIndices headers[maxHeaders]; // offsets into clientHeader of the key/value pairs
- size_t numCommandWords;
- size_t numQualKeys; // number of qualifier keys we have found, <= maxQualKeys
- size_t numHeaderKeys; // number of keys we have found, <= maxHeaders
-
- // HTTP sessions
- struct HttpSession
- {
- uint32_t ip;
- uint32_t lastQueryTime;
- bool isPostUploading;
- uint16_t postPort;
- };
-
- HttpSession sessions[maxHttpSessions];
- uint8_t numSessions;
- uint8_t clientsServed;
-
- bool Authenticate();
- bool IsAuthenticated() const;
- void UpdateAuthentication();
- bool RemoveAuthentication();
-
- // Deal with incoming G-Codes
-
- char gcodeBuffer[gcodeBufferLength];
- uint16_t gcodeReadIndex, gcodeWriteIndex; // head and tail indices into gcodeBuffer
-
- void LoadGcodeBuffer(const char* gc);
- void ProcessGcode(const char* gc);
- void StoreGcodeData(const char* data, uint16_t len);
-
- // Responses from GCodes class
-
- uint32_t seq; // Sequence number for G-Code replies
- OutputStack *gcodeReply;
-
- // File uploads
- uint32_t postFileLength, uploadedBytes; // How many POST bytes do we expect and how many have already been written?
- time_t fileLastModified;
-
- // Deferred requests (rr_fileinfo)
- ConnectionState * volatile deferredRequestConnection; // Which connection expects a response for a deferred request?
- char filenameBeingProcessed[FILENAME_LENGTH]; // The filename being processed (for rr_fileinfo)
-
- void ProcessDeferredRequest();
- };
- HttpInterpreter *httpInterpreter;
-
- class FtpInterpreter : public ProtocolInterpreter
- {
- public:
-
- FtpInterpreter(Platform *p, Webserver *ws, Network *n);
- void Diagnostics(MessageType mtype) override;
-
- void ConnectionEstablished() override;
- void ConnectionLost(const ConnectionState *cs) override;
- bool CharFromClient(const char c) override;
- void ResetState();
-
- bool DoingFastUpload() const override;
-
- private:
-
- enum FtpState
- {
- idle, // no client connected
- authenticating, // not logged in
- authenticated, // logged in
- waitingForPasvPort, // waiting for connection to be established on PASV port
- pasvPortConnected, // client connected to PASV port, ready to send data
- doingPasvIO // client is connected and data is being transferred
- };
- FtpState state;
- uint8_t connectedClients;
-
- char clientMessage[ftpMessageLength];
- size_t clientPointer;
-
- char filename[FILENAME_LENGTH];
- char currentDir[FILENAME_LENGTH];
-
- uint32_t portOpenTime;
-
- void ProcessLine();
- void SendReply(int code, const char *message, bool keepConnection = true);
- void SendFeatures();
-
- void ReadFilename(uint16_t start);
- void ChangeDirectory(const char *newDirectory);
- };
- FtpInterpreter *ftpInterpreter;
-
- class TelnetInterpreter : public ProtocolInterpreter
- {
- public:
-
- TelnetInterpreter(Platform *p, Webserver *ws, Network *n);
- void Diagnostics(MessageType mtype) override;
-
- void ConnectionEstablished() override;
- void ConnectionLost(const ConnectionState *cs) override;
- bool CanParseData() override;
- bool CharFromClient(const char c) override;
- void ResetState();
-
- bool GCodeAvailable() const;
- char ReadGCode();
- void HandleGCodeReply(OutputBuffer *reply);
- void HandleGCodeReply(const char *reply);
- uint16_t GetGCodeBufferSpace() const;
-
- void SendGCodeReply();
-
- private:
-
- enum TelnetState
- {
- idle, // not connected
- justConnected, // not logged in, but the client has just connected
- authenticating, // not logged in
- authenticated // logged in
- };
- TelnetState state;
- uint8_t connectedClients;
- uint32_t connectTime;
-
- bool processNextLine;
- char clientMessage[GCODE_LENGTH];
- size_t clientPointer;
-
- bool ProcessLine();
-
- // Deal with incoming G-Codes
-
- char gcodeBuffer[gcodeBufferLength];
- uint16_t gcodeReadIndex, gcodeWriteIndex; // head and tail indices into gcodeBuffer
-
- void ProcessGcode(const char* gc);
- void StoreGcodeData(const char* data, uint16_t len);
-
- // Converted response from GCodes class (NL -> CRNL)
-
- OutputBuffer * volatile gcodeReply;
- };
- TelnetInterpreter *telnetInterpreter;
-
- private:
-
- Platform* platform;
- Network* network;
- bool webserverActive;
- NetworkTransaction *currentTransaction;
- ConnectionState * volatile readingConnection;
-
- float longWait;
-};
-
-inline bool ProtocolInterpreter::CanParseData() { return true; }
-inline bool ProtocolInterpreter::DoingFastUpload() const { return false; }
-inline bool ProtocolInterpreter::IsUploading() const { return uploadState != notUploading; }
-
-inline uint32_t Webserver::GetReplySeq() const { return httpInterpreter->GetReplySeq(); }
-
-inline uint16_t Webserver::HttpInterpreter::GetGCodeBufferSpace() const { return (gcodeReadIndex - gcodeWriteIndex - 1u) % gcodeBufferLength; }
-inline bool Webserver::HttpInterpreter::GCodeAvailable() const { return gcodeReadIndex != gcodeWriteIndex; }
-inline uint32_t Webserver::HttpInterpreter::GetReplySeq() const { return seq; }
-
-inline uint16_t Webserver::TelnetInterpreter::GetGCodeBufferSpace() const { return (gcodeReadIndex - gcodeWriteIndex - 1u) % gcodeBufferLength; }
-inline bool Webserver::TelnetInterpreter::GCodeAvailable() const { return gcodeReadIndex != gcodeWriteIndex; }
-
-#endif
diff --git a/src/DuetNG/DuetEthernet/Wiznet/Ethernet/socket.cpp b/src/DuetNG/DuetEthernet/Wiznet/Ethernet/socketlib.cpp
index a647d08a..0ad63a22 100644
--- a/src/DuetNG/DuetEthernet/Wiznet/Ethernet/socket.cpp
+++ b/src/DuetNG/DuetEthernet/Wiznet/Ethernet/socketlib.cpp
@@ -53,7 +53,7 @@
//! THE POSSIBILITY OF SUCH DAMAGE.
//
//*****************************************************************************
-#include "socket.h"
+#include <socketlib.h>
#define _SOCKET_DEBUG_
@@ -272,6 +272,11 @@ int8_t disconnect(uint8_t sn)
return SOCK_OK;
}
+void disconnectNoWait(uint8_t sn)
+{
+ ExecCommand(sn, Sn_CR_DISCON);
+}
+
int32_t send(uint8_t sn, uint8_t * buf, uint16_t len)
{
CHECK_SOCKMODE(Sn_MR_TCP);
diff --git a/src/DuetNG/DuetEthernet/Wiznet/Ethernet/socket.h b/src/DuetNG/DuetEthernet/Wiznet/Ethernet/socketlib.h
index 24bb7f7c..5cd57c57 100644
--- a/src/DuetNG/DuetEthernet/Wiznet/Ethernet/socket.h
+++ b/src/DuetNG/DuetEthernet/Wiznet/Ethernet/socketlib.h
@@ -226,6 +226,8 @@ int8_t connect(uint8_t sn, uint8_t * addr, uint16_t port);
*/
int8_t disconnect(uint8_t sn);
+void disconnectNoWait(uint8_t sn);
+
/**
* @ingroup WIZnet_socket_APIs
* @brief Send data to the connected peer in TCP socket.
diff --git a/src/DuetNG/DuetEthernet/Wiznet/Internet/DHCP/dhcp.cpp b/src/DuetNG/DuetEthernet/Wiznet/Internet/DHCP/dhcp.cpp
index 079f72bf..2d42feb0 100644
--- a/src/DuetNG/DuetEthernet/Wiznet/Internet/DHCP/dhcp.cpp
+++ b/src/DuetNG/DuetEthernet/Wiznet/Internet/DHCP/dhcp.cpp
@@ -49,7 +49,7 @@
//
//*****************************************************************************
-#include "socket.h"
+#include <socketlib.h>
#include "dhcp.h"
#include <cstring>
diff --git a/src/DuetNG/DuetWiFi/Network.cpp b/src/DuetNG/DuetWiFi/Network.cpp
index 31aa3459..03e8f1d3 100644
--- a/src/DuetNG/DuetWiFi/Network.cpp
+++ b/src/DuetNG/DuetWiFi/Network.cpp
@@ -4,12 +4,12 @@
****************************************************************************************************/
-#include "RepRapFirmware.h"
-#include "compiler.h"
-#include "Pins.h"
-#include "WifiFirmwareUploader.h"
+#include "Network.h"
+#include "Platform.h"
+#include "RepRap.h"
#include "TransactionBuffer.h"
#include "TransactionBufferReader.h"
+#include "WifiFirmwareUploader.h"
// Define exactly one of the following as 1, thje other as zero
// The PDC seems to be too slow to work reliably without getting transmit underruns, so we use the DMAC now.
diff --git a/src/DuetNG/DuetWiFi/Network.h b/src/DuetNG/DuetWiFi/Network.h
index 2d403412..3258c34a 100644
--- a/src/DuetNG/DuetWiFi/Network.h
+++ b/src/DuetNG/DuetWiFi/Network.h
@@ -9,11 +9,7 @@ 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 "RepRapFirmware.h"
#include "MessageType.h"
// Return code definitions
diff --git a/src/DuetNG/DuetWiFi/Webserver.cpp b/src/DuetNG/DuetWiFi/Webserver.cpp
index 2a2a94f4..7d16d3a4 100644
--- a/src/DuetNG/DuetWiFi/Webserver.cpp
+++ b/src/DuetNG/DuetWiFi/Webserver.cpp
@@ -82,7 +82,13 @@
****************************************************************************************************/
-#include "RepRapFirmware.h"
+#include "Webserver.h"
+
+#include "GCodes/GCodes.h"
+#include "Network.h"
+#include "Platform.h"
+#include "PrintMonitor.h"
+#include "RepRap.h"
const char* overflowResponse = "overflow";
const char* badEscapeResponse = "bad escape";
diff --git a/src/DuetNG/DuetWiFi/Webserver.h b/src/DuetNG/DuetWiFi/Webserver.h
index 5b4ba5fd..d8459d8d 100644
--- a/src/DuetNG/DuetWiFi/Webserver.h
+++ b/src/DuetNG/DuetWiFi/Webserver.h
@@ -30,6 +30,9 @@ Licence: GPL
#ifndef WEBSERVER_H
#define WEBSERVER_H
+#include "RepRapFirmware.h"
+#include "MessageType.h"
+#include "Storage/FileData.h"
// List of protocols that can execute G-Codes
enum class WebSource
diff --git a/src/DuetNG/DuetWiFi/WifiFirmwareUploader.cpp b/src/DuetNG/DuetWiFi/WifiFirmwareUploader.cpp
index 070c86f2..cfe108d3 100644
--- a/src/DuetNG/DuetWiFi/WifiFirmwareUploader.cpp
+++ b/src/DuetNG/DuetWiFi/WifiFirmwareUploader.cpp
@@ -6,7 +6,11 @@
*/
#include "WifiFirmwareUploader.h"
-#include "RepRapFirmware.h"
+
+#include "Network.h"
+#include "Platform.h"
+#include "RepRap.h"
+#include "Storage/FileStore.h"
// ESP8266 command codes
const uint8_t ESP_FLASH_BEGIN = 0x02;
diff --git a/src/DuetNG/DuetWiFi/WifiFirmwareUploader.h b/src/DuetNG/DuetWiFi/WifiFirmwareUploader.h
index 22b92fea..bc5012ec 100644
--- a/src/DuetNG/DuetWiFi/WifiFirmwareUploader.h
+++ b/src/DuetNG/DuetWiFi/WifiFirmwareUploader.h
@@ -8,8 +8,7 @@
#ifndef SRC_DUETNG_WIFIFIRMWAREUPLOADER_H_
#define SRC_DUETNG_WIFIFIRMWAREUPLOADER_H_
-#include "Core.h"
-#include "Storage/FileStore.h"
+#include "RepRapFirmware.h"
class WifiFirmwareUploader
{
diff --git a/src/DuetNG/FirmwareUpdater.cpp b/src/DuetNG/FirmwareUpdater.cpp
index 9f2104b3..3fd43179 100644
--- a/src/DuetNG/FirmwareUpdater.cpp
+++ b/src/DuetNG/FirmwareUpdater.cpp
@@ -6,7 +6,11 @@
*/
#include "FirmwareUpdater.h"
+
#include "RepRapFirmware.h"
+#include "Network.h"
+#include "Platform.h"
+#include "RepRap.h"
#ifdef DUET_WIFI
#include "WifiFirmwareUploader.h"
diff --git a/src/DuetNG/Pins_DuetNG.h b/src/DuetNG/Pins_DuetNG.h
index 60ce39f2..b01c8099 100644
--- a/src/DuetNG/Pins_DuetNG.h
+++ b/src/DuetNG/Pins_DuetNG.h
@@ -33,8 +33,6 @@ const size_t MaxDriversPerAxis = 4; // The maximum number of stepper drivers
const size_t MAX_AXES = 6; // The maximum number of movement axes in the machine, usually just X, Y and Z, <= DRIVES
const size_t MIN_AXES = 3; // The minimum and default number of axes
-const size_t DELTA_AXES = 3; // The number of axis involved in delta movement
-const size_t CART_AXES = 3; // The number of Cartesian axes
const size_t MaxExtruders = DRIVES - MIN_AXES; // The maximum number of extruders
const size_t NUM_SERIAL_CHANNELS = 2; // The number of serial IO channels (USB and one auxiliary UART)
diff --git a/src/Fan.cpp b/src/Fan.cpp
index 83c56e45..67206112 100644
--- a/src/Fan.cpp
+++ b/src/Fan.cpp
@@ -5,7 +5,9 @@
* Author: David
*/
-#include "RepRapFirmware.h"
+#include "Fan.h"
+#include "Platform.h"
+#include "RepRap.h"
void Fan::Init(Pin p_pin, bool hwInverted)
{
diff --git a/src/Fan.h b/src/Fan.h
index 1c277bf4..77c919f8 100644
--- a/src/Fan.h
+++ b/src/Fan.h
@@ -8,7 +8,7 @@
#ifndef SRC_FAN_H_
#define SRC_FAN_H_
-#include "Core.h"
+#include "RepRapFirmware.h"
class Fan
{
diff --git a/src/GCodes/GCodeBuffer.cpp b/src/GCodes/GCodeBuffer.cpp
index 8fa81bac..fcc8d3a1 100644
--- a/src/GCodes/GCodeBuffer.cpp
+++ b/src/GCodes/GCodeBuffer.cpp
@@ -7,7 +7,9 @@
//*************************************************************************************
-#include "RepRapFirmware.h"
+#include "GCodeBuffer.h"
+#include "Platform.h"
+#include "RepRap.h"
// Create a default GCodeBuffer
GCodeBuffer::GCodeBuffer(const char* id, MessageType mt)
diff --git a/src/GCodes/GCodeBuffer.h b/src/GCodes/GCodeBuffer.h
index b1540871..198512d1 100644
--- a/src/GCodes/GCodeBuffer.h
+++ b/src/GCodes/GCodeBuffer.h
@@ -8,7 +8,9 @@
#ifndef GCODEBUFFER_H_
#define GCODEBUFFER_H_
+#include "RepRapFirmware.h"
#include "GCodeMachineState.h"
+#include "MessageType.h"
// Class to hold an individual GCode and provide functions to allow it to be parsed
class GCodeBuffer
diff --git a/src/GCodes/GCodeMachineState.h b/src/GCodes/GCodeMachineState.h
index db8fdd01..eec66ebc 100644
--- a/src/GCodes/GCodeMachineState.h
+++ b/src/GCodes/GCodeMachineState.h
@@ -8,13 +8,9 @@
#ifndef SRC_GCODES_GCODEMACHINESTATE_H_
#define SRC_GCODES_GCODEMACHINESTATE_H_
-#include <cstdint>
-#include "Configuration.h"
+#include "RepRapFirmware.h"
#include "Storage/FileData.h"
-const float minutesToSeconds = 60.0;
-const float secondsToMinutes = 1.0/minutesToSeconds;
-
// Enumeration to list all the possible states that the Gcode processing machine may be in
enum class GCodeState : uint8_t
{
diff --git a/src/GCodes/GCodes.cpp b/src/GCodes/GCodes.cpp
index a4f249d7..fe84e7f3 100644
--- a/src/GCodes/GCodes.cpp
+++ b/src/GCodes/GCodes.cpp
@@ -23,7 +23,15 @@
****************************************************************************************************/
-#include "RepRapFirmware.h"
+#include "GCodes.h"
+#include "GCodeBuffer.h"
+#include "Heating/Heat.h"
+#include "Platform.h"
+#include "Movement/Move.h"
+#include "PrintMonitor.h"
+#include "RepRap.h"
+#include "Tool.h"
+#include "Webserver.h"
#ifdef DUET_NG
#include "FirmwareUpdater.h"
@@ -160,6 +168,11 @@ void GCodes::Reset()
}
}
+bool GCodes::DoingFileMacro() const
+{
+ return fileGCode->IsDoingFileMacro();
+}
+
float GCodes::FractionOfFilePrinted() const
{
const FileData& fileBeingPrinted = fileGCode->OriginalMachineState().fileState;
diff --git a/src/GCodes/GCodes.h b/src/GCodes/GCodes.h
index fa145eef..5aaea8ce 100644
--- a/src/GCodes/GCodes.h
+++ b/src/GCodes/GCodes.h
@@ -22,8 +22,11 @@ Licence: GPL
#ifndef GCODES_H
#define GCODES_H
-#include "GCodeBuffer.h"
+#include "RepRapFirmware.h"
#include "Libraries/sha1/sha1.h"
+#include "Platform.h" // for type EndStopHit
+
+class GCodeBuffer;
const char feedrateLetter = 'F'; // GCode feedrate
const char extrudeLetter = 'E'; // GCode extrude
@@ -312,9 +315,4 @@ private:
//*****************************************************************************************************
-inline bool GCodes::DoingFileMacro() const
-{
- return fileGCode->IsDoingFileMacro();
-}
-
#endif
diff --git a/src/GCodes/GCodes2.cpp b/src/GCodes/GCodes2.cpp
index 34c0baab..cce862fe 100644
--- a/src/GCodes/GCodes2.cpp
+++ b/src/GCodes/GCodes2.cpp
@@ -7,7 +7,16 @@
* This file contains the code to see what G, M or T command we have and start processing it.
*/
-#include "RepRapFirmware.h"
+#include "GCodes.h"
+
+#include "GCodeBuffer.h"
+#include "Heating/Heat.h"
+#include "Movement/Move.h"
+#include "Network.h"
+#include "PrintMonitor.h"
+#include "RepRap.h"
+#include "Tool.h"
+#include "Version.h"
#ifdef DUET_NG
#include "FirmwareUpdater.h"
diff --git a/src/Heating/FOPDT.cpp b/src/Heating/FOPDT.cpp
index e3bc22c6..e2d9244e 100644
--- a/src/Heating/FOPDT.cpp
+++ b/src/Heating/FOPDT.cpp
@@ -6,8 +6,6 @@
*/
#include "FOPDT.h"
-#include "Core.h"
-#include "Configuration.h"
#include "Storage/FileStore.h"
#include "Libraries/General/StringRef.h"
diff --git a/src/Heating/FOPDT.h b/src/Heating/FOPDT.h
index 4f703039..80850865 100644
--- a/src/Heating/FOPDT.h
+++ b/src/Heating/FOPDT.h
@@ -10,7 +10,7 @@
#ifndef SRC_HEATING_FOPDT_H_
#define SRC_HEATING_FOPDT_H_
-#include <cstddef>
+#include "RepRapFirmware.h"
// This is how PID parameters are stored internally
struct PidParameters
diff --git a/src/Heating/Heat.cpp b/src/Heating/Heat.cpp
index 57df8e95..674cd60d 100644
--- a/src/Heating/Heat.cpp
+++ b/src/Heating/Heat.cpp
@@ -18,11 +18,12 @@ Licence: GPL
****************************************************************************************************/
-#include "RepRapFirmware.h"
-#include "Pid.h"
+#include "Heat.h"
+#include "Platform.h"
+#include "RepRap.h"
Heat::Heat(Platform* p)
- : platform(p), active(false), coldExtrude(false), bedHeater(DefaultBedHeater), chamberHeater(-1), heaterBeingTuned(-1), lastHeaterTuned(-1)
+ : platform(p), active(false), coldExtrude(false), bedHeater(DefaultBedHeater), chamberHeater(DefaultChamberHeater), heaterBeingTuned(-1), lastHeaterTuned(-1)
{
for (size_t heater = 0; heater < HEATERS; heater++)
{
@@ -37,7 +38,7 @@ void Heat::ResetHeaterModels()
{
if (pids[heater]->IsHeaterEnabled())
{
- if (heater == DefaultBedHeater)
+ if (heater == DefaultBedHeater || heater == DefaultChamberHeater)
{
pids[heater]->SetModel(DefaultBedHeaterGain, DefaultBedHeaterTimeConstant, DefaultBedHeaterDeadTime, 1.0, false);
}
@@ -53,7 +54,7 @@ void Heat::Init()
{
for (int heater = 0; heater < HEATERS; heater++)
{
- if (heater == DefaultBedHeater)
+ if (heater == DefaultBedHeater || heater == DefaultChamberHeater)
{
pids[heater]->Init(DefaultBedHeaterGain, DefaultBedHeaterTimeConstant, DefaultBedHeaterDeadTime,
DefaultBedTemperatureLimit, false);
diff --git a/src/Heating/Heat.h b/src/Heating/Heat.h
index e6395a27..e71ab847 100644
--- a/src/Heating/Heat.h
+++ b/src/Heating/Heat.h
@@ -25,7 +25,9 @@ Licence: GPL
* The master class that controls all the heaters in the RepRap machine
*/
+#include "RepRapFirmware.h"
#include "Pid.h"
+#include "MessageType.h"
class Heat
{
diff --git a/src/Heating/Pid.cpp b/src/Heating/Pid.cpp
index 039729a6..0347f066 100644
--- a/src/Heating/Pid.cpp
+++ b/src/Heating/Pid.cpp
@@ -5,8 +5,11 @@
* Author: David
*/
-#include "RepRapFirmware.h"
#include "Pid.h"
+#include "GCodes/GCodes.h"
+#include "Heat.h"
+#include "Platform.h"
+#include "RepRap.h"
// Private constants
const uint32_t InitialTuningReadingInterval = 250; // the initial reading interval in milliseconds
@@ -31,6 +34,11 @@ PID::PID(Platform* p, int8_t h) : platform(p), heater(h), mode(HeaterMode::off)
{
}
+inline void PID::SetHeater(float power) const
+{
+ platform->SetHeater(heater, power);
+}
+
void PID::Init(float pGain, float pTc, float pTd, float tempLimit, bool usePid)
{
temperatureLimit = tempLimit;
diff --git a/src/Heating/Pid.h b/src/Heating/Pid.h
index b5ecb8cb..523eea95 100644
--- a/src/Heating/Pid.h
+++ b/src/Heating/Pid.h
@@ -12,7 +12,9 @@
* This class implements a PID controller for the heaters
*/
+#include "RepRapFirmware.h"
#include "FOPDT.h"
+#include "TemperatureError.h"
class PID
{
@@ -182,11 +184,6 @@ inline float PID::GetAccumulator() const
return iAccumulator;
}
-inline void PID::SetHeater(float power) const
-{
- platform->SetHeater(heater, power);
-}
-
inline bool PID::IsTuning() const
{
return mode >= HeaterMode::tuning0;
diff --git a/src/Heating/TemperatureSensor.cpp b/src/Heating/TemperatureSensor.cpp
index 31a1e248..7ed05eef 100644
--- a/src/Heating/TemperatureSensor.cpp
+++ b/src/Heating/TemperatureSensor.cpp
@@ -1,5 +1,6 @@
#include "TemperatureSensor.h"
-#include "RepRapFirmware.h"
+#include "Platform.h"
+#include "RepRap.h"
// MAX31855 thermocouple chip
//
diff --git a/src/Heating/TemperatureSensor.h b/src/Heating/TemperatureSensor.h
index d29f6f74..38cb5e33 100644
--- a/src/Heating/TemperatureSensor.h
+++ b/src/Heating/TemperatureSensor.h
@@ -1,8 +1,8 @@
#ifndef TEMPERATURESENSOR_H
#define TEMPERATURESENSOR_H
+#include "RepRapFirmware.h"
#include "TemperatureError.h" // for result codes
-#include "Core.h"
#include "SharedSpi.h" // for sspi_device
class TemperatureSensor
diff --git a/src/Heating/Thermistor.cpp b/src/Heating/Thermistor.cpp
index 991dc441..d8357573 100644
--- a/src/Heating/Thermistor.cpp
+++ b/src/Heating/Thermistor.cpp
@@ -6,8 +6,6 @@
*/
#include "Thermistor.h"
-#include "Pins.h"
-#include "Configuration.h"
// The Steinhart-Hart equation for thermistor resistance is:
// 1/T = A + B ln(R) + C [ln(R)]^3
diff --git a/src/Heating/Thermistor.h b/src/Heating/Thermistor.h
index 58c504c2..eabc45f9 100644
--- a/src/Heating/Thermistor.h
+++ b/src/Heating/Thermistor.h
@@ -8,7 +8,7 @@
#ifndef SRC_HEATING_THERMISTOR_H_
#define SRC_HEATING_THERMISTOR_H_
-#include "Core.h"
+#include "RepRapFirmware.h"
// The Steinhart-Hart equation for thermistor resistance is:
// 1/T = A + B ln(R) + C [ln(R)]^3
diff --git a/src/Libraries/Fatfs/fattime_rtc.cpp b/src/Libraries/Fatfs/fattime_rtc.cpp
index 9c416759..8584585f 100644
--- a/src/Libraries/Fatfs/fattime_rtc.cpp
+++ b/src/Libraries/Fatfs/fattime_rtc.cpp
@@ -41,6 +41,8 @@
*
*/
#include "RepRapFirmware.h"
+#include "RepRap.h"
+#include "Platform.h"
/**
* \brief Current time returned is packed into a DWORD value.
diff --git a/src/Movement/DDA.cpp b/src/Movement/DDA.cpp
index 6456d6e2..52333877 100644
--- a/src/Movement/DDA.cpp
+++ b/src/Movement/DDA.cpp
@@ -5,7 +5,10 @@
* Author: David
*/
-#include "RepRapFirmware.h"
+#include "DDA.h"
+#include "RepRap.h"
+#include "Platform.h"
+#include "Move.h"
#ifdef DUET_NG
# define DDA_MOVE_DEBUG (1)
diff --git a/src/Movement/DDA.h b/src/Movement/DDA.h
index e0ceb1d6..423fdbb3 100644
--- a/src/Movement/DDA.h
+++ b/src/Movement/DDA.h
@@ -8,7 +8,9 @@
#ifndef DDA_H_
#define DDA_H_
+#include "RepRapFirmware.h"
#include "DriveMovement.h"
+#include "GCodes/GCodes.h" // for class RawMove
#ifdef DUET_NG
#define DDA_LOG_PROBE_CHANGES 1
diff --git a/src/Movement/DeltaParameters.cpp b/src/Movement/DeltaParameters.cpp
index 4594ae6c..9dc96a4b 100644
--- a/src/Movement/DeltaParameters.cpp
+++ b/src/Movement/DeltaParameters.cpp
@@ -5,7 +5,10 @@
* Author: David
*/
-#include "RepRapFirmware.h"
+#include "DeltaParameters.h"
+#include "Pins.h"
+#include "Configuration.h"
+#include "Storage/FileStore.h"
void DeltaParameters::Init()
{
diff --git a/src/Movement/DeltaParameters.h b/src/Movement/DeltaParameters.h
index b4d677ff..90a84690 100644
--- a/src/Movement/DeltaParameters.h
+++ b/src/Movement/DeltaParameters.h
@@ -8,6 +8,24 @@
#ifndef DELTAPARAMETERS_H_
#define DELTAPARAMETERS_H_
+#include "RepRapFirmware.h"
+
+#ifdef DUET_NG
+typedef double floatc_t; // type of matrix element used for delta calibration
+#else
+// We are more memory-constrained on the SAM3X
+typedef float floatc_t; // type of matrix element used for delta calibration
+#endif
+
+// Delta parameter defaults
+const float defaultPrintRadius = 50; // mm
+const float defaultDeltaHomedHeight = 200; // mm
+
+const size_t DELTA_AXES = 3;
+const size_t A_AXIS = 0;
+const size_t B_AXIS = 1;
+const size_t C_AXIS = 2;
+
// Class to hold the parameter for a delta machine.
class DeltaParameters
{
diff --git a/src/Movement/DeltaProbe.cpp b/src/Movement/DeltaProbe.cpp
index d52472d0..b557fb52 100644
--- a/src/Movement/DeltaProbe.cpp
+++ b/src/Movement/DeltaProbe.cpp
@@ -5,7 +5,10 @@
* Author: David
*/
-#include "RepRapFirmware.h"
+#include "Deltaprobe.h"
+#include "DDA.h"
+#include "Platform.h"
+#include "RepRap.h"
// Set up to probe
bool DeltaProbe::Init(float frequency, float amplitude, float rate, float height)
diff --git a/src/Movement/DeltaProbe.h b/src/Movement/DeltaProbe.h
index 50e0cc50..90b2fb49 100644
--- a/src/Movement/DeltaProbe.h
+++ b/src/Movement/DeltaProbe.h
@@ -8,6 +8,8 @@
#ifndef DELTAPROBE_H_
#define DELTAPROBE_H_
+#include "RepRapFirmware.h"
+
// Class to hold the parameters for my new Z probing method
class DeltaProbe
{
diff --git a/src/Movement/DriveMovement.cpp b/src/Movement/DriveMovement.cpp
index 447d7aac..59e3f1ba 100644
--- a/src/Movement/DriveMovement.cpp
+++ b/src/Movement/DriveMovement.cpp
@@ -5,7 +5,10 @@
* Author: David
*/
-#include "RepRapFirmware.h"
+#include "DriveMovement.h"
+#include "DDA.h"
+#include "Move.h"
+#include "RepRap.h"
#include "Libraries/Math/Isqrt.h"
// Prepare this DM for a Cartesian axis move
diff --git a/src/Movement/DriveMovement.h b/src/Movement/DriveMovement.h
index e30020bd..e27319dd 100644
--- a/src/Movement/DriveMovement.h
+++ b/src/Movement/DriveMovement.h
@@ -8,7 +8,7 @@
#ifndef DRIVEMOVEMENT_H_
#define DRIVEMOVEMENT_H_
-class DDA;
+#include "RepRapFirmware.h"
// Struct for passing parameters to the DriveMovement Prepare methods
struct PrepParams
diff --git a/src/Movement/Grid.cpp b/src/Movement/Grid.cpp
index ac1709ca..a0a8fb87 100644
--- a/src/Movement/Grid.cpp
+++ b/src/Movement/Grid.cpp
@@ -6,7 +6,9 @@
*/
#include "Grid.h"
-#include "RepRapFirmware.h"
+#include "Platform.h"
+#include "RepRap.h"
+#include "Storage/FileStore.h"
#include <cmath>
const char *GridDefinition::HeightMapLabelLine = "xmin,xmax,ymin,ymax,radius,spacing,xnum,ynum";
diff --git a/src/Movement/Grid.h b/src/Movement/Grid.h
index 6dba8b38..2de4e0ba 100644
--- a/src/Movement/Grid.h
+++ b/src/Movement/Grid.h
@@ -9,11 +9,8 @@
#define SRC_MOVEMENT_GRID_H_
#include <cstdint>
-#include "ecv.h"
+#include "RepRapFirmware.h"
#include "Libraries/General/StringRef.h"
-#include "Configuration.h"
-
-class FileStore;
// This class defines the bed probing grid
class GridDefinition
diff --git a/src/Movement/Move.cpp b/src/Movement/Move.cpp
index a2fec5ca..a63ab16d 100644
--- a/src/Movement/Move.cpp
+++ b/src/Movement/Move.cpp
@@ -5,7 +5,9 @@
* Author: David
*/
-#include "RepRapFirmware.h"
+#include "Move.h"
+#include "Platform.h"
+#include "RepRap.h"
Move::Move(Platform* p, GCodes* g) : currentDda(NULL), grid(zBedProbePoints)
{
diff --git a/src/Movement/Move.h b/src/Movement/Move.h
index e427176a..b0d1015d 100644
--- a/src/Movement/Move.h
+++ b/src/Movement/Move.h
@@ -8,16 +8,16 @@
#ifndef MOVE_H_
#define MOVE_H_
-#include "DDA.h"
+#include "RepRapFirmware.h"
+#include "MessageType.h"
+#include "DDA.h" // needed because of our inline functions
#include "Libraries/Math/Matrix.h"
#ifdef DUET_NG
const unsigned int DdaRingLength = 40;
-typedef double floatc_t; // type of matrix element used for delta calibration
#else
// We are more memory-constrained on the SAM3X
const unsigned int DdaRingLength = 20;
-typedef float floatc_t; // type of matrix element used for delta calibration
#endif
#include "DeltaParameters.h"
diff --git a/src/OutputMemory.cpp b/src/OutputMemory.cpp
index b907cfa3..d825ecaa 100644
--- a/src/OutputMemory.cpp
+++ b/src/OutputMemory.cpp
@@ -6,7 +6,8 @@
*/
#include "OutputMemory.h"
-#include "RepRapFirmware.h"
+#include "Platform.h"
+#include "RepRap.h"
#include <cstdarg>
/*static*/ OutputBuffer * volatile OutputBuffer::freeOutputBuffers = nullptr; // Messages may also be sent by ISRs,
diff --git a/src/OutputMemory.h b/src/OutputMemory.h
index 629fbe4f..671bac9f 100644
--- a/src/OutputMemory.h
+++ b/src/OutputMemory.h
@@ -8,9 +8,7 @@
#ifndef OUTPUTMEMORY_H_
#define OUTPUTMEMORY_H_
-#include "Core.h"
-#include "Configuration.h"
-#include "Libraries/General/StringRef.h"
+#include "RepRapFirmware.h"
#include "MessageType.h"
const size_t OUTPUT_STACK_DEPTH = 4; // Number of OutputBuffer chains that can be pushed onto one stack instance
diff --git a/src/Platform.cpp b/src/Platform.cpp
index b118ba5d..9f686279 100644
--- a/src/Platform.cpp
+++ b/src/Platform.cpp
@@ -19,7 +19,14 @@
****************************************************************************************************/
-#include "RepRapFirmware.h"
+#include "Platform.h"
+
+#include "Heating/Heat.h"
+#include "Movement/DDA.h"
+#include "Movement/Move.h"
+#include "Network.h"
+#include "RepRap.h"
+#include "Webserver.h"
#include "sam/drivers/tc/tc.h"
#include "sam/drivers/hsmci/hsmci.h"
@@ -30,6 +37,9 @@
# include "FirmwareUpdater.h"
#endif
+#include <climits>
+#include <malloc.h>
+
extern char _end;
extern "C" char *sbrk(int i);
diff --git a/src/Platform.h b/src/Platform.h
index 0cde3f1d..cbbd4ec2 100644
--- a/src/Platform.h
+++ b/src/Platform.h
@@ -36,22 +36,19 @@ Licence: GPL
// Language-specific includes
-#include <cctype>
-#include <cstring>
-#include <malloc.h>
-#include <cstdlib>
-#include <climits>
-#include <ctime>
-
// Platform-specific includes
-#include "Core.h"
+#include "RepRapFirmware.h"
#include "DueFlashStorage.h"
+#include "Fan.h"
#include "Heating/TemperatureSensor.h"
#include "Heating/Thermistor.h"
#include "Heating/TemperatureError.h"
#include "OutputMemory.h"
-#include "Libraries/Fatfs/ff.h"
+#include "Storage/FileStore.h"
+#include "Storage/FileData.h"
+#include "Storage/MassStorage.h" // must be after Pins.h because it needs NumSdCards defined
+#include "MessageType.h"
#if defined(DUET_NG)
# include "DueXn.h"
@@ -59,23 +56,9 @@ Licence: GPL
# include "MCP4461/MCP4461.h"
#endif
-#include "Storage/FileStore.h"
-#include "Storage/FileData.h"
-#include "MessageType.h"
-
-// Definitions needed by Fan.h
-const float SecondsToMillis = 1000.0;
-const float MillisToSeconds = 0.001;
-
-#include "Fan.h"
-
-// Definitions needed by Pins.h
const bool FORWARDS = true;
const bool BACKWARDS = !FORWARDS;
-#include "Pins.h"
-#include "Storage/MassStorage.h" // must be after Pins.h because it needs NumSdCards defined
-
/**************************************************************************************************/
// Some constants
@@ -101,15 +84,9 @@ const float INSTANT_DVS[DRIVES] = DRIVES_(15.0, 15.0, 0.2, 2.0, 2.0, 2.0, 2.0, 2
// AXES
-const size_t X_AXIS = 0, Y_AXIS = 1, Z_AXIS = 2, E0_AXIS = 3; // The indices of the Cartesian axes in drive arrays
-const size_t A_AXIS = 0, B_AXIS = 1, C_AXIS = 2; // The indices of the 3 tower motors of a delta printer in drive arrays
-
const float AXIS_MINIMA[MAX_AXES] = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; // mm
const float AXIS_MAXIMA[MAX_AXES] = { 230.0, 210.0, 200.0, 0.0, 0.0, 0.0 }; // mm
-const float defaultPrintRadius = 50; // mm
-const float defaultDeltaHomedHeight = 200; // mm
-
// Z PROBE
const float Z_PROBE_STOP_HEIGHT = 0.7; // Millimetres
@@ -132,9 +109,6 @@ const unsigned int ThermistorAverageReadings = 32;
const uint32_t maxPidSpinDelay = 5000; // Maximum elapsed time in milliseconds between successive temp samples by Pid::Spin() permitted for a temp sensor
-const size_t DefaultBedHeater = 0; // Index of the default bed heater
-const size_t DefaultE0Heater = 1; // Index of the default first extruder heater
-
/****************************************************************************************************/
// File handling
diff --git a/src/PrintMonitor.cpp b/src/PrintMonitor.cpp
index 102ba4aa..83fdd3da 100644
--- a/src/PrintMonitor.cpp
+++ b/src/PrintMonitor.cpp
@@ -17,7 +17,13 @@ Licence: GPL
****************************************************************************************************/
-#include "RepRapFirmware.h"
+#include "PrintMonitor.h"
+
+#include "GCodes/GCodes.h"
+#include "Heating/Heat.h"
+#include "Movement/Move.h"
+#include "Platform.h"
+#include "RepRap.h"
PrintMonitor::PrintMonitor(Platform *p, GCodes *gc) : platform(p), gCodes(gc), isPrinting(false),
printStartTime(0), pauseStartTime(0.0), totalPauseTime(0.0), heatingUp(false), currentLayer(0), warmUpDuration(0.0),
diff --git a/src/PrintMonitor.h b/src/PrintMonitor.h
index c71770f0..36eb26cc 100644
--- a/src/PrintMonitor.h
+++ b/src/PrintMonitor.h
@@ -20,6 +20,8 @@ Licence: GPL
#ifndef PRINTMONITOR_H
#define PRINTMONITOR_H
+#include "RepRapFirmware.h"
+
const FilePosition GCODE_HEADER_SIZE = 8192uL; // How many bytes to read from the header
const FilePosition GCODE_FOOTER_SIZE = 400000uL; // How many bytes to read from the footer
diff --git a/src/RADDS/Network.h b/src/RADDS/Network.h
index 564702c9..4f91e86e 100644
--- a/src/RADDS/Network.h
+++ b/src/RADDS/Network.h
@@ -1,13 +1,13 @@
#ifndef NETWORK_H
#define NETWORK_H
-#include <inttypes.h>
-#include "Platform.h"
+#include "RepRapFirmware.h"
+#include "MessageType.h"
-const uint8_t MAC_ADDRESS[6] = { 0, 0, 0, 0, 0, 0 };
-const uint8_t IP_ADDRESS[4] = { 0, 0, 0, 0 };
-const uint8_t NET_MASK[4] = { 0, 0, 0, 0 };
-const uint8_t GATE_WAY[4] = { 0, 0, 0, 0 };
+const uint8_t DefaultMacAddress[6] = { 0, 0, 0, 0, 0, 0 };
+const uint8_t DefaultIpAddress[4] = { 0, 0, 0, 0 };
+const uint8_t DefaultNetMask[4] = { 0, 0, 0, 0 };
+const uint8_t DefaultGateway[4] = { 0, 0, 0, 0 };
// The main network class that drives the network.
class Network
@@ -23,8 +23,8 @@ public:
void Interrupt() const { };
void Diagnostics(MessageType mtype) const { };
- boolean IsEnabled() const { return false; }
- boolean InLwip() const { return false; }
+ bool IsEnabled() const { return false; }
+ bool InLwip() const { return false; }
void SetHostname(const char *name) const { };
void SetHttpPort(uint16_t port) const { };
uint16_t GetHttpPort() const { return (uint16_t)0; }
diff --git a/src/RADDS/Pins_radds.h b/src/RADDS/Pins_radds.h
index 3cb48c4d..ced457c3 100644
--- a/src/RADDS/Pins_radds.h
+++ b/src/RADDS/Pins_radds.h
@@ -34,8 +34,6 @@ const int8_t HEATERS = 4;
const size_t MAX_AXES = 6; // FIXME The maximum number of movement axes in the machine, usually just X, Y and Z, <= DRIVES
const size_t MIN_AXES = 3; // The minimum and default number of axes
-const size_t DELTA_AXES = 3; // The number of axis involved in delta movement
-const size_t CART_AXES = 3; // The number of Cartesian axes
const size_t MaxExtruders = DRIVES - MIN_AXES; // The maximum number of extruders
const size_t NUM_SERIAL_CHANNELS = 2;
diff --git a/src/RADDS/Webserver.h b/src/RADDS/Webserver.h
index 0c8accb0..de65d6cf 100644
--- a/src/RADDS/Webserver.h
+++ b/src/RADDS/Webserver.h
@@ -1,8 +1,8 @@
#ifndef WEBSERVER_H
#define WEBSERVER_H
-#include <inttypes.h>
-#include "OutputMemory.h"
+#include "RepRapFirmware.h"
+#include "MessageType.h"
// List of protocols that can execute G-Codes
enum class WebSource
@@ -32,7 +32,9 @@ public:
inline void Webserver::HandleGCodeReply(const WebSource source, OutputBuffer *reply) const
{
if (reply != (OutputBuffer *)0)
+ {
OutputBuffer::ReleaseAll(reply);
+ }
}
#endif
diff --git a/src/RepRapFirmware.cpp b/src/RepRapFirmware.cpp
index d73ec507..9a09fbd3 100644
--- a/src/RepRapFirmware.cpp
+++ b/src/RepRapFirmware.cpp
@@ -158,6 +158,10 @@ Licence: GPL
#include "RepRapFirmware.h"
+#include "MessageType.h"
+#include "Platform.h"
+#include "RepRap.h"
+
// We just need one instance of RepRap; everything else is contained within it and hidden
RepRap reprap;
diff --git a/src/RepRapFirmware.h b/src/RepRapFirmware.h
index 74640eee..03783fb6 100644
--- a/src/RepRapFirmware.h
+++ b/src/RepRapFirmware.h
@@ -26,8 +26,10 @@ Licence: GPL
#include <cfloat>
#include <cstdarg>
+#include "ecv.h"
#include "Core.h"
#include "Configuration.h"
+#include "Pins.h"
#include "Libraries/General/StringRef.h"
// Module numbers and names, used for diagnostics and debug
@@ -49,50 +51,51 @@ enum Module : uint8_t
extern const char *moduleName[];
-// Warn of what's to come, so we can use pointers to classes...
-
+// Warn of what's to come, so we can use pointers to classes without including the entire header files
class Network;
class Platform;
class Webserver;
class GCodes;
class Move;
+class DDA;
class Heat;
class Tool;
class Roland;
class PrintMonitor;
class RepRap;
class FileStore;
+class OutputBuffer;
+class OutputStack;
// A single instance of the RepRap class contains all the others
-
extern RepRap reprap;
// Functions and globals not part of any class
-
extern "C" void debugPrintf(const char* fmt, ...);
bool StringEndsWith(const char* string, const char* ending);
bool StringStartsWith(const char* string, const char* starting);
bool StringEquals(const char* s1, const char* s2);
int StringContains(const char* string, const char* match);
-
-// Macro to assign an array from an initializer list
+
+// Macro to assign an array from an initialiser list
#define ARRAY_INIT(_dest, _init) static_assert(sizeof(_dest) == sizeof(_init), "Incompatible array types"); memcpy(_dest, _init, sizeof(_init));
+// A string buffer used for temporary purposes
extern StringRef scratchString;
-#include "OutputMemory.h"
-#include "Network.h"
-#include "Platform.h"
-#include "Webserver.h"
-#include "GCodes/GCodes.h"
-#include "Movement/Move.h"
-#include "Heating/Heat.h"
-#include "Tool.h"
-#include "Roland.h"
-#include "PrintMonitor.h"
-#include "Reprap.h"
+// Common definitions used by more than one module
+const size_t CART_AXES = 3; // The number of Cartesian axes
+const size_t X_AXIS = 0, Y_AXIS = 1, Z_AXIS = 2, E0_AXIS = 3; // The indices of the Cartesian axes in drive arrays
-#endif
+// Common conversion factors
+const float minutesToSeconds = 60.0;
+const float secondsToMinutes = 1.0/minutesToSeconds;
+const float SecondsToMillis = 1000.0;
+const float MillisToSeconds = 0.001;
+// Type of an offset in a file
+typedef uint32_t FilePosition;
+const FilePosition noFilePosition = 0xFFFFFFFF;
+#endif
diff --git a/src/Reprap.cpp b/src/Reprap.cpp
index d4d4a692..6459c801 100644
--- a/src/Reprap.cpp
+++ b/src/Reprap.cpp
@@ -1,5 +1,14 @@
-#include "RepRapFirmware.h"
-#include <ctime>
+#include "RepRap.h"
+
+#include "Network.h"
+#include "Movement/Move.h"
+#include "GCodes/GCodes.h"
+#include "Heating/Heat.h"
+#include "Platform.h"
+#include "PrintMonitor.h"
+#include "Tool.h"
+#include "Webserver.h"
+#include "Version.h"
// RepRap member functions.
diff --git a/src/Reprap.h b/src/Reprap.h
index 8b80d527..7d63152a 100644
--- a/src/Reprap.h
+++ b/src/Reprap.h
@@ -21,6 +21,9 @@ Licence: GPL
#ifndef REPRAP_H
#define REPRAP_H
+#include "RepRapFirmware.h"
+#include "MessageType.h"
+
enum class ResponseSource
{
HTTP,
diff --git a/src/Storage/FileStore.cpp b/src/Storage/FileStore.cpp
index cc8207fd..df435e9e 100644
--- a/src/Storage/FileStore.cpp
+++ b/src/Storage/FileStore.cpp
@@ -4,6 +4,7 @@
#include "FileStore.h"
#include "MassStorage.h"
#include "Platform.h"
+#include "RepRap.h"
uint32_t FileStore::longestWriteTime = 0;
diff --git a/src/Storage/FileStore.h b/src/Storage/FileStore.h
index e998e3e8..29ace01c 100644
--- a/src/Storage/FileStore.h
+++ b/src/Storage/FileStore.h
@@ -6,8 +6,6 @@
#include "Core.h"
#include "Libraries/Fatfs/ff.h"
-typedef uint32_t FilePosition;
-const FilePosition noFilePosition = 0xFFFFFFFF;
const size_t FileBufLen = 256; // 512 would be more efficient, but need to free up some RAM first
enum class IOStatus : uint8_t
diff --git a/src/Storage/MassStorage.cpp b/src/Storage/MassStorage.cpp
index b296329d..2084b359 100644
--- a/src/Storage/MassStorage.cpp
+++ b/src/Storage/MassStorage.cpp
@@ -1,4 +1,6 @@
-#include "RepRapFirmware.h"
+#include "MassStorage.h"
+#include "Platform.h"
+#include "RepRap.h"
#include "sd_mmc.h"
// Static helper functions - not declared as class members to avoid having to include sd_mmc.h everywhere
diff --git a/src/Storage/MassStorage.h b/src/Storage/MassStorage.h
index ee355796..2371e671 100644
--- a/src/Storage/MassStorage.h
+++ b/src/Storage/MassStorage.h
@@ -1,10 +1,11 @@
#ifndef MASSSTORAGE_H
#define MASSSTORAGE_H
+#include "RepRapFirmware.h"
+#include "Pins.h"
+#include "Libraries/FatFs/ff.h"
#include <ctime>
-class Platform;
-
// Info returned by FindFirst/FindNext calls
struct FileInfo
{
diff --git a/src/Tool.cpp b/src/Tool.cpp
index e5e4bcbe..e98beaa8 100644
--- a/src/Tool.cpp
+++ b/src/Tool.cpp
@@ -23,7 +23,12 @@
****************************************************************************************************/
-#include "RepRapFirmware.h"
+#include "Tool.h"
+
+#include "GCodes/GCodes.h"
+#include "Heating/Heat.h"
+#include "Platform.h"
+#include "RepRap.h"
Tool * Tool::freelist = nullptr;
diff --git a/src/Tool.h b/src/Tool.h
index 042285fd..7fe9a845 100644
--- a/src/Tool.h
+++ b/src/Tool.h
@@ -26,6 +26,8 @@ Licence: GPL
#ifndef TOOL_H_
#define TOOL_H_
+#include "RepRapFirmware.h"
+
const uint32_t DefaultXAxisMapping = 0x0001; // by default, X is mapped to X
class Tool
diff --git a/src/Version.h b/src/Version.h
new file mode 100644
index 00000000..5f7a3706
--- /dev/null
+++ b/src/Version.h
@@ -0,0 +1,21 @@
+/*
+ * Version.h
+ *
+ * Created on: 25 Dec 2016
+ * Author: David
+ */
+
+#ifndef SRC_VERSION_H_
+#define SRC_VERSION_H_
+
+#ifndef VERSION
+# define VERSION "1.17+2"
+#endif
+
+#ifndef DATE
+# define DATE "2016-12-25"
+#endif
+
+#define AUTHORS "reprappro, dc42, zpl, t3p3, dnewman"
+
+#endif /* SRC_VERSION_H_ */
diff --git a/src/Duet/Webserver.cpp b/src/Webserver/Webserver.cpp
index 18cad76b..15d34e5e 100644
--- a/src/Duet/Webserver.cpp
+++ b/src/Webserver/Webserver.cpp
@@ -83,13 +83,19 @@
****************************************************************************************************/
#include "RepRapFirmware.h"
+#include "Webserver.h"
+#include "NetworkTransaction.h"
+#include "Platform.h"
+#include "Network.h"
+#include "RepRap.h"
+#include "GCodes/GCodes.h"
+#include "PrintMonitor.h"
//***************************************************************************************************
const char* overflowResponse = "overflow";
const char* badEscapeResponse = "bad escape";
-
//********************************************************************************************
//
//**************************** Generic Webserver implementation ******************************
@@ -110,7 +116,7 @@ void Webserver::Init()
// initialise the webserver class
longWait = platform->Time();
webserverActive = true;
- readingConnection = nullptr;
+ readingConnection = NoConnection;
// initialise all protocol handlers
httpInterpreter->ResetState();
@@ -221,7 +227,7 @@ void Webserver::Spin()
// calling either Commit(), Discard() or Defer()
if (interpreter->CharFromClient(c))
{
- readingConnection = nullptr;
+ readingConnection = NoConnection;
break;
}
}
@@ -231,18 +237,18 @@ void Webserver::Spin()
// message length exceeds the TCP MSS. Notify the current ProtocolInterpreter about this,
// which will remove the current transaction too
interpreter->NoMoreDataAvailable();
- readingConnection = nullptr;
+ readingConnection = NoConnection;
break;
}
}
}
}
- else if (readingConnection != nullptr)
+ else if (readingConnection != NoConnection)
{
// We failed to find a transaction for a reading connection.
// This should never happen, but if it does, terminate this connection instantly
platform->Message(HOST_MESSAGE, "Error: Transaction for reading connection not found\n");
- readingConnection->Terminate();
+ Network::Terminate(readingConnection);
}
network->Unlock(); // unlock LWIP again
}
@@ -339,10 +345,10 @@ uint16_t Webserver::GetGCodeBufferSpace(const WebSource source) const
// Handle immediate disconnects here (cs will be freed after this call)
// May be called by ISR, but not while LwIP is NOT locked
-void Webserver::ConnectionLost(const ConnectionState *cs)
+void Webserver::ConnectionLost(Connection conn)
{
// Inform protocol handlers that this connection has been lost
- uint16_t localPort = cs->GetLocalPort();
+ uint16_t localPort = Network::GetLocalPort(conn);
ProtocolInterpreter *interpreter;
switch (localPort)
{
@@ -373,14 +379,14 @@ void Webserver::ConnectionLost(const ConnectionState *cs)
// Print some debug information and notify the protocol interpreter
if (reprap.Debug(moduleWebserver))
{
- platform->MessageF(HOST_MESSAGE, "ConnectionLost called for local port %d (remote port %d)\n", localPort, cs->GetRemotePort());
+ platform->MessageF(HOST_MESSAGE, "ConnectionLost called for local port %d (remote port %d)\n", localPort, Network::GetRemotePort(conn));
}
- interpreter->ConnectionLost(cs);
+ interpreter->ConnectionLost(conn);
// Don't process any more data from this connection if has gone down
- if (readingConnection == cs)
+ if (readingConnection == conn)
{
- readingConnection = nullptr;
+ readingConnection = NoConnection;
}
}
@@ -539,7 +545,7 @@ Webserver::HttpInterpreter::HttpInterpreter(Platform *p, Webserver *ws, Network
{
gcodeReadIndex = gcodeWriteIndex = 0;
gcodeReply = new OutputStack();
- deferredRequestConnection = nullptr;
+ deferredRequestConnection = NoConnection;
seq = 0;
}
@@ -900,7 +906,7 @@ void Webserver::HttpInterpreter::SendJsonResponse(const char* command)
if (!OutputBuffer::Allocate(jsonResponse))
{
// Reset the connection immediately if we cannot write any data. Should never happen
- webserver->currentTransaction->GetConnection()->Terminate();
+ Network::Terminate(webserver->currentTransaction->GetConnection());
return;
}
@@ -1060,7 +1066,7 @@ void Webserver::HttpInterpreter::GetJsonResponse(const char* request, OutputBuff
}
else if (StringEquals(request, "fileinfo"))
{
- if (deferredRequestConnection != nullptr)
+ if (deferredRequestConnection != NoConnection)
{
// Don't allow multiple deferred requests to be processed at once
webserver->currentTransaction->Defer(DeferralMode::ResetData);
@@ -1133,17 +1139,17 @@ void Webserver::HttpInterpreter::NoMoreDataAvailable()
}
// May be called from ISR!
-void Webserver::HttpInterpreter::ConnectionLost(const ConnectionState *cs)
+void Webserver::HttpInterpreter::ConnectionLost(Connection conn)
{
// Make sure deferred requests are cancelled
- if (deferredRequestConnection == cs)
+ if (deferredRequestConnection == conn)
{
reprap.GetPrintMonitor()->StopParsing(filenameBeingProcessed);
- deferredRequestConnection = nullptr;
+ deferredRequestConnection = NoConnection;
}
// If we couldn't read an entire request from a connection, reset our state here again
- if (webserver->readingConnection == cs)
+ if (webserver->readingConnection == conn)
{
ResetState();
}
@@ -1152,8 +1158,8 @@ void Webserver::HttpInterpreter::ConnectionLost(const ConnectionState *cs)
// because the client *might* have two instances of the web interface running.
if (uploadState == uploadOK)
{
- const uint32_t remoteIP = cs->GetRemoteIP();
- const uint16_t remotePort = cs->GetRemotePort();
+ const uint32_t remoteIP = Network::GetRemoteIP(conn);
+ const uint16_t remotePort = Network::GetRemotePort(conn);
for(size_t i = 0; i < numSessions; i++)
{
if (sessions[i].ip == remoteIP && sessions[i].isPostUploading && sessions[i].postPort == remotePort)
@@ -1191,7 +1197,7 @@ bool Webserver::HttpInterpreter::CanParseData()
// Are we still processing a deferred request?
if (deferredRequestConnection == webserver->currentTransaction->GetConnection())
{
- if (deferredRequestConnection->IsConnected())
+ if (Network::IsConnected(deferredRequestConnection))
{
// Process more of this request. If it doesn't finish this time, it will be appended to the list
// of ready transactions again, which will ensure it can be processed later again
@@ -1557,8 +1563,8 @@ bool Webserver::HttpInterpreter::ProcessMessage()
}
else if (IsAuthenticated() && StringEquals(commandWords[0], "POST"))
{
- bool isUploadRequest = (StringEquals(commandWords[1], KO_START "upload"));
- isUploadRequest |= (commandWords[1][0] == '/' && StringEquals(commandWords[1] + 1, KO_START "upload"));
+ const bool isUploadRequest = (StringEquals(commandWords[1], KO_START "upload"))
+ || (commandWords[1][0] == '/' && StringEquals(commandWords[1] + 1, KO_START "upload"));
if (isUploadRequest)
{
if (numQualKeys > 0 && StringEquals(qualifiers[0].key, "name"))
@@ -1876,7 +1882,7 @@ void Webserver::HttpInterpreter::HandleGCodeReply(const char *reply)
void Webserver::HttpInterpreter::ProcessDeferredRequest()
{
OutputBuffer *jsonResponse = nullptr;
- const ConnectionState *lastDeferredConnection = deferredRequestConnection;
+ const Connection lastDeferredConnection = deferredRequestConnection;
// At the moment only file info requests are deferred.
// Parsing the file may take a while, so keep LwIP running while we're waiting
@@ -1891,7 +1897,7 @@ void Webserver::HttpInterpreter::ProcessDeferredRequest()
NetworkTransaction *transaction = webserver->currentTransaction;
if (gotFileInfo)
{
- deferredRequestConnection = nullptr;
+ deferredRequestConnection = NoConnection;
// Got it - send the response now
transaction->Write("HTTP/1.1 200 OK\n");
@@ -1981,11 +1987,11 @@ void Webserver::FtpInterpreter::ConnectionEstablished()
}
// May be called from ISR!
-void Webserver::FtpInterpreter::ConnectionLost(const ConnectionState *cs)
+void Webserver::FtpInterpreter::ConnectionLost(Connection conn)
{
connectedClients--;
- if (cs->GetLocalPort() != FTP_PORT)
+ if (Network::GetLocalPort(conn) != FTP_PORT)
{
// Did everything work out? Usually this is only called for uploads
if (network->AcquireFTPTransaction())
@@ -1993,7 +1999,7 @@ void Webserver::FtpInterpreter::ConnectionLost(const ConnectionState *cs)
webserver->currentTransaction = network->GetTransaction();
if (state == doingPasvIO)
{
- if (uploadState != uploadError && !cs->IsTerminated())
+ if (uploadState != uploadError && !Network::IsTerminated(conn))
{
SendReply(226, "Transfer complete.");
FinishUpload(0);
@@ -2615,7 +2621,7 @@ void Webserver::TelnetInterpreter::ConnectionEstablished()
}
// May be called from ISR!
-void Webserver::TelnetInterpreter::ConnectionLost(const ConnectionState *cs)
+void Webserver::TelnetInterpreter::ConnectionLost(Connection conn)
{
connectedClients--;
if (connectedClients == 0)
diff --git a/src/Webserver/Webserver.h b/src/Webserver/Webserver.h
new file mode 100644
index 00000000..ef14bcab
--- /dev/null
+++ b/src/Webserver/Webserver.h
@@ -0,0 +1,385 @@
+/****************************************************************************************************
+
+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
+
+#include "NetworkDefs.h"
+#include "RepRapFirmware.h"
+#include "MessageType.h"
+#include "Storage/FileData.h"
+
+/* Generic values */
+
+const size_t gcodeBufferLength = 512; // size of our gcode ring buffer, preferably a power of 2
+
+/* HTTP */
+
+#define KO_START "rr_"
+#define KO_FIRST 3
+
+const uint16_t webMessageLength = TCP_MSS; // maximum length of the web message we accept after decoding
+const size_t minHttpResponseSize = 768; // minimum number of bytes required for an HTTP response
+
+const size_t maxCommandWords = 4; // max number of space-separated words in the command
+const size_t maxQualKeys = 5; // max number of key/value pairs in the qualifier
+const size_t maxHeaders = 16; // max number of key/value pairs in the headers
+
+const size_t maxHttpSessions = 8; // maximum number of simultaneous HTTP sessions
+const uint32_t httpSessionTimeout = 8000; // HTTP session timeout in milliseconds
+
+/* FTP */
+
+const uint16_t ftpMessageLength = 128; // maximum line length for incoming FTP commands
+const uint32_t ftpPasvPortTimeout = 10000; // maximum time to wait for an FTP data connection in milliseconds
+
+/* Telnet */
+
+const uint32_t telnetSetupDuration = 4000; // ignore the first Telnet request within this duration (in ms)
+
+
+class Webserver;
+
+// List of protocols that can execute G-Codes
+enum class WebSource
+{
+ HTTP,
+ Telnet
+};
+
+// This is the abstract class for all supported protocols
+// Any inherited class should implement a state machine to increase performance and reduce memory usage.
+class ProtocolInterpreter
+{
+ public:
+
+ ProtocolInterpreter(Platform *p, Webserver *ws, Network *n);
+ virtual ~ProtocolInterpreter() { } // to keep Eclipse happy
+ virtual void Diagnostics(MessageType mtype) = 0;
+ virtual void Spin();
+
+ virtual void ConnectionEstablished();
+ virtual void ConnectionLost(Connection conn /*const ConnectionState *cs*/) { }
+ virtual bool CanParseData();
+ virtual bool CharFromClient(const char c) = 0;
+ virtual void NoMoreDataAvailable();
+
+ virtual bool DoingFastUpload() const;
+ virtual void DoFastUpload();
+ void CancelUpload(); // may be called from ISR!
+
+ protected:
+
+ Platform *platform;
+ Webserver *webserver;
+ Network *network;
+
+ // Information for file uploading
+ enum UploadState
+ {
+ notUploading, // no upload in progress
+ uploadOK, // upload in progress, no error so far
+ uploadError // upload in progress but had error
+ };
+
+ UploadState uploadState;
+ FileData fileBeingUploaded;
+ char filenameBeingUploaded[FILENAME_LENGTH];
+
+ bool StartUpload(FileStore *file, const char *fileName);
+ bool IsUploading() const;
+ bool FinishUpload(uint32_t fileLength);
+};
+
+class Webserver
+{
+public:
+
+ friend class Platform;
+ friend class ProtocolInterpreter;
+
+ 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;
+
+ // 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;
+
+ void ConnectionLost(Connection conn /*const ConnectionState *cs*/);
+ void ConnectionError();
+
+protected:
+
+ class HttpInterpreter : public ProtocolInterpreter
+ {
+ public:
+
+ HttpInterpreter(Platform *p, Webserver *ws, Network *n);
+ void Spin();
+ void Diagnostics(MessageType mtype) override;
+ void ConnectionLost(Connection conn /*const ConnectionState *cs*/) override;
+ bool CanParseData() override;
+ bool CharFromClient(const char c) override;
+ void NoMoreDataAvailable() override;
+ void ResetState();
+ void ResetSessions();
+
+ bool DoingFastUpload() const override;
+ void DoFastUpload();
+
+ bool GCodeAvailable() const;
+ char ReadGCode();
+ void HandleGCodeReply(OutputBuffer *reply);
+ void HandleGCodeReply(const char *reply);
+ uint16_t GetGCodeBufferSpace() const;
+ uint32_t GetReplySeq() const;
+
+ private:
+
+ // HTTP server state enumeration. The order is important, in particular xxxEsc1 must follow xxx, and xxxEsc2 must follow xxxEsc1.
+ // We assume that qualifier keys do not contain escapes, because none of ours needs to be encoded. If we are sent escapes in the key,
+ // it won't do any harm, but the key won't be recognised even if it would be valid were it decoded.
+ enum HttpState
+ {
+ doingCommandWord, // receiving a word in the first line of the HTTP request
+ 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
+ doingHeaderKey, // receiving a header key
+ expectingHeaderValue, // expecting a header value
+ doingHeaderValue, // receiving a header value
+ doingHeaderContinuation // received a newline after a header value
+ };
+ HttpState state;
+
+ struct KeyValueIndices
+ {
+ const char* key;
+ const char* value;
+ };
+
+ void SendFile(const char* nameOfFileToSend, bool isWebFile);
+ void SendGCodeReply();
+ void SendJsonResponse(const char* command);
+ void GetJsonResponse(const char* request, OutputBuffer *&response, const char* key, const char* value, size_t valueLength, bool& keepOpen);
+ bool ProcessMessage();
+ bool RejectMessage(const char* s, unsigned int code = 500);
+
+ // Buffers for processing HTTP input
+ char clientMessage[webMessageLength + 3]; // holds the command, qualifier, and headers
+ size_t clientPointer; // current index into clientMessage
+ char decodeChar;
+
+ const char* commandWords[maxCommandWords];
+ KeyValueIndices qualifiers[maxQualKeys + 1]; // offsets into clientQualifier of the key/value pairs, the +1 is needed so that values can contain nulls
+ KeyValueIndices headers[maxHeaders]; // offsets into clientHeader of the key/value pairs
+ size_t numCommandWords;
+ size_t numQualKeys; // number of qualifier keys we have found, <= maxQualKeys
+ size_t numHeaderKeys; // number of keys we have found, <= maxHeaders
+
+ // HTTP sessions
+ struct HttpSession
+ {
+ uint32_t ip;
+ uint32_t lastQueryTime;
+ bool isPostUploading;
+ uint16_t postPort;
+ };
+
+ HttpSession sessions[maxHttpSessions];
+ uint8_t numSessions;
+ uint8_t clientsServed;
+
+ bool Authenticate();
+ bool IsAuthenticated() const;
+ void UpdateAuthentication();
+ bool RemoveAuthentication();
+
+ // Deal with incoming G-Codes
+
+ char gcodeBuffer[gcodeBufferLength];
+ uint16_t gcodeReadIndex, gcodeWriteIndex; // head and tail indices into gcodeBuffer
+
+ void LoadGcodeBuffer(const char* gc);
+ void ProcessGcode(const char* gc);
+ void StoreGcodeData(const char* data, uint16_t len);
+
+ // Responses from GCodes class
+
+ uint32_t seq; // Sequence number for G-Code replies
+ OutputStack *gcodeReply;
+
+ // File uploads
+ uint32_t postFileLength, uploadedBytes; // How many POST bytes do we expect and how many have already been written?
+ time_t fileLastModified;
+
+ // Deferred requests (rr_fileinfo)
+ volatile Connection deferredRequestConnection; // Which connection expects a response for a deferred request?
+ char filenameBeingProcessed[FILENAME_LENGTH]; // The filename being processed (for rr_fileinfo)
+
+ void ProcessDeferredRequest();
+ };
+ HttpInterpreter *httpInterpreter;
+
+ class FtpInterpreter : public ProtocolInterpreter
+ {
+ public:
+
+ FtpInterpreter(Platform *p, Webserver *ws, Network *n);
+ void Diagnostics(MessageType mtype) override;
+
+ void ConnectionEstablished() override;
+ void ConnectionLost(Connection conn /*const ConnectionState *cs*/) override;
+ bool CharFromClient(const char c) override;
+ void ResetState();
+
+ bool DoingFastUpload() const override;
+
+ private:
+
+ enum FtpState
+ {
+ idle, // no client connected
+ authenticating, // not logged in
+ authenticated, // logged in
+ waitingForPasvPort, // waiting for connection to be established on PASV port
+ pasvPortConnected, // client connected to PASV port, ready to send data
+ doingPasvIO // client is connected and data is being transferred
+ };
+ FtpState state;
+ uint8_t connectedClients;
+
+ char clientMessage[ftpMessageLength];
+ size_t clientPointer;
+
+ char filename[FILENAME_LENGTH];
+ char currentDir[FILENAME_LENGTH];
+
+ uint32_t portOpenTime;
+
+ void ProcessLine();
+ void SendReply(int code, const char *message, bool keepConnection = true);
+ void SendFeatures();
+
+ void ReadFilename(uint16_t start);
+ void ChangeDirectory(const char *newDirectory);
+ };
+ FtpInterpreter *ftpInterpreter;
+
+ class TelnetInterpreter : public ProtocolInterpreter
+ {
+ public:
+
+ TelnetInterpreter(Platform *p, Webserver *ws, Network *n);
+ void Diagnostics(MessageType mtype) override;
+
+ void ConnectionEstablished() override;
+ void ConnectionLost(Connection conn /*const ConnectionState *cs*/) override;
+ bool CanParseData() override;
+ bool CharFromClient(const char c) override;
+ void ResetState();
+
+ bool GCodeAvailable() const;
+ char ReadGCode();
+ void HandleGCodeReply(OutputBuffer *reply);
+ void HandleGCodeReply(const char *reply);
+ uint16_t GetGCodeBufferSpace() const;
+
+ void SendGCodeReply();
+
+ private:
+
+ enum TelnetState
+ {
+ idle, // not connected
+ justConnected, // not logged in, but the client has just connected
+ authenticating, // not logged in
+ authenticated // logged in
+ };
+ TelnetState state;
+ uint8_t connectedClients;
+ uint32_t connectTime;
+
+ bool processNextLine;
+ char clientMessage[GCODE_LENGTH];
+ size_t clientPointer;
+
+ bool ProcessLine();
+
+ // Deal with incoming G-Codes
+
+ char gcodeBuffer[gcodeBufferLength];
+ uint16_t gcodeReadIndex, gcodeWriteIndex; // head and tail indices into gcodeBuffer
+
+ void ProcessGcode(const char* gc);
+ void StoreGcodeData(const char* data, uint16_t len);
+
+ // Converted response from GCodes class (NL -> CRNL)
+
+ OutputBuffer * volatile gcodeReply;
+ };
+ TelnetInterpreter *telnetInterpreter;
+
+ private:
+
+ Platform* platform;
+ Network* network;
+ bool webserverActive;
+ NetworkTransaction *currentTransaction;
+ volatile Connection readingConnection;
+
+ float longWait;
+};
+
+inline bool ProtocolInterpreter::CanParseData() { return true; }
+inline bool ProtocolInterpreter::DoingFastUpload() const { return false; }
+inline bool ProtocolInterpreter::IsUploading() const { return uploadState != notUploading; }
+
+inline uint32_t Webserver::GetReplySeq() const { return httpInterpreter->GetReplySeq(); }
+
+inline uint16_t Webserver::HttpInterpreter::GetGCodeBufferSpace() const { return (gcodeReadIndex - gcodeWriteIndex - 1u) % gcodeBufferLength; }
+inline bool Webserver::HttpInterpreter::GCodeAvailable() const { return gcodeReadIndex != gcodeWriteIndex; }
+inline uint32_t Webserver::HttpInterpreter::GetReplySeq() const { return seq; }
+
+inline uint16_t Webserver::TelnetInterpreter::GetGCodeBufferSpace() const { return (gcodeReadIndex - gcodeWriteIndex - 1u) % gcodeBufferLength; }
+inline bool Webserver::TelnetInterpreter::GCodeAvailable() const { return gcodeReadIndex != gcodeWriteIndex; }
+
+#endif