Bi-directional data over LAN

Hello all.

I have two Mega 2560's each with an ethernet shield that will be connected to a LAN within our building. I need the two Mega's to 'know' the status of certain inputs on the opposite Mega. I understand that an application of client-server is required, but I cannot find examples of how to setup that relationship.

Questions - is 'client-server' the best way to get digital (one or two byte) status from each Mega to the other over the LAN? If not, please suggest most economic method. If yes, please provide string or link to tutorial to setup said client-server.

Many thanks in advance.
D

It's a long read but I think most of what you need is here:

Alternatively use UDP, which is simpler but doesn't guarantee delivery and isn't error free.

To explain:
UDP is 'connexionless', which in practice means the data is sent as a packet with no guarantee of delivery and nothing to check if delivery has happened. I don't have a tutorial for UDP but I think it's in the examples for ethernet.

TCP, which is what the topic I linked to is about, first establishes a connection between the 2 devices then, when data is sent, there exists a means to ensure the data is reliably delivered.

Either way you will need static IP addresses for both devices. Do you control the router (or server if it's a server doing DHCP)? Or know who does? You should not just put some static IP address on a device because it seems to be free, that leads to conflict. Ideally reserve them in the router against the MAC address.

Hi @PerryBebbington and thanks for the background info. I am digging through it now. I may come back with more questions, but I'm striving to try to answer my questions with all the info and linked info in your post.

To answer your query about our LAN - yes, the two Mega's have MAC reserved IP addresses (the facility IT guru is tolerant of my machinations in this space :slightly_smiling_face:)

D

1 Like

Hi again @PerryBebbington .

Been reading and trying to understand your linked info. I admit to only picking up a few things that make sense to me.

One. If I want to share 8bits of data back and forth between two Mega's I need to setup a client-server socket - or TCP connection. Right?

Two. Most of the examples shown are for connections to multiple clients. I don't need that. I need to work with only these two Megas.

Three. I did find this example for a client working with a small exchange of data. What would the corresponding example for the server side look like?

Four. I see in the above example the key line is
char c = client.read();
This is the line where the client gets the data from the server, right?
How does one get data from the client to the server?
How does one receive data at the server?

Sorry to be asking REALLY DUMB questions, but in honesty this is all very new to me.

Thx for any and all help.

initially try UDP as it iis the simplest - you just send datagrams to each other

here is a simple UDP chat program where two devices send UDP datagrams to each other

// UDCP chat program using Arduino Ethernet Shield

#include <SPI.h>  
#include <Ethernet.h>
#include <EthernetUdp.h>  
#define UDP_TX_PACKET_MAX_SIZE 100 //increase UDP size

byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};  // MAC address update as required
IPAddress localIP(192, 168, 1, 176);                // local static localIP address
IPAddress remoteIP(192, 168, 1, 68);                // local static localIP address
unsigned int port = 999;                            // port to receive/transmit

char packetBuffer[UDP_TX_PACKET_MAX_SIZE];  //buffer to hold incoming packet

EthernetUDP Udp;  // An EthernetUDP instance to let us send and receive packets over UDP

void setup() {
  Serial.begin(115200);
  Serial.println("\n\nUDP chat program");
  Ethernet.begin(mac, localIP);            // initialize Ethernet shield
  Serial.print("My localIP address: ");    // print local localIP address:
  Serial.println(Ethernet.localIP());
  // Initializes the ethernet UDP library and network settings.
  if (Udp.begin(port)) Serial.println("UDP begin() OK");
  else Serial.println("UDP begin() failed!");
  Serial.print("UDP_TX_PACKET_MAX_SIZE ");
  Serial.println(UDP_TX_PACKET_MAX_SIZE);
}

void loop() {
  // if datagram received read it
  int packetSize = Udp.parsePacket();
  if (packetSize) {
    Serial.print("Received packet of size ");
    Serial.print(packetSize);         // datagram packet size
    Serial.print(" From ");
    remoteIP = Udp.remoteIP();  // from localIP
    Serial.print(remoteIP);
    Serial.print(", port ");            // from port
    Serial.println(Udp.remotePort());
    memset(packetBuffer, 0, UDP_TX_PACKET_MAX_SIZE);
    // read the packet into packetBufffer and print contents
    Udp.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE);
    Serial.print("Contents: ");
    Serial.println(packetBuffer);
  }
  if (Serial.available()) {   // if text entered read it and send it
    char text[100] = { 0 };
    Serial.readBytesUntil('\n', text, 100);
    Serial.print("Transmitting datagram ");
    Serial.println(text);
    Udp.beginPacket(remoteIP, port);   // send to remote localIP and port 999
    Udp.write(text);
    Udp.endPacket();
  }
  delay(10);
}

you can run a copy on each Mega remembering to change the MAC and IP addresses
to test I used a Java program on a PC

// UDPchat.java - simple peer-to-peer chat program using UDP
//           - given remote IP address can send strings using UDP datagrams 
//           - will also wait for datagrams and display contents
// remote IP and port are specified via command line - default IP is 127.0.0.1 (i.e. localhost) and port is 1000  
//
// e.g. to send datagrams to IP 146.227.59.130 port 1006
// java chat 146.227.59.130 1006

import java.io.*;
import java.util.*;
import java.net.*;

public class UDPchat extends Thread
{
   private final static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
   int port=999;//10001;// 10000;                             // port to send/receive datagrams on
   String remoteIPaddress= "192.168.1.177";//"169.254.144.20";//("192.168.1.8");//127.0.0.1");      // IP to send datagrams

   // constructor, parameter is command line parameters
   public UDPchat(String args[]) throws Exception
    {
    // get remote IP address and port from command line parameters
    if (args.length > 0)    remoteIPaddress =  (args[0]);           // get IPaddress
    if (args.length > 1)    port = Integer.parseInt(args[1]);        // get port number
    System.out.println("chat program: IP address " + InetAddress.getLocalHost().toString() + " port " + port );
    
    start();        // start thread to receive and display datagrams

    // loop waiting for keyboard input, send datagram to remote IP                
    while(true)
      try
        {
        String s = in.readLine();                       // read a String
        System.out.println("Sending to " + remoteIPaddress + " socket " + port + " data: " + s);
        byte[] data = s.getBytes();                                     // convert to byte array
        DatagramSocket theSocket = new DatagramSocket();                // create datagram socket and the datagram
        DatagramPacket   theOutput = new DatagramPacket(data, s.length(), InetAddress.getByName(remoteIPaddress), port);
        theSocket.send(theOutput);                                      // and send the datagram
       }
      catch (Exception e) {System.out.println("Eroor sending datagram " + e);}
    }

   // thread run method, receives datagram and display contents as a string
   public void run()                
        {
          try
              {
              // open DatagramSocket to receive 
              DatagramSocket ds = new DatagramSocket(port);
              // loop forever reading datagrams from the DatagramSocket
              while (true)
                 {
                 byte[] buffer = new byte[65507];                       // array to put datagrams in
                 DatagramPacket dp = new DatagramPacket(buffer, buffer.length); // DatagramPacket to hold the datagram
                 ds.receive(dp);                                     // wait for next datagram
                 String s = new String(dp.getData(),0,dp.getLength());        // get contenets as a String
                 System.out.println("UDP datagram length " + s.length()+ "  from IP " + dp.getAddress() + " received: " + s );
s = "Acknowledge\n";
     //            byte[] data = s.getBytes();                                     // convert to byte array
     //   DatagramSocket theSocket = new DatagramSocket();                // create datagram socket and the datagram
     //   DatagramPacket   theOutput = new DatagramPacket(data, data.length, InetAddress.getByName(remoteIPaddress), port);
    //    theSocket.send(theOutput);                                      // and send the datagram

              }
              }
          catch (SocketException se) {System.err.println("chat error " + se); }
          catch (IOException se) {System.err.println("chat error " + se);}
          System.exit(1);                                                       // exit on error
        }


public static void main(String args[]) throws Exception
{
   UDPchat c=new UDPchat(args);
}

}

Mega serial monitor output

UDP chat program
My localIP address: 192.168.1.176
UDP begin() OK
UDP_TX_PACKET_MAX_SIZE 100
Transmitting datagram test 1 fro mega - hello

Received packet of size 24 From 192.168.1.68, port 63305
Contents: test 1 from java - hello
Received packet of size 29 From 192.168.1.68, port 62807
Contents: test 2 from java - 1234567890
Received packet of size 44 From 192.168.1.68, port 61851
Contents: test3 from java - abcdefghijklmnopqrstuvwxyz
Transmitting datagram test 2 from mega - 1234567890987654321

Transmitting datagram test 3 from mega - asdfghjklpoiuytrewqzxcvbnm

Java on PC console output

F:\Ardunio\Networking\Ethernet\EthernetSheildV1\UDP\ChatProgram>java UDPchat 192.168.1.176
chat program: IP address BB-DELL2/192.168.1.68 port 999
UDP datagram length 24  from IP /192.168.1.176 received: test 1 fro mega - hello
test 1 from java - hello
Sending to 192.168.1.176 socket 999 data: test 1 from java - hello
test 2 from java - 1234567890
Sending to 192.168.1.176 socket 999 data: test 2 from java - 1234567890
test3 from java - abcdefghijklmnopqrstuvwxyz
Sending to 192.168.1.176 socket 999 data: test3 from java - abcdefghijklmnopqrstuvwxyz
UDP datagram length 39  from IP /192.168.1.176 received: test 2 from mega - 1234567890987654321
UDP datagram length 46  from IP /192.168.1.176 received: test 3 from mega - asdfghjklpoiuytrewqzxcvbnm

as @PerryBebbington stated UDP is unreliable - transmitted datagrams may arrive at the receiver out of order or not at all

1 Like

First off there are 2 ways to do this, the topic I linked to uses TCP, have a read of my post #3 to understand the difference between UDP and TCP. Although I linked to a TCP topic I think maybe at this stage you should start by using UDP because it's dead simple to do. There's no client / server with UDP you just send a UDP packet to the IP address you want it to go to. Get that working and understand it, also familiarise yourself with the limitations of UDP (no guarantee of delivery, no guarantee of no errors). There should be UDP examples in the library you are using (all my work was done using ESP8266s and WiFi, but I think Ethernet will be the same).

I'll deal with your questions as best I can:

Correct for TCP.

It doesn't matter, just use the sample code and make only 1 connection. Just because it can do more connections doesn't mean you have to use them.

I would expect, but I can't know for sure, that the server code in post #8 of the topic I linked to would work as the server. You will have to modify it for Ethernet rather than WiFi (no SSID and WiFi connections needed for example). I would expect any good server code to be OK for the server because TCP is a standard and any code that complies with the TCP standard should be compatible with any other TCP compliant code.

I can't really comment about that example as it does things very differently to how I would, for example I never use Strings and associated code. This:
char c = client.read();
Gets one byte.

The dumb questions are the ones you don't ask. This forum exists for exactly the kinds of questions you are asking.

I know my replies above don't properly answer your questions but I hope they help somehow. When I wrote those replies in the linked topic I was experimenting myself, getting stuff to work and posting the results of what I did, so I'm only 1 step ahead of where you are now.

Edit:
While I was typing the above @horace posted his reply, post #6. I suggest you go with that and understand it before moving on.

Hello @PerryBebbington and @horace

Okay, took me a while to get back to this, but today I lined up two Mega2560s side-by-each and used @horace sample chat code to create a chat between the two. Success!

Now I am trying to figure out how to (assemble) transmit and receive (dismantle) two integers. Am I forced to coding my two integers to ASCII on the sending end and decoding them from ASCII on the receiving end? Or is there a way to send and receive integer data?

Thx again for your help.
D

no need for ASCII ! you loose precision and spend time converting from and to binary
simplest thing is to use a structure to hold the multiple data elements and transmit it in a datagram
will see if I can find an Ethernet example

have a look at this which will transmit and receive a datagram containing a structure

struct __attribute__((packed)) Struct1 {
  byte StructureID;  // identifies the structure type
  byte NodeID;       // ID of transmitting node
  int16_t seq;       // sequence number
  int32_t distance;
  float voltage;
};

when transmitting binary data between programs which may be running on different processors one has to be careful with data sizes, e.g. on a Mega an int is 16 bits and on a ESP32 is 32bit - hence the use of predefined sizes int16_t (16 bit integer) and int32_t (32 bit integer)
mega code using ethernet shield

// UDP transmit/receive structure using Arduino Ethernet Shield

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

#define nodeID 1  //  <<< set up required node ID

struct __attribute__((packed)) Struct1 {
  byte StructureID;  // identifies the structure type
  byte NodeID;       // ID of transmitting node
  int16_t seq;       // sequence number
  int32_t distance;
  float voltage;
};

Struct1 struct1 = { 1, nodeID, 0, 25, 4.5 }, struct2;

#define UDP_TX_PACKET_MAX_SIZE 100  //increase UDP size

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };  // MAC address update as required
IPAddress localIP(192, 168, 1, 177);                  // local static localIP address
IPAddress remoteIP(192, 168, 1, 176);                 // local static localIP address
unsigned int port = 999;                              // port to receive/transmit

char packetBuffer[UDP_TX_PACKET_MAX_SIZE];  //buffer to hold incoming packet

EthernetUDP Udp;  // An EthernetUDP instance to let us send and receive packets over UDP

void setup() {
  Serial.begin(115200);
  Serial.println("\n\nUDP transmit/receive structure ");
  // select static or dynamic IP initialization
  //Ethernet.begin(mac, localIP);    // static IP initialize Ethernet shield
  if (Ethernet.begin(mac))  // dyname IP initialize Ethernet shield using DHCP
    Serial.println("Configured Ethernet using DHCP OK");
  else Serial.println("Failed to configure Ethernet using DHCP");

  Serial.print("My localIP address: ");  // print local localIP address:
  Serial.println(Ethernet.localIP());
  // Initializes the ethernet UDP library and network settings.
  if (Udp.begin(port)) Serial.println("UDP begin() OK");
  else Serial.println("UDP begin() failed!");
  Serial.print("UDP_TX_PACKET_MAX_SIZE ");
  Serial.println(UDP_TX_PACKET_MAX_SIZE);
}

void loop() {
  // if datagram received read it
  int packetSize = Udp.parsePacket();
  if (packetSize) {
    Serial.print("Received packet of size ");
    Serial.print(packetSize);  // datagram packet size
    Serial.print(" From ");
    remoteIP = Udp.remoteIP();  // from localIP
    Serial.print(remoteIP);
    Serial.print(", port ");  // from port
    Serial.println(Udp.remotePort());
    // if packet is correct size read it and display contents
    if (packetSize == sizeof(struct2)) {
      Udp.read((unsigned char*)&struct2, sizeof(struct2));  //packetBuffer, UDP_TX_PACKET_MAX_SIZE);
      Serial.print("Contents: ");
      Serial.print(" StructureID ");
      Serial.print(struct2.StructureID);
      Serial.print(" from Node ");
      Serial.print(struct2.NodeID);
      Serial.print(" seq number ");
      Serial.print(struct2.seq);
      Serial.print(" distance 1 = ");
      Serial.print(struct2.distance);
      Serial.print(" voltage 1 = ");
      Serial.println(struct2.voltage);
      checkSeqNumber(struct2.NodeID, struct2.seq);
    }
  }
  if (Serial.available()) {  // if key hit transmit structure
    while (Serial.available()) Serial.read();
    Serial.print("Transmitting datagram to ");
    displayIPaddress(remoteIP, port);
    struct1.seq++;          // increment packet sequence number
    struct1.distance += 1;  // setup test values
    struct1.voltage += 1;
    Serial.print(" Node ");
    Serial.print(nodeID);
    Serial.print(" seq number ");
    Serial.print(struct1.seq);
    Serial.print(" distance = ");
    Serial.print(struct1.distance);
    Serial.print(" voltage = ");
    Serial.println(struct1.voltage);
    Udp.beginPacket(remoteIP, port);  // send to remote localIP and port 999
    Udp.write((const uint8_t*)&struct1, sizeof(struct1));
    Udp.endPacket();
  }
  delay(10);
}

// set transmit mode
void checkSeqNumber(int NodeID, int seq) {
  static int seqNumbers[] = { 0, 0, 0, 0 }, seqErrors = 0;
  if (NodeID >= sizeof(seqNumbers) / sizeof(int)) return;
  if (seq != seqNumbers[NodeID]) {  // check for sequence error!
    Serial.print("  seq number error expected ");
    Serial.print(seqNumbers[NodeID]);
    Serial.print(" received ");
    Serial.print(seq);
    Serial.print("  seq  errors ");
    Serial.println(++seqErrors);
    seqNumbers[NodeID] = seq;
  }
  seqNumbers[NodeID]++;  // next sequence nunber expected
}


void displayIPaddress(const IPAddress address, unsigned int port) {
  Serial.print(" IP ");
  for (int i = 0; i < 4; i++) {
    Serial.print(address[i], DEC);
    if (i < 3) Serial.print(".");
  }
  Serial.print(" port ");
  Serial.println(port);
}

to test I used a Nano with a ENC28J60 ethernet module

// UDP transmit/receive structure using  ENC28J60  tested on UNO and Nano
// **** change IP addresses and ports to suit requirements *****

#include <EthernetENC.h>  // for  ENC28J60
#include <EthernetUdp.h>  // for UDP

#define nodeID 2  //  <<< set up required node ID

struct __attribute__((packed)) Struct1 {
  byte StructureID;  // identifies the structure type
  byte NodeID;       // ID of transmitting node
  int16_t seq;       // sequence number
  int32_t distance;
  float voltage;
};

Struct1 struct1 = { 1, nodeID, 0, 25, 4.5 }, struct2;

// *****   IP of this machine and remote machine *********
IPAddress localIP;  // these will be read from the keyboard
IPAddress remoteIP;

// Enter a MAC address
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEC };

unsigned int localPort = 999;   // local port to listen on
unsigned int remotePort = 999;  // remote port to transmiit too

// An EthernetUDP instance to let us send and receive packets over UDP
EthernetUDP Udp;

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(115200);
  while (!Serial)
    ;
  Serial.println("\nEthernet transmit/receive structure ");
  // Check for Ethernet hardware present

  // You can use Ethernet.init(pin) to configure the CS pin
  Ethernet.init(10);  // Most Arduino shields
                      //Ethernet.init(5);   // MKR ETH Shield
                      //Ethernet.init(0);   // Teensy 2.0
                      //Ethernet.init(20);  // Teensy++ 2.0
                      //Ethernet.init(15);  // ESP8266 with Adafruit FeatherWing Ethernet
                      //Ethernet.init(33);  // ESP32 with Adafruit FeatherWing Ethernet
  localIP = getIPaddress("\nenter local IP address (e.g. 192.168.1.176)? ");
  Serial.print("\nRemote ");
  //displayMACaddress(mac);
  mac[5] = localIP[3];  // change default MAC address
  displayMACaddress(mac);
  // start the Ethernet
  Ethernet.begin(mac, localIP);
  if (Ethernet.hardwareStatus() == EthernetNoHardware) {
    Serial.println("Ethernet shield was not found.  Sorry, can't run without hardware. :(");
    while (true) delay(1);  // do nothing, no point running without Ethernet hardware
  }
  if (Ethernet.linkStatus() == LinkOFF) {
    Serial.println("Ethernet cable is not connected.");
  }
  Udp.begin(localPort);  // start UDP
  Serial.print("Ethernet UDP started ");
  displayIPaddress(localIP, localPort);
  // get remote IP address
  remoteIP = getIPaddress("\nenter remote IP address (e.g. 192.168.1.176)? ");
  Serial.print("\nRemote ");
  displayIPaddress(remoteIP, remotePort);
}

void loop() {
  Udp.begin(localPort);
  // if Serial text entered transmit as a datagram
  if (Serial.available()) {
    while (Serial.available()) Serial.read();
    Serial.print("Transmitting datagram to ");
    displayIPaddress(remoteIP, remotePort);
    struct1.seq++;          // increment packet sequence number
    struct1.distance += 1;  // setup test values
    struct1.voltage += 1;
    Serial.print(" Node ");
    Serial.print(nodeID);
    Serial.print(" seq number ");
    Serial.print(struct1.seq);
    Serial.print(" distance = ");
    Serial.print(struct1.distance);
    Serial.print(" voltage = ");
    Serial.println(struct1.voltage);
    Udp.beginPacket(remoteIP, remotePort);  // send to remote localIP and port 999
    Udp.write((const uint8_t*)&struct1, sizeof(struct1));
    Udp.endPacket();
    Udp.endPacket();
  }
  // if there's data available, read a packet
  int packetSize = Udp.parsePacket();
  if (packetSize) {
    Serial.print("Received packet of size ");
    Serial.println(packetSize);
    Serial.print("From ");
    displayIPaddress(Udp.remoteIP(), Udp.remotePort());
    // if packet is correct size read it and display contents
    if (packetSize == sizeof(struct2)) {
      Udp.read((unsigned char*)&struct2, sizeof(struct2));  //packetBuffer, UDP_TX_PACKET_MAX_SIZE);
      Serial.print("Contents: ");
      Serial.print(" StructureID ");
      Serial.print(struct2.StructureID);
      Serial.print(" from Node ");
      Serial.print(struct2.NodeID);
      Serial.print(" seq number ");
      Serial.print(struct2.seq);
      Serial.print(" distance 1 = ");
      Serial.print(struct2.distance);
      Serial.print(" voltage 1 = ");
      Serial.println(struct2.voltage);
      checkSeqNumber(struct2.NodeID, struct2.seq);
    }
  }
  delay(10);
}

// read IP address from keyboard and check it
IPAddress getIPaddress(const char* prompt) {
  IPAddress ip;
  while (1) {  // read  IP (end with new line)
    Serial.print(prompt);
    while (Serial.available() == 0) delay(10);
    char text[40] = { 0 };
    Serial.readBytesUntil('\n', (char*)text, 40);
    //Serial.println(text);
    if (ip.fromString(text)) break;  // if IP OK break while
    Serial.println("\ninvalid IP try again");
  }
  return ip;
}

// set transmit mode
void checkSeqNumber(int NodeID, int seq) {
  static int seqNumbers[] = { 0, 0, 0, 0 }, seqErrors = 0;
  if (NodeID >= sizeof(seqNumbers) / sizeof(int)) return;
  if (seq != seqNumbers[NodeID]) {  // check for sequence error!
    Serial.print("  seq number error expected ");
    Serial.print(seqNumbers[NodeID]);
    Serial.print(" received ");
    Serial.print(seq);
    Serial.print("  seq  errors ");
    Serial.println(++seqErrors);
    seqNumbers[NodeID] = seq;
  }
  seqNumbers[NodeID]++;  // next sequence nunber expected
}


// print IPAdress and port
void displayIPaddress(const IPAddress address, unsigned int port) {
  Serial.print(" IP ");
  for (int i = 0; i < 4; i++) {
    Serial.print(address[i], DEC);
    if (i < 3) Serial.print(".");
  }
  Serial.print(" port ");
  Serial.println(port);
}

void displayMACaddress(byte address[]) {
  Serial.print("MAC address ");
  for (int i = 0; i < 6; i++) {
    Serial.print("0x");
    Serial.print(address[i], HEX);
    if (i < 5) Serial.print(".");
  }
  Serial.println();
}

Mega serial output (hit serial monitor keyboard to transmit a datagram)

UDP transmit/receive structure 
Configured Ethernet using DHCP OK
My localIP address: 192.168.1.177
UDP begin() OK
UDP_TX_PACKET_MAX_SIZE 100
Received packet of size 12 From 192.168.1.176, port 999
Contents:  StructureID 1 from Node 2 seq number 1 distance 1 = 26 voltage 1 = 5.50
  seq number error expected 0 received 1  seq  errors 1
Received packet of size 12 From 192.168.1.176, port 999
Contents:  StructureID 1 from Node 2 seq number 2 distance 1 = 27 voltage 1 = 6.50
Received packet of size 12 From 192.168.1.176, port 999
Contents:  StructureID 1 from Node 2 seq number 3 distance 1 = 28 voltage 1 = 7.50
Transmitting datagram to  IP 192.168.1.176 port 999
 Node 1 seq number 1 distance = 26 voltage = 5.50
Transmitting datagram to  IP 192.168.1.176 port 999
 Node 1 seq number 2 distance = 27 voltage = 6.50
Transmitting datagram to  IP 192.168.1.176 port 999
 Node 1 seq number 3 distance = 28 voltage = 7.50

nano output

Ethernet transmit/receive structure 

enter local IP address (e.g. 192.168.1.176)? 
Remote MAC address 0xDE.0xAD.0xBE.0xEF.0xFE.0xB0
Ethernet cable is not connected.
Ethernet UDP started  IP 192.168.1.176 port 999

enter remote IP address (e.g. 192.168.1.176)? 
Remote  IP 192.168.1.177 port 999
Transmitting datagram to  IP 192.168.1.177 port 999
 Node 2 seq number 1 distance = 26 voltage = 5.50
Transmitting datagram to  IP 192.168.1.177 port 999
 Node 2 seq number 2 distance = 27 voltage = 6.50
Transmitting datagram to  IP 192.168.1.177 port 999
 Node 2 seq number 3 distance = 28 voltage = 7.50
Received packet of size 12
From  IP 192.168.1.177 port 999
Contents:  StructureID 1 from Node 1 seq number 1 distance 1 = 26 voltage 1 = 5.50
  seq number error expected 0 received 1  seq  errors 1
Received packet of size 12
From  IP 192.168.1.177 port 999
Contents:  StructureID 1 from Node 1 seq number 2 distance 1 = 27 voltage 1 = 6.50
Received packet of size 12
From  IP 192.168.1.177 port 999
Contents:  StructureID 1 from Node 1 seq number 3 distance 1 = 28 voltage 1 = 7.50
1 Like

Hi @horace

Thx for the fulsome reply. Especially appreciate the explanation of data size specs.

I will head back to the dungeon this afternoon to copy and modify this example for my purpose. Can you answer the purpose and mechanics of the checkSeqNumber function? I understand the use of Serial.print to interact with illustrative code, but I want to be sure I don't strip anything important when I strip the code for stand-alone application.

Thx. D

to check for lost or duplicate datagrams (remember UDP is unreliable) each transmitted datagram carries a sequence number - the receiver keeps a note of the sequence number expected next so when a datagram arrives it can check if it is the one expected and if any have been lost or duplicated
a sophisticated system could request a resend of lost datagrams - these days it is simpler to use TCP
for further error checking you can a CRC or checksum to the structure

I have always hated the packed attribute. It can force the compiler to generate extra code to handle unaligned data and is easy to forget (common header files help).

Without it the compiler will generate fillers to force boundary alignment which could be different on different processors. The solution is to order the data by the boundary requirements of the most restrictive processor, so that no fillers (or packed attribute) is required.

[EDIT]
Remove confusion/incorrect example

Of course if you have to interface with existing poor code that you can't change go for it.

1 Like

Hello @horace and @oldcurmudgeon

@horace Good to know about how the sequence numbering works. That has value to my application as I need to know if the comms between the two Megas is on or off. I don't have need for retransmission, I can just wait for comms to re-establish.

Also, I think UDP will continue to work for this application as UDP is already used to send commands to the digital audio mixers. <OSCMessage.h>

@oldcurmudgeon Thanks for the warnings. My application is fixed scope between two Megas. I don't need to manage integration of unknown processors.

My application is to send custom commands to a number of Midas mixers on a LAN from two remote locations. Some coordination between the locations is required - thus the need for simple messaging between the two Megas.

Again, thx for all your help.
I'll let you know how I make out.

D

good point! there are advantages/disadvantages to using packed attribute
just tested with ESP32 based WS-ETH01 ethernet module
both

struct __attribute__((packed)) Struct1 {
  byte StructureID;  // identifies the structure type
  byte NodeID;       // ID of transmitting node
  int16_t seq;       // sequence number
  int32_t distance;
  float voltage;
};

and

struct  Struct1 {
  byte StructureID;  // identifies the structure type
  byte NodeID;       // ID of transmitting node
  int16_t seq;       // sequence number
  int32_t distance;
  float voltage;
};

are sizeof 12 bytes on Nano, Mega and ESP32 therefore packed attribute is not required

first started using packed attribute when porting P2P communication programs using LoRa modules with various hosts, e.g. Nano, Pro Micro, Pro Mini, ESP32, RP2040, Due, 32u4, STM32, etc

ESP32 WS-ETH01 code to communicate with Mega and Nano code in post 10

// UDP ESP32 WS-ETH01 ethernet transmit/receive structure

// UDP chat program using  ESP32-ETH01  tested on UNO and Nano
// **** change IP addresses and ports to suit requirements *****

#include <EthernetESP32.h>
#include <EthernetUdp.h>  // for UDP

#define nodeID 3  //  <<< set up required node ID

//struct __attribute__((packed)) Struct1 {
struct  Struct1 {
  byte StructureID;  // identifies the structure type
  byte NodeID;       // ID of transmitting node
  int16_t seq;       // sequence number
  int32_t distance;
  float voltage;
};

Struct1 struct1 = { 1, nodeID, 0, 25, 4.5 }, struct2;

//  EMACDriver(EthPhyType phyType, int mdcPin = 23, int mdioPin = 18, int powerPin = -1, emac_rmii_clock_gpio_t clockPin = EMAC_APPL_CLK_OUT_GPIO, emac_rmii_clock_mode_t clockMode = EMAC_CLK_EXT_IN);
EMACDriver driver(ETH_PHY_LAN8720, 23, 18, 16, EMAC_CLK_IN_GPIO);  // note powerPin = 16 required


// *****   IP of this machine and remote machine *********
IPAddress localIP(192, 168, 1, 167);  // local static localIP address
IPAddress remoteIP(0, 0, 0, 0);       // local static localIP address

// Enter a MAC address
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x32 };

unsigned int localPort = 999;   //10000;   // local port to listen on
unsigned int remotePort = 999;  //10000;  // remote port to transmiit too

// An EthernetUDP instance to let us send and receive packets over UDP
EthernetUDP Udp;

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(115200);
  delay(2000);
  while (!Serial) 
    ;
  Serial.println("\n\nUDP ESP32 WS-ETH01 ethernet transmit/receive structure ");
  Serial.print("sizeof(struct1) ");
  Serial.println(sizeof(struct1));

  // Check for Ethernet hardware present
  // You can use Ethernet.init(pin) to configure the CS pin
  //Ethernet.init(10);  // Most Arduino shields
  //Ethernet.init(5);   // MKR ETH Shield
  //Ethernet.init(0);   // Teensy 2.0
  //Ethernet.init(20);  // Teensy++ 2.0
  //Ethernet.init(15);  // ESP8266 with Adafruit FeatherWing Ethernet
  //Ethernet.init(33);  // ESP32 with Adafruit FeatherWing Ethernet
  Ethernet.init(driver);  // for ESP32-ETH01
  Serial.println("Initialize Ethernet with DHCP:");
  if (Ethernet.begin()) {
    Serial.print("  DHCP assigned IP ");
    Serial.println(Ethernet.localIP());
  } else {
    Serial.println("Failed to configure Ethernet using DHCP");
    Ethernet.begin(localIP);
    Serial.print("configured Ethernet static IP ");
    Serial.println(Ethernet.localIP());
    // while (true) {
    // delay(1);
  }
  if (Udp.begin(localPort))  // start UDP
    Serial.print("Ethernet UDP started ");
  else Serial.println("UDP begin() failed!");
  displayIPaddress(Ethernet.localIP(),  localPort);
}

void loop() {
  // if Serial text entered transmit as a datagram
  if (Serial.available()) {
    while (Serial.available()) Serial.read();
    // if no datagram has been receive request remote IP address
    if (remoteIP[0] == 0) {
      // get remote IP address
      remoteIP = getIPaddress("\nenter remote IP address (e.g. 192.168.1.176)? ");
    }
    Serial.print("Transmitting datagram to ");
    displayIPaddress(remoteIP, remotePort);
    struct1.seq++;          // increment packet sequence number
    struct1.distance += 1;  // setup test values
    struct1.voltage += 1;
    Serial.print(" Node ");
    Serial.print(nodeID);
    Serial.print(" seq number ");
    Serial.print(struct1.seq);
    Serial.print(" distance = ");
    Serial.print(struct1.distance);
    Serial.print(" voltage = ");
    Serial.println(struct1.voltage);
    Udp.beginPacket(remoteIP, remotePort);  // send to remote localIP and port 999
    Udp.write((const uint8_t*)&struct1, sizeof(struct1));
    Udp.endPacket();
  }
  // if there's data available, read a packet
  int packetSize = Udp.parsePacket();
  if (packetSize) {
    Serial.print("Received packet of size ");
    Serial.print(packetSize);  // datagram packet size
    Serial.print(" From ");
    remoteIP = Udp.remoteIP();  // from localIP
    Serial.print(remoteIP);
    Serial.print(", port ");  // from port
    Serial.println(Udp.remotePort());
    // if packet is correct size read it and display contents
    if (packetSize == sizeof(struct2)) {
      Udp.read((unsigned char*)&struct2, sizeof(struct2));  //packetBuffer, UDP_TX_PACKET_MAX_SIZE);
      Serial.print("Contents: ");
      Serial.print(" StructureID ");
      Serial.print(struct2.StructureID);
      Serial.print(" from Node ");
      Serial.print(struct2.NodeID);
      Serial.print(" seq number ");
      Serial.print(struct2.seq);
      Serial.print(" distance 1 = ");
      Serial.print(struct2.distance);
      Serial.print(" voltage 1 = ");
      Serial.println(struct2.voltage);
      checkSeqNumber(struct2.NodeID, struct2.seq);
    }
  }
  delay(10);
}

// read IP address from keyboard and check it
IPAddress getIPaddress(const char* prompt) {
  IPAddress ip;
  while (1) {  // read  IP (end with new line)
    Serial.print(prompt);
    while (Serial.available() == 0) delay(10);
    char text[40] = { 0 };
    Serial.readBytesUntil('\n', (char*)text, 40);
    for (int i = 0; i < 40; i++)
      if (text[i] < ' ') text[i] = 0;  // remove CR or LF
    Serial.print(text);
    if (ip.fromString(text)) break;  // if IP OK break while
    Serial.println(" invalid IP try again");
  }
  return ip;
}


// print IPAdress and port
void displayIPaddress(const IPAddress address, unsigned int port) {
  Serial.print(" IP ");
  for (int i = 0; i < 4; i++) {
    Serial.print(address[i], DEC);
    if (i < 3) Serial.print(".");
  }
  Serial.print(" port ");
  Serial.println(port);
}

void displayMACaddress(byte address[]) {
  Serial.print("MAC address ");
  for (int i = 0; i < 6; i++) {
    Serial.print("0x");
    Serial.print(address[i], HEX);
    if (i < 5) Serial.print(".");
  }
  Serial.println();
}

// set transmit mode
void checkSeqNumber(int NodeID, int seq) {
  static int seqNumbers[] = { 0, 0, 0, 0 }, seqErrors = 0;
  if (NodeID >= sizeof(seqNumbers) / sizeof(int)) return;
  if (seq != seqNumbers[NodeID]) {  // check for sequence error!
    Serial.print("  seq number error expected ");
    Serial.print(seqNumbers[NodeID]);
    Serial.print(" received ");
    Serial.print(seq);
    Serial.print("  seq  errors ");
    Serial.println(++seqErrors);
    seqNumbers[NodeID] = seq;
  }
  seqNumbers[NodeID]++;  // next sequence nunber expected
}

ESP32 serial monitor output

UDP ESP32 WS-ETH01 ethernet transmit/receive structure 
sizeof(struct1) 12
Initialize Ethernet with DHCP:
  DHCP assigned IP 192.168.1.67
Ethernet UDP started  IP 192.168.1.67 port 999
Received packet of size 12 From 192.168.1.177, port 999
Contents:  StructureID 1 from Node 1 seq number 21 distance 1 = 46 voltage 1 = 25.50
  seq number error expected 0 received 21  seq  errors 1
Received packet of size 12 From 192.168.1.177, port 999
Contents:  StructureID 1 from Node 1 seq number 22 distance 1 = 47 voltage 1 = 26.50
Received packet of size 12 From 192.168.1.177, port 999
Contents:  StructureID 1 from Node 1 seq number 23 distance 1 = 48 voltage 1 = 27.50
Transmitting datagram to  IP 192.168.1.177 port 999
 Node 3 seq number 1 distance = 26 voltage = 5.50
Transmitting datagram to  IP 192.168.1.177 port 999
 Node 3 seq number 2 distance = 27 voltage = 6.50
Received packet of size 12 From 192.168.1.177, port 999
Contents:  StructureID 1 from Node 1 seq number 24 distance 1 = 49 voltage 1 = 28.50

Hi @horace

I am struggling to understand how the checkSeqNumber function operates.
Can you provide a little more explanation of each of the lines...

static int seqNumbers[] = { 0, 0, 0, 0 }, seqErrors = 0;
      if (NodeID >= sizeof(seqNumbers) / sizeof(int)) return;
      if (seq != seqNumbers[NodeID]) {

I think the first line creates an array of four integers to store the sequence numbers for up to four external nodes. Yes? No?

I am totally flummoxed on the second line.

I think the third line compares the seq number to the expected (below incremented) sequence number. Yes?

I also don't understand how this function maintains record of the sequence numbers. Isn't
static int seqNumbers[] = { 0, 0, 0, 0 }
reset every time the function is called?

Thanks again for you gentle assistance. D

The second line checks to see if the NodeID is greater or equal the number of elements in seqNumbers

sizeof(seqNumbers) / sizeof(int))

(total size of seqNumbers divided by the size of one element)

If so it is an error so it just hangs it up with a return.

No static means it is global allocated and retains values between calls.

Ah! Thx @oldcurmudgeon
D

Hi @PerryBebbington , @horace and @oldcurmudgeon
Thanks a tonne for all your help and assistance.

I've successfully created a sketch for my two Mega2560's that will pass 8 bits of data between them, alert me when the tx-rx is faulty, and re-establish tx-rx automatically. I'm sure more robust and elegant solutions are possible, but I would appreciate your experienced view towards potential faults I cannot perceive. My next step is to add OSC commands to be sent to Midas audio mixers in our ecosystem as commanded/coordinated by these Megas.

I do have one question about how often is reasonable to run the tx. Currently I have it set to 250ms, but my initial idea was to use 100ms. What I don't understand is the impact this setup will have on the LAN in our facility. Can you offer any thoughts?

Again, thx for your assistance. D

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

// setup IO consts
const int In_Pins[] = {23, 25,  27, 29, 31, 33, 35, 37}; // input button pins
const int LED_Pin = 53; // green LED to indicate good comms

// setup vars and consts for comms
unsigned long Curr_ms   = 0;        // Current time for debouncers
unsigned long Nexttx_ms = 0;        // time of next transmission
unsigned long Stalerx_ms = 0;       // time after rx becomes stale
const unsigned long tx_ms = 250;    // period of transmissions
const float StaleMult = 2.5;        // multiples of tx period to stale rx
byte LocData = 0;                   // block of 8 bits to be tx'd to other node
byte RemData = 0;                   // block of 8 bits rx'd from other node
bool CommsOK = false;               // good data blocks from other node

// structure for tx and rx
// pretty simple, a sequence number and the actual 8 bits of interest
struct __attribute__((packed)) dBlock {
  int16_t Seq;      // sequence number
  byte DigDat;      // 8 bits of usable data
};
// create two structures one for tx one for rx
dBlock txBlock = {0, 0 }, rxBlock;

// Setup for Arduino #1 - rem out #1 or #2
byte mac[] = {0xA8, 0x61, 0x0A, 0xAE, 0x4F, 0xF9};  // #1 MAC address
IPAddress locIP(192, 168, 1, 134);                  // local static localIP address
IPAddress remIP(192, 168, 1, 136);                  // remote static localIP address 

// Setup for Arduino #2 - rem out #1 or #2
// byte mac[] = {0xA8, 0x61, 0x0A, 0xAF, 0x19, 0x24};  // #2 MAC address
// IPAddress locIP(192, 168, 1, 136);                  // local static localIP address
// IPAddress remIP(192, 168, 1, 134);                  // remote static localIP address

// custom port for this purpose
unsigned int txrxPort = 23900;                         // port to rx-tx mega-mega

// An EthernetUDP instance to let us send and receive packets over UDP
EthernetUDP Udp;

void setup() {
  Serial.begin(9600);             // setup diagonistic printing
  while (!Serial) {}
  Ethernet.begin(mac, locIP);     // static IP initialize Ethernet shield
  Udp.begin(txrxPort);            // start UDP 

  // initialize digital pins
  for (int i=0; i<sizeof In_Pins; i++) pinMode(In_Pins[i], INPUT);
  pinMode(LED_Pin, OUTPUT);
}

void loop() {
  // Set current time for the cycle
	Curr_ms  = millis();

  // got a new datagram so reel it in
  int packetSize = Udp.parsePacket();
  if (packetSize) {
    // assume packet was bad until proven good
    CommsOK = false;
    // check if packet is correct size then read it
    if (packetSize == sizeof(rxBlock)) {
      Udp.read((unsigned char*)&rxBlock, sizeof(rxBlock));
      // check if packet is right sequence
      if (CheckRx(rxBlock.Seq)) {
        RemData = rxBlock.DigDat;
        CommsOK = true;
        Stalerx_ms = Curr_ms + (tx_ms * StaleMult);
      }
    }
  }
// check for stale data
if (Curr_ms >= Stalerx_ms) CommsOK = false;

// set status LED
digitalWrite(LED_Pin, CommsOK);

// read and assemble the datablock to be transmitted
for (int i=0; i<sizeof In_Pins; i++) bitWrite(LocData, i, digitalRead(In_Pins[i]));

// run the tx every now and then
if(Curr_ms >= Nexttx_ms) {
  // assemble tx block
  txBlock.Seq++;
  txBlock.DigDat = LocData;
  // do transmit magic
  Udp.beginPacket(remIP, txrxPort);  // send to remote localIP and custom port
  Udp.write((const uint8_t*)&txBlock, sizeof(txBlock));
  Udp.endPacket();
  // set time for next tx
  Nexttx_ms = Curr_ms + tx_ms;
}

// diagnostic printing
  if (Serial.available()) {  // if CR print diagnostics
    while (Serial.available()) Serial.read();
    Serial.println("");
    Serial.print("Curent recieved data: ");
    Serial.println(RemData, BIN);
    Serial.print("Last rx seq: ");
    Serial.println(rxBlock.Seq);
    Serial.print("Current transmit data: ");
    Serial.println(LocData, BIN);
    Serial.print("Last tx seq: ");
    Serial.println(txBlock.Seq);
  }
}

// Check received sequence number
boolean CheckRx(int16_t seq) {
  static int16_t ExpSeq = 0;    // next expected received seq  
  if (seq == ExpSeq) {
    ExpSeq = seq+1;
    return true;
  } else {
    ExpSeq = seq+1;
    return false;
  }
}

I adapted much of the code of post 10 from a LoRa application with a number (up to 4) transmitters sending data to a single receiver - the transmitters had NodeID's 0 thru 3 where the next sequence numbers expected were stored in seqNumbers[]
the line

if (NodeID >= sizeof(seqNumbers) / sizeof(int)) return;

was a check for a NodeID > 3 which should not happen (there were 4 transmitters)
in this case the function returns ignoring the sequence number check
what it should do is also display an error message before returning to indicate there is a problem - possible a transmitter has been added with NodeID 4 without updating the receiver code

the Ethernet UDP code of post 10 has single transmitter/receiver and does not need the seqNumbers[] array - the code could be updated to use a single variable, e.g.

// check sequence number
void checkSeqNumber(int NodeID, int seq) {
  static int seqNumber=0, seqErrors = 0;
  if (seq != seqNumber) {  // check for sequence error!
    Serial.print("  seq number error expected ");
    Serial.print(seqNumber);
    Serial.print(" received ");
    Serial.print(seq);
    Serial.print("  seq  errors ");
    Serial.println(++seqErrors);
    seqNumber = seq;
  }
  seqNumber++;  // next sequence nunber expected
}

the seqNumbers[] array would be useful if one had a number of transmitters sending UDP datagrams to a receiver

shows one has be careful porting code between applications without thorough testing

EDIT:: worth noting the structure contained

byte StructureID;  

the LoRa transmitters could send different structures with different data elements
the receiver would use StructureID to identify the different structures and extract the data - see post decimals-strings-and-lora