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

github.com/Duet3D/RepRapFirmware.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.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