Arduino IRC bot having issues - Out of SRAM?

Tonight I've been working on getting my Arduino UNO with the R3 Ethernet shield to connect to the IRC server that im using. Getting the arduino to connect to the server was simple enough with the Ethernet libray. But parsing strings or chars in a manner that are manageable and manipulable was a hassle.

At first i used char arrays to store the incoming messages from the server, but not having vast amount of C++ knowledge i ended up confused and derailed. I then tried to take use of the String class, even though it is more memory intensive it was much easier to handle. After that making the Arduino do the initial handshake with the server was a breeze, not soon after it responded to pings and was idle sitting in our channel observing.

But when i asked the arduino to answer my PM's it started to freeze at random.
It connected fine every time. i could see messages for a minute or so before the stream went silent.

I would believe its the strings that causes SRAM to overload (stack overflow?). The arduino sometimes restarts automatically and begins from scratch without me touching the reset.

Right now ive removed the PM answering part of the code and connected it to the server. It seems to be playing ping pong fine now. only been on for about 15 min so far. So i thought id come here and see if anyone of you could point out eventual pitfalls ive ventured into, or general mistakes ive made.

Also on a side note.

FYI: The strings that are stored in temp are usualy around 130~200 bytes large.

TL;DR
Arduino IRC bot stops working after couple of minutes. Possible SRAM fault?
Please have a look at my code.

Hope you guys can help me out
-Thomas

#include <SPI.h>
#include <Ethernet.h>

// ----------VARIABLES----------

// Ethernet Related
byte mac[] = { 
  0x00, 0xAA, 0xBB, 0xCC, 0xDE, 0x01 };
IPAddress ip(10,0,0,100);
char serverName[] = "underworld2.no.quakenet.org";

// Handshake
boolean ForceClose = false;
String nick = "CoffieBOT";
String channel = "#####";

// -----------INIT-----------

// initialize the library instance:
EthernetClient client;



void setup() {

  // Open serial communications and wait for port to open:
  Serial.begin(9600);

  // attempt a DHCP connection:
  Serial.println("Attempting to get an IP address using DHCP:");
  if (!Ethernet.begin(mac)) {
    // if DHCP fails, start with a hard-coded address:
    Serial.println("failed to get an IP address using DHCP, trying manually");
    while(true) {
    }
  }
  Serial.print("My address:");
  Serial.println(Ethernet.localIP());

}



void loop()
{
  //Connect to the server
  if (!client.connected()){
    if (client.connect(serverName, 6667)) {
      Serial.println("Connected");
      Handshake(nick);
    }
  }

  readFromServer();



  // Does the server need to shut down?
  if(ForceClose && client.connected()) {
    Serial.println("Closing connection forcefully");
    client.stop();
  } //End of ForceClose
} //End of Loop

void Handshake(String name) {
  client.println("NICK " + name + "\nUSER " + name + " 8 * : CoffieMachine\n");
} //End of Handshake

void readFromServer(){
  String temp;
  String response;

  while (client.available()){

    char c = client.read();

    temp = "";
    //pull out characters from the client serial and assemble a string
    while (client.available() && c != '\n'){
      temp = temp + c;
      c = client.read();
    }
    
    
    Serial.print(temp);
    respond(temp);

  } //End of While

} // End of Read

void respond(String temp) {
  String response = "";
  int space_index = temp.indexOf(' ') + 1;
  int colon_index = temp.indexOf(':', space_index) + 1;
  String recipient = temp.substring(1,temp.indexOf("!"));
  
  if (temp.startsWith("PING")){
    response = temp;
    response.setCharAt(1,'O');
        Serial.println();
  }
  
  //JOIN Channel if Handshake MOTD is displayed
  if (temp.startsWith("221 " + nick + " +i",space_index)){
    response = "JOIN " + channel;
  }  
  
//  // Someone Private Messaged the bot
//  if (temp.startsWith("PRIVMSG " + nick,space_index)) {
//    if (temp.startsWith("Hello",colon_index)) {
//      response = "PRIVMSG " + recipient + " : Hello there" ;
//          Serial.println();
//    }
//    
//  }
//  
  
  
  
  
    client.println(response);

    Serial.println(response);
}

So i thought id come here and see if anyone of you could point out eventual pitfalls ive ventured into, or general mistakes ive made.

Well, yeah, you're using the String class. It has all kinds of issues, including memory leaks and memory fragmenting. Get rid of it.

At first i used char arrays to store the incoming messages from the server, but not having vast amount of C++ knowledge i ended up confused and derailed.

We're here to help. Char arrays are rather straightforward.

Yes, the problem is the simple fact you are using strings.

Strings are fine if you have huge amounts of memory, but not if you don't, and microcontrollers don't.

Definitely switch to using char arrays. Yes, they are a bit more of a pain to get to grips with, but the efficiencies far out weigh the difficulty.

Some other optimisations you can do, which will ensure you use the minimum RAM possible, and will also keep your program size to a minimum:

  • Place all constant values (like the server name, etc) in PROGMEM, not SRAM.
  • Any repeated string constants (things between double quotes used directly in your program more than once) place in a PROGMEM variable (doesn't impact SRAM, but is more efficient from a FLASH size perspective.)
  • Ensure all char arrays are statically allocated to a predefined maximum size - never use malloc() and free(), it's as bad as using the string class.

Started to redo major parts of the code to get rid of the String class.
But now i have another problem.

Now during the handshake the arduino restarts at the same spot...

Ethernet initiated
Opened connection
Attempting Handshake
NOTICE AUTH :*** Looking up your hostname

NOTICE AUTH :*** Checking Ident

PING :2776346576

PONG :2776346576

NOTICE AUTH :*** Found your hostname

NOTICE AUTH :*** No ident response

:underworld2.no.quakenet.org 001 CoffieBOT :Welcome to the QuakeNet IRC Network, CoffieBOT

:underworld2.no.quakenet.org 002 CoffieBOT :Your host is underworld2.no.quakenet.org, running version u2.10.12.10+snircd(1.3.4a)

:underworld2.no.quakenet.org 003 CoffieBOT :This server was created Mon Aug 25 2008 at 13:46:29 CEST

:underworld2.no.quakenet.org 004 CoffieBOT underworld2.no.quakenet.org u2.10.12.10+snircd(1.3.4a) dioswkgxRXInP biklmnopstvrDcCNuMT bklov

:underworld2.no.quakenet.org 005 CoffieBOT WHOX WALLCHOPS WALLVOICES USERIP CPRIVMSG CNOTICE SILENCE=15 MODES=6 MAXCHANNELS=20 MAXBANS=45 NICKLEN=15 :are supported by this server

After instruction 005 it resets and tries to connect again. Got any idea why it crashes there every single time?

Heres the new code:

#include <SPI.h>
#include <Ethernet.h>

// ----------VARIABLES----------

// Ethernet Related
byte mac[] = {
  0x00, 0xAA, 0xBB, 0xCC, 0xDE, 0x01 };
IPAddress ip(10,0,0,100);

  char string[200];

// -----------INIT-----------

// initialize the library instance:
EthernetClient client;

void setup() {

  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  
  if (!Ethernet.begin(mac)) {
    Serial.println("failed to get an IP address using DHCP");
    while(true) {
    }
  } //End of Ethernet Init
  
  Serial.println("Ethernet initiated");

} //End of Setup


void loop() {
  
  //Connect to the server
  if (!client.connected()){
    if (client.connect("underworld2.no.quakenet.org", 6667)) {
      Serial.println("Opened connection");
      Handshake();
    }
  }

  readFromServer();

} //End of Loop

void Handshake() {
  client.println("NICK CoffieBOT \nUSER CoffieBOT 8 * : CoffieMachine\n");
  Serial.println("Attempting Handshake");
} //End of Handshake

void readFromServer(){
  int index = 0;

  while (client.available()){

    char c = client.read();
    string[index] = c;
    index++;
    
    if (c == '\n') {
      string[index] = '\0';
      Serial.println(string);
      respond();
      break;
    }
    
  } //End of While
  
  
  

} // End of Read

void respond() {
  if ( string[0] == 'P' && string[1] == 'I' && string[2] == 'N' && string[3] == 'G') {
    string[1] = 'O';
    postString();
  }
  
  

  clearString();
}

void clearString() {
  for (int x = 0; x < 200; x ++) {
    string[x] = '\0';
    }
}
void postString() {
    client.println(string);
    Serial.println(string);
}

My guess is that command 006 (not shown) is greater than 200 characters in length and is overflowing the string.

Good call Majenko, added an failsafe to the method.

Got a simple way to analyze the arrays?
As you can see; to detect the PING im basically just using 4 && conditions, but that will not work for the rest.

:CoffieBOT!~CoffieBOT@ti0033a380-dhcp1558.bb.online.no MODE CoffieBOT +i

The part between ! and the first " " is not static, and can change.

Considering a for loop looking for the first space and going from there.

Got some pointers?

Got some pointers?

You said it right there :slight_smile:

Take a look at the "strchr()" function for a quick way to find the space.

Then take a look at the "strncmp()" function for comparing substrings.

Untested:

char *intext = ":CoffieBOT!~CoffieBOT@ti0033a380-dhcp1558.bb.online.no MODE CoffieBOT +i";
char *start;

start = strchr(intext,' ');
if(start)
{
  if(strncmp(start,"MODE ",5) == 0)
  {
    // Mode command found.
  }
}

There are lots more string manipulation / comparison functions. A good reference is: http://www.cplusplus.com/reference/clibrary/cstring/

void clearString() {
  for (int x = 0; x < 200; x ++) {
    string[x] = '\0';
    }
}

A string is a NULL terminated array of chars. One NULL terminates the string. Only sloppy coders need 200 of them to terminate a string.

  while (client.available()){

    char c = client.read();
    string[index] = c;
    index++;

It would be a good idea to make sure that there is room in the array BEFORE adding the character.