Hard fault caused by the TCPServer example from the Spark docs and a simple Python client

Here’s my quick test. I have a Photon (0.4.9) and it’s running a TCPServer in the program below. It accepts a connection and receives 10 MB of data as fast as it can. It takes about 83 seconds or around 123 Kbytes/sec. Then it closes the connection and the process immediately repeats on a new connection. I let this go 30 times, for a total of 300 MB of data received by the Photon. No problems so far. I’m going to try a few other things to see if I can cause trouble.

#include "Particle.h"

const int MAX_CLIENTS = 5;
const int LISTEN_PORT = 7123;
const int CLIENT_BUF_SIZE = 1024;
const unsigned long INACTIVITY_TIMEOUT_MS = 30000;
const int CLOSE_AFTER_SIZE = 1024 * 1024 * 10; // 10 MB, set to -1 for unlimited

class ClientConnection {
public:
	ClientConnection();
	virtual ~ClientConnection();

	void loop();
	bool accept();

protected:
	void clear();
	void readRequest();
	void writeData();

private:
	unsigned char clientBuf[CLIENT_BUF_SIZE];
	bool inUse;
	int clientId;
	TCPClient client;
	int readOffset;
	int writeOffset;
	unsigned long lastUse;
	unsigned char expectedChar;
	unsigned long bytesRead;
	time_t startTime;
};


String localIP;
TCPServer server(LISTEN_PORT);
ClientConnection clients[MAX_CLIENTS];
int nextClientId = 1;

void setup() {
	Serial.begin(9600);

	// From CLI, use something like:
	// particle get test5 localip
	// to get the IP address of the Photon (replace "test5" with your device name)
	localIP = WiFi.localIP(); // localIP must be a global variable
	Particle.variable("localip", localIP);
	Serial.printlnf("server=%s:%d", localIP.c_str(), LISTEN_PORT);

	server.begin();
}

void loop() {
	// Handle any existing connections
	for(int ii = 0; ii < MAX_CLIENTS; ii++) {
		clients[ii].loop();
	}

	// Accept a new one if there is one waiting (and we have a free client)
	for(int ii = 0; ii < MAX_CLIENTS; ii++) {
		if (clients[ii].accept()) {
			break;
		}
	}
}


ClientConnection::ClientConnection() : inUse(false) {
	clear();
}

ClientConnection::~ClientConnection() {
}

void ClientConnection::loop() {
	if (!inUse) {
		return;
	}

	if (client.connected()) {
		readRequest();

		if (millis() - lastUse > INACTIVITY_TIMEOUT_MS) {
			Serial.printlnf("%d: inactivity timeout", clientId);
			client.stop();
			clear();
		}
	}
	else {
		Serial.printlnf("%d: client disconnected", clientId);
		client.stop();
		clear();
	}
}

bool ClientConnection::accept() {
	if (inUse) {
		return false;
	}

	client = server.available();
	if (client.connected()) {
		lastUse = millis();
		inUse = true;
		clientId = nextClientId++;
		startTime = Time.now();
		Serial.printlnf("%d: connection accepted", clientId);
	}
	return true;
}

void ClientConnection::clear() {
	lastUse = 0;
	readOffset = 0;
	writeOffset = 0;
	inUse = false;
	expectedChar = 0;
	bytesRead = 0;
}

void ClientConnection::readRequest() {
	// Note: client.read returns -1 if there is no data; there is no need to call available(),
	// which basically does the same check as the one inside read().

	int count = client.read(clientBuf, CLIENT_BUF_SIZE);
	if (count > 0) {
		for(int ii = 0; ii < count; ii++) {
			if (clientBuf[ii] != expectedChar) {
				Serial.printlnf("%d: mismatch expected %02x got %02x index %d bytesRead %d, closing",
						clientId, expectedChar, ii, bytesRead + ii);
				client.stop();
				clear();
				break;
			}
			expectedChar++;
			bytesRead++;

			if (CLOSE_AFTER_SIZE > 0 && bytesRead >= CLOSE_AFTER_SIZE) {
				time_t now = Time.now();

				Serial.printlnf("%d: received %d bytes in %d sec, closing connection",
						clientId, bytesRead, now - startTime);
				client.stop();
				clear();
				break;
			}
		}
		lastUse = millis();
	}
}

2 Likes