sequential polling of arduinos via udp

hello,

i'm working on a project where i need a series of networked arduinos to sequentially take sensor measurements (otherwise the sensors interfere with each other). They're all connected via a network, and use UDP messages to let each other know when its their turn to poll the sensors.

The first arduino in the chain has a timer, so it takes a measurement every 5 seconds and starts the chain:

#include <avr/wdt.h>
#include <SPI.h>
#include <Ethernet.h>

//localClient parameters, for sending data to the server.
byte mac[] = {0x90, 0xA2, 0xDA, 0x0F, 0x05, 0x59};
byte ip[] = {192,168,0,250};

byte nextArduino[] = {192, 168, 0, 251};
EthernetUDP UdpClient;
char packetBuffer[UDP_TX_PACKET_MAX_SIZE];
unsigned int localPort = 8888;

//sonar stuff
int NUM_SONARS = 1;
int sonarPin[] = {2, 3, 4, 5};
int triggerPin = 6;
int sonarThreshold = 12.0;
int sonarState[] = {0, 0, 0, 0};
long pulse;
int numPulses = 3;
int pulseArray[] = {0,0,0,0,0};
int filteredMode = 0;

float current_time;
float last_ping;

boolean debug = true;

void setup() {
  if(debug) {
    Serial.begin(9600);
  }
  Ethernet.begin(mac, ip);
  
  if(debug) {
    Serial.print("My IP address: ");
    Serial.println(Ethernet.localIP());
  }

  wdt_enable(WDTO_8S);
  
  UdpClient.begin(localPort);    
    
  for(int i = 0; i < NUM_SONARS; i++) {
    pinMode(sonarPin[i], INPUT);
  }
  pinMode(triggerPin, OUTPUT);
  digitalWrite(triggerPin, LOW);
  current_time = 0;
  last_ping = 0;
}

void loop() {
  wdt_reset();
  current_time = millis();
  
  Serial.println("Checking polling server.");
  int packetSize = UdpClient.parsePacket();
  if (packetSize) {
    if(debug) {
      Serial.print("Received packet of size ");
      Serial.println(packetSize);
      Serial.print("From ");
      IPAddress remote = UdpClient.remoteIP();
      for (int i =0; i < 4; i++) {
        Serial.print(remote[i], DEC);
        if (i < 3) {
          Serial.print(".");
        }
      }
    Serial.print(", port ");
    Serial.println(UdpClient.remotePort());
    }
  }

  UdpClient.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE);
  if(debug) {
    Serial.print("Contents of packet: ");
    Serial.println(packetBuffer);
  }
  
  if(current_time - last_ping > 5000) {
    pingSonars();
  } 

  //fix sensor state for edge detection
  //delay(50);
}

String split(String data, char delimiter, int index) {
  int found = 0;
  int strIndex[] = {0, -1};
  int maxIndex = data.length()-1;

  for(int i=0; i<=maxIndex && found<=index; i++){
    if(data.charAt(i)==delimiter || i==maxIndex){
        found++;
        strIndex[0] = strIndex[1]+1;
        strIndex[1] = (i == maxIndex) ? i+1 : i;
    }
  }

  return found>index ? data.substring(strIndex[0], strIndex[1]) : "";
}

void isort(int *a, int n) {
  for (int i = 1; i < n; ++i) {
    int j = a[i];
    int k;
    for (k = i - 1; (k >= 0) && (j < a[k]); k--) {
      a[k + 1] = a[k];
    }
    a[k + 1] = j;
  }
}

int mode(int *x,int n){

  int i = 0;
  int count = 0;
  int maxCount = 0;
  int mode = 0;
  int bimodal;
  int prevCount = 0;
  while(i<(n-1)){
    prevCount=count;
    count=0;
    while(x[i]==x[i+1]){
      count++;
      i++;
    }
    if(count>prevCount&count>maxCount){
      mode=x[i];
      maxCount=count;
      bimodal=0;
    }
    if(count==0){
      i++;
    }
    if(count==maxCount){//If the dataset has 2 or more modes.
      bimodal=1;
    }
    if(mode==0||bimodal==1){//Return the median if there is no mode.
      mode=x[(n/2)];
    }
    return mode;
  }
}

void printArray(int *a, int n) {

  for (int i = 0; i < n; i++)
  {
    Serial.print(a[i], DEC);
    Serial.print(' ');
  }
  Serial.println();
}

void pingSonars() {
  digitalWrite(6, HIGH);
    for(int i = 0; i < NUM_SONARS; i++) {
    for(int j = 0; j < numPulses; j++) {
      pulse = pulseIn(sonarPin[i], HIGH);
      pulseArray[j] = pulse/147; //convert to inches -- 147 uS per inches
      delay(5);
    }
    isort(pulseArray, numPulses);
    filteredMode = mode(pulseArray,numPulses);
    //printArray(pulseArray,numPulses);
    if(debug) {
    Serial.print("Filtered distance for Sonar ");
    Serial.print(i);
    Serial.print(": ");
    Serial.println(filteredMode);
    }
    if((filteredMode < sonarThreshold) && !sonarState[i]) {
      //if we are closer than the threshold and previously were not, this is a rising edge:
      if(debug) {
      Serial.print("Sonar ");
      Serial.print(i);
      Serial.println(" triggered!");
      }
      sonarState[i] = 1;
    }
    else if (filteredMode > sonarThreshold && sonarState[i]) {
    //if we are greater than the threshold and previously were, this is a falling edge:
    if(debug) {
      Serial.print("Sonar ");
      Serial.print(i);
      Serial.println(" falling!");
    }
      sonarState[i] = 0;
    }
      char message[] = {'G','E','T','\n','S','O','N','A','R','\n','\n'};
      UdpClient.beginPacket(nextArduino, localPort);
      UdpClient.write(message);
      Serial.println("Sent UDP message to next sonar server.");
      UdpClient.endPacket();
      last_ping = millis();
  }
}

The remaining arduinos use the exact same code, but with two differences:

  • only the first one has a reference to the time since last ping; the others only go off when the previous arduino commands them to.
  • each arduino has a different IP Address (but same port).

However, when I run this code, the first arduino always sends a message after 5 seconds, but the second arduino never reacts to it. The second arduino just repeats:

Checking polling server.
Contents of packet:

so I can only assume that, somehow, the message isn't getting to the final arduino. Some things I've checked:
-both arduinos are on the network, I can ping them.
-the first arduino is successfully sending all of the appropriate confirmations for reading the sonar and sending the UDP message

what could be causing this?

thanks

int pollPort = 10000 + (int)ip[3];

EthernetServer pollServer = EthernetServer(pollPort);

Why aren't you just using the same port for all servers?

You didn't provide links to the used libraries, please post these links.

There's no reason they couldn't be the same other than convention of the group I'm working with.

I switched from TCP/IP to UDP in an attempt to simplify the problem, and changed them to all using the same port numbers -- but still the same issue. Any idea what could be causing it?

Oddly enough, if I run the UDPSendReceiveString example, the second arduino receives the UDP messages just fine. Except for the fact that I receive the message from the first arduino twice in a row.

How rather peculiar...

It doesn't seem peculiar from here. I can't see the code you are using.

If you are using the code in your original post, then it doesn't change the ip to the next Arduino. It polls the same ip over and over as far as I can tell.

You are changing the mac address of each ethernet shield, correct? Like the ip addresses, they must be unique.

I change the current IP and the nextArduino's ip for each arduino -- so 192.168.0.250 sends to 192.168.0.251, and 192.168.0.251 sends to 192.168.0.252, and so on...

I am changing the MAC Addresses to match what's printed on the shield. I would imagine if this was an issue, the shields wouldn't even be able to connect to the network, right?

Since my first reply you did change the code of the original post almost completely. It would have been better to post the changed code in a new entry because like this we don't know what of the original post still applies.

You should at least post the code that is running on the other Arduinos, maybe it includes similar errors as the one we already have.

      char message[] = {'G','E','T','\n','S','O','N','A','R','\n','\n'};
      UdpClient.beginPacket(nextArduino, localPort);
      UdpClient.write(message);
      Serial.println("Sent UDP message to next sonar server.");
      UdpClient.endPacket();

What do you expect that UdpClient.write(message) is doing? I guess you think that it puts 11 characters into a UDP packet, which is then sent by UdpClient.endPacket(). The problem is, that the write() method you're using is this one (from Print.h):

    size_t write(const char *str) {
      if (str == NULL) return 0;
      return write((const uint8_t *)str, strlen(str));
    }

To understand what's going on you have to know how strlen() works. It iterates over the character array until it finds a null byte, returning the index of this null byte as the functions result. In your case there is no null byte in the array, so it searches over the end of the array into the rest of the memory until it eventually finds a null byte anywhere.

It would have worked much better if you defined your string the usual way:

char message[] = "GET\nSONAR\n\n";

because this way the compiler inserts the null byte at the end.

As you have a watchdog timer the resets the Arduino every 8 seconds if it went to Nirvana that way you probably didn't notice that you have a problem there. Remove the watchdog during development and insert it when you think your code is production ready.